前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SPlisHSPlasH\Simulator\SimulatorBase.cpp

SPlisHSPlasH\Simulator\SimulatorBase.cpp

作者头像
周星星9527
发布2020-11-11 16:11:30
2.1K0
发布2020-11-11 16:11:30
举报

mrk it up!

代码语言:javascript
复制
#include "SimulatorBase.h"
#include "SPlisHSPlasH/Utilities/SceneLoader.h"
#include "Utilities/FileSystem.h"
#include "SPlisHSPlasH/TimeManager.h"
#include "Utilities/PartioReaderWriter.h"
#include "SPlisHSPlasH/Emitter.h"
#include "SPlisHSPlasH/EmitterSystem.h"
#include "SPlisHSPlasH/Simulation.h"
#include "SPlisHSPlasH/Vorticity/MicropolarModel_Bender2017.h"
#include "NumericParameter.h"
#include "Utilities/Logger.h"
#include "Utilities/Timing.h"
#include "Utilities/Counting.h"
#include "Utilities/Version.h"
#include "Utilities/SystemInfo.h"
#include "extern/partio/src/lib/Partio.h"
#include "SPlisHSPlasH/Utilities/GaussQuadrature.h"
#include "SPlisHSPlasH/Utilities/SimpleQuadrature.h"
#include "extern/cxxopts/cxxopts.hpp"
#include "Simulator/GUI/Simulator_GUI_Base.h"
#include "SPlisHSPlasH/Utilities/VolumeSampling.h"
#include "Utilities/OBJLoader.h"
#include "Utilities/BinaryFileReaderWriter.h"
#include "PositionBasedDynamicsWrapper/PBDBoundarySimulator.h"
#include "StaticBoundarySimulator.h"

INIT_LOGGING
INIT_TIMING
INIT_COUNTING

using namespace SPH;
using namespace std;
using namespace GenParam;
using namespace Utilities;

int SimulatorBase::PAUSE = -1;
int SimulatorBase::PAUSE_AT = -1;
int SimulatorBase::STOP_AT = -1;
int SimulatorBase::NUM_STEPS_PER_RENDER = -1;
int SimulatorBase::PARTIO_EXPORT = -1;
int SimulatorBase::VTK_EXPORT = -1;
int SimulatorBase::RB_VTK_EXPORT = -1;
int SimulatorBase::RB_EXPORT = -1;
int SimulatorBase::DATA_EXPORT_FPS = -1;
int SimulatorBase::PARTICLE_EXPORT_ATTRIBUTES = -1;
int SimulatorBase::STATE_EXPORT = -1;
int SimulatorBase::STATE_EXPORT_FPS = -1;
int SimulatorBase::RENDER_WALLS = -1;
int SimulatorBase::ENUM_WALLS_NONE = -1;
int SimulatorBase::ENUM_WALLS_PARTICLES_ALL = -1;
int SimulatorBase::ENUM_WALLS_PARTICLES_NO_WALLS = -1;
int SimulatorBase::ENUM_WALLS_GEOMETRY_ALL = -1;
int SimulatorBase::ENUM_WALLS_GEOMETRY_NO_WALLS = -1;

 
SimulatorBase::SimulatorBase()
{
  Utilities::logger.addSink(unique_ptr<Utilities::ConsoleSink>(new Utilities::ConsoleSink(Utilities::LogLevel::INFO)));

  m_boundarySimulator = nullptr;
  m_gui = nullptr;
  m_isStaticScene = true;
  m_numberOfStepsPerRenderUpdate = 4;
  m_sceneFile = "";
  m_renderWalls = 4;
  m_doPause = true;
  m_pauseAt = -1.0;
  m_stopAt = -1.0;
  m_useParticleCaching = true;
  m_useGUI = true;
  m_enablePartioExport = false;
  m_enableVTKExport = false;
  m_enableRigidBodyVTKExport = false;
  m_enableRigidBodyExport = false;
  m_enableStateExport = false;
  m_framesPerSecond = 25;
  m_framesPerSecondState = 1;
  m_nextFrameTime = 0.0;
  m_nextFrameTimeState = 0.0;
  m_frameCounter = 1;
  m_isFirstFrame = true;
  m_isFirstFrameVTK = true;
  m_firstState = true;
  m_colorField.resize(1, "velocity");
  m_colorMapType.resize(1, 0);
  m_renderMinValue.resize(1, 0.0);
  m_renderMaxValue.resize(1, 5.0);
  m_particleAttributes = "velocity";
  m_timeStepCB = nullptr;
#ifdef DL_OUTPUT
  m_nextTiming = 1.0;
#endif
}

SimulatorBase::~SimulatorBase()
{
  Utilities::Timing::printAverageTimes();
  Utilities::Timing::printTimeSums();

  Utilities::Counting::printAverageCounts();
  Utilities::Counting::printCounterSums();

  delete m_boundarySimulator;
}

void SimulatorBase::initParameters()
{
  ParameterObject::initParameters();

  PAUSE = createBoolParameter("pause", "Pause", &m_doPause);
  setGroup(PAUSE, "General");
  setDescription(PAUSE, "Pause simulation.");
  setHotKey(PAUSE, "space");

  PAUSE_AT = createNumericParameter("pauseAt", "Pause simulation at", &m_pauseAt);
  setGroup(PAUSE_AT, "General");
  setDescription(PAUSE_AT, "Pause simulation at the given time. When the value is negative, the simulation is not paused.");

  STOP_AT = createNumericParameter("stopAt", "Stop simulation at", &m_stopAt);
  setGroup(STOP_AT, "General");
  setDescription(STOP_AT, "Stop simulation at the given time. When the value is negative, the simulation is not stopped.");

  NUM_STEPS_PER_RENDER = createNumericParameter("numberOfStepsPerRenderUpdate", "# time steps / update", &m_numberOfStepsPerRenderUpdate);
  setGroup(NUM_STEPS_PER_RENDER, "Visualization");
  setDescription(NUM_STEPS_PER_RENDER, "Number of simulation steps per rendered frame.");
  static_cast<NumericParameter<unsigned int>*>(getParameter(NUM_STEPS_PER_RENDER))->setMinValue(1);

  RENDER_WALLS = createEnumParameter("renderWalls", "Render walls", &m_renderWalls);
  setGroup(RENDER_WALLS, "Visualization");
  setDescription(RENDER_WALLS, "Make walls visible/invisible.");
  EnumParameter *enumParam = static_cast<EnumParameter*>(getParameter(RENDER_WALLS));
  enumParam->addEnumValue("None", ENUM_WALLS_NONE);
  enumParam->addEnumValue("Particles (all)", ENUM_WALLS_PARTICLES_ALL);
  enumParam->addEnumValue("Particles (no walls)", ENUM_WALLS_PARTICLES_NO_WALLS);
  enumParam->addEnumValue("Geometry (all)", ENUM_WALLS_GEOMETRY_ALL);
  enumParam->addEnumValue("Geometry (no walls)", ENUM_WALLS_GEOMETRY_NO_WALLS);

  PARTIO_EXPORT = createBoolParameter("enablePartioExport", "Partio export", &m_enablePartioExport);
  setGroup(PARTIO_EXPORT, "Export");
  setDescription(PARTIO_EXPORT, "Enable/disable partio export.");

  RB_EXPORT = createBoolParameter("enableRigidBodyExport", "Rigid body export", &m_enableRigidBodyExport);
  setGroup(RB_EXPORT, "Export");
  setDescription(RB_EXPORT, "Enable/disable rigid body export.");

  VTK_EXPORT = createBoolParameter("enableVTKExport", "VTK export", &m_enableVTKExport);
  setGroup(VTK_EXPORT, "Export");
  setDescription(VTK_EXPORT, "Enable/disable VTK export.");

  RB_VTK_EXPORT = createBoolParameter("enableRigidBodyVTKExport", "Rigid body VTK export", &m_enableRigidBodyVTKExport);
  setGroup(RB_VTK_EXPORT, "Export");
  setDescription(RB_VTK_EXPORT, "Enable/disable rigid body VTK export.");

  DATA_EXPORT_FPS = createNumericParameter("dataExportFPS", "Export FPS", &m_framesPerSecond);
  setGroup(DATA_EXPORT_FPS, "Export");
  setDescription(DATA_EXPORT_FPS, "Frame rate of partio, vtk and rigid body export.");

  STATE_EXPORT = createBoolParameter("enableStateExport", "Simulation state export", &m_enableStateExport);
  setGroup(STATE_EXPORT, "Export");
  setDescription(STATE_EXPORT, "Enable/disable export of complete simulation state.");

  STATE_EXPORT_FPS = createNumericParameter("stateExportFPS", "State export FPS", &m_framesPerSecondState);
  setGroup(STATE_EXPORT_FPS, "Export");
  setDescription(STATE_EXPORT_FPS, "Frame rate of simulation state export.");


  PARTICLE_EXPORT_ATTRIBUTES = createStringParameter("particleAttributes", "Export attributes", &m_particleAttributes);
  getParameter(PARTICLE_EXPORT_ATTRIBUTES)->setReadOnly(true);
  setGroup(PARTICLE_EXPORT_ATTRIBUTES, "Export");
  setDescription(PARTICLE_EXPORT_ATTRIBUTES, "Attributes that are exported in the partio files (except id and position).");
}

void SimulatorBase::run()
{
  initSimulation();
  runSimulation();
  cleanup();
}

void SimulatorBase::init(std::vector<std::string> argv, const std::string &windowName){
  m_argc = static_cast<int>(argv.size());
  m_argv_vec.clear();
  m_argv_vec.reserve(argv.size());
  for (auto & a : argv)
    m_argv_vec.push_back(&a[0]);

  m_argv = m_argv_vec.data();
  init(m_argc, m_argv, windowName);
}

void SimulatorBase::init(int argc, char **argv, const std::string &windowName)
{
  m_argc = argc;
  m_argv = argv;
  m_windowName = windowName;

  initParameters();
  m_exePath = FileSystem::getProgramPath();
  setUseParticleCaching(true);

  try
  {
    cxxopts::Options options(argv[0], "SPlisHSPlasH - An open-source library for the physically-based simulation of fluids.");
    options
      .positional_help("[scene file]")
      .show_positional_help();

    options.add_options()
      ("h,help", "Print help")
      ("v,version", "Print version")
      ("no-cache", "Disable caching of boundary samples/maps.")
      ("state-file", "State file (state_<time>.bin) that should be loaded.", cxxopts::value<std::string>())
      ("output-dir", "Output directory for log file and partio files.", cxxopts::value<std::string>())
      ("no-initial-pause", "Disable initial pause when starting the simulation.")
      ("no-gui", "Disable GUI.")
      ("stopAt", "Sets or overwrites the stopAt parameter of the scene.", cxxopts::value<Real>())
      ("param", "Sets or overwrites a parameter of the scene.\n\n" 
            "- Setting a fluid parameter:\n\t<fluid-id>:<parameter-name>:<value>\n"
            "- Example: --param Fluid:viscosity:0.01\n\n"
            "- Setting a configuration parameter:\n\t<parameter-name>:<value>\n"
            "- Example: --param cflMethod:1\n"
            , cxxopts::value<std::string>())
      ;

    options.add_options("invisible")
      ("scene-file", "Scene file", cxxopts::value<std::string>());

    options.parse_positional("scene-file");
    auto result = options.parse(argc, argv);

    if (result.count("help"))
    {
      std::cout << options.help({ "" }) << std::endl;
      exit(0);
    }

    if (result.count("version"))
    {
      std::cout << SPLISHSPLASH_VERSION << std::endl;
      exit(0);
    }

    if (result.count("no-cache"))
    {
      setUseParticleCaching(false);
    }

    if (result.count("no-gui"))
    {
      setUseGUI(false);
    }

    if (result.count("stopAt"))
    {
      m_stopAt = result["stopAt"].as<Real>();
    }

    if (result.count("param"))
    {
      const string paramStr = result["param"].as<std::string>();
      Utilities::StringTools::tokenize(paramStr, m_paramTokens, ":");

      // add third element to get a unified method for all parameters
      if (m_paramTokens.size() == 2)
        m_paramTokens.insert(m_paramTokens.begin(), "config");
      if (m_paramTokens.size() != 3)
      {
        LOG_ERR << "--param has wrong syntax!";
        LOG_ERR << "Example 1: --param Fluid:viscosity:0.01";
        LOG_ERR << "Example 2: --param cflMethod:1";
        exit(1);
      }
    }

    m_sceneFile = "";

    if (result.count("scene-file"))
    {
      m_sceneFile = result["scene-file"].as<std::string>();
      if (FileSystem::isRelativePath(m_sceneFile))
        m_sceneFile = FileSystem::normalizePath(m_exePath + "/" + m_sceneFile);
    }
#ifdef WIN32
    else
    {
      std::string scenePath = FileSystem::normalizePath(m_exePath + "/../data/Scenes/");
      if (!FileSystem::isDirectory(scenePath))
        scenePath = m_exePath;
      std::replace(scenePath.begin(), scenePath.end(), '/', '\\');
      m_sceneFile = FileSystem::fileDialog(0, scenePath.c_str(), "*.json");

      if (m_sceneFile == "")
        exit(0);
    }
#endif

    m_outputPath = FileSystem::normalizePath(getExePath() + "/output/" + FileSystem::getFileName(m_sceneFile));
    if (result.count("output-dir"))
    {
      m_outputPath = result["output-dir"].as<std::string>();
    }

    if (result.count("no-initial-pause"))
    {
      m_doPause = false;
    }

    setStateFile("");
    if (result.count("state-file"))
    {
      setStateFile(result["state-file"].as<std::string>());
      if (FileSystem::isRelativePath(getStateFile()))
        setStateFile(FileSystem::normalizePath(m_exePath + "/" + getStateFile()));
    }
  }
  catch (const cxxopts::OptionException& e)
  {
    std::cout << "error parsing options: " << e.what() << std::endl;
    exit(1);
  }


  std::string logPath = FileSystem::normalizePath(m_outputPath + "/log");
  FileSystem::makeDirs(logPath);
  Utilities::logger.addSink(unique_ptr<Utilities::FileSink>(new Utilities::FileSink(Utilities::LogLevel::DEBUG, logPath + "/SPH_log.txt")));

  LOG_INFO  << "SPlisHSPlasH version: " << SPLISHSPLASH_VERSION;
  LOG_DEBUG << "Git refspec:          " << GIT_REFSPEC;
  LOG_DEBUG << "Git SHA1:             " << GIT_SHA1;
  LOG_DEBUG << "Git status:           " << GIT_LOCAL_STATUS;
  LOG_DEBUG << "Host name:            " << SystemInfo::getHostName();

  if (!getUseParticleCaching())
    LOG_INFO << "Boundary cache disabled.";
  LOG_INFO << "Output directory: " << m_outputPath;

  //////////////////////////////////////////////////////////////////////////
  // read scene
  //////////////////////////////////////////////////////////////////////////
  m_sceneLoader = std::unique_ptr<SceneLoader>(new SceneLoader());
  if (m_sceneFile != "")
    m_sceneLoader->readScene(m_sceneFile.c_str(), m_scene);
  else
    return;

  //////////////////////////////////////////////////////////////////////////
  // init boundary simulation
  //////////////////////////////////////////////////////////////////////////
  m_isStaticScene = true;
  for (unsigned int i = 0; i < m_scene.boundaryModels.size(); i++)
  {
    if (m_scene.boundaryModels[i]->dynamic)
    {
      m_isStaticScene = false;
      break;
    }
  }
  if (m_isStaticScene)
  {
    LOG_INFO << "Initialize static boundary simulation";
    m_boundarySimulator = new StaticBoundarySimulator(this);
  }
  else
  {
    LOG_INFO << "Initialize dynamic boundary simulation";
    m_boundarySimulator = new PBDBoundarySimulator(this);
  }
}

void SimulatorBase::initSimulation()
{
#ifdef DL_OUTPUT
    // copy scene files in output so that the simulation can be reproduced
  std::string sceneFilePath = FileSystem::normalizePath(m_outputPath + "/scene");
  FileSystem::makeDirs(sceneFilePath);
  FileSystem::copyFile(m_sceneFile, sceneFilePath + "/" + FileSystem::getFileNameWithExt(m_sceneFile));

  std::string modelsFilePath = FileSystem::normalizePath(m_outputPath + "/models");
  FileSystem::makeDirs(modelsFilePath);
  for (unsigned int i = 0; i < m_scene.boundaryModels.size(); i++)
  {
    std::string meshFileName = m_scene.boundaryModels[i]->meshFile;
    if (meshFileName != "")
    {
      if (FileSystem::isRelativePath(meshFileName))
        meshFileName = FileSystem::normalizePath(FileSystem::getFilePath(m_sceneFile) + "/" + meshFileName);
      FileSystem::copyFile(meshFileName, modelsFilePath + "/" + FileSystem::getFileNameWithExt(meshFileName));
    }
    std::string mapFileName = m_scene.boundaryModels[i]->mapFile;
    if (mapFileName != "")
    {
      if (FileSystem::isRelativePath(mapFileName))
        mapFileName = FileSystem::normalizePath(FileSystem::getFilePath(m_sceneFile) + "/" + mapFileName);
      FileSystem::copyFile(m_scene.boundaryModels[i]->mapFile, modelsFilePath + "/" + FileSystem::getFileNameWithExt(m_scene.boundaryModels[i]->mapFile));
    }
  }

  for (unsigned int i = 0; i < m_scene.fluidModels.size(); i++)
  {
    std::string samplesFileName = m_scene.fluidModels[i]->samplesFile;
    if (samplesFileName != "")
    {
      if (FileSystem::isRelativePath(samplesFileName))
        samplesFileName = FileSystem::normalizePath(FileSystem::getFilePath(m_sceneFile) + "/" + samplesFileName);
      FileSystem::copyFile(samplesFileName, modelsFilePath + "/" + FileSystem::getFileNameWithExt(samplesFileName));
    }
  }

  std::string progFilePath = FileSystem::normalizePath(m_outputPath + "/program");
  FileSystem::makeDirs(progFilePath);
  FileSystem::copyFile(m_argv[0], progFilePath + "/" + FileSystem::getFileNameWithExt(m_argv[0]));
  #endif

  Simulation *sim = Simulation::getCurrent();
  sim->init(getScene().particleRadius, getScene().sim2D);

  buildModel();

  if (sim->getBoundaryHandlingMethod() == BoundaryHandlingMethods::Akinci2012)
  {
    unsigned int nBoundaryParticles = 0;
    for (unsigned int i = 0; i < sim->numberOfBoundaryModels(); i++)
      nBoundaryParticles += static_cast<BoundaryModel_Akinci2012*>(sim->getBoundaryModel(i))->numberOfParticles();

    LOG_INFO << "Number of boundary particles: " << nBoundaryParticles;
  }

  if (m_useGUI)
    m_gui->init(m_argc, m_argv, m_windowName.c_str());

  m_boundarySimulator->init();

  if (m_useGUI)
  {
    Simulation *sim = Simulation::getCurrent();
    for (unsigned int i = 0; i < sim->numberOfFluidModels(); i++)
    {
      FluidModel *model = sim->getFluidModel(i);
      const std::string &key = model->getId();
      model->setDragMethodChangedCallback([this, model]() { m_gui->initSimulationParameterGUI(); getSceneLoader()->readMaterialParameterObject(model->getId(), (ParameterObject*)model->getDragBase()); });
      model->setSurfaceMethodChangedCallback([this, model]() { m_gui->initSimulationParameterGUI(); getSceneLoader()->readMaterialParameterObject(model->getId(), (ParameterObject*)model->getSurfaceTensionBase()); });
      model->setViscosityMethodChangedCallback([this, model]() { m_gui->initSimulationParameterGUI(); getSceneLoader()->readMaterialParameterObject(model->getId(), (ParameterObject*)model->getViscosityBase()); });
      model->setVorticityMethodChangedCallback([this, model]() { m_gui->initSimulationParameterGUI(); getSceneLoader()->readMaterialParameterObject(model->getId(), (ParameterObject*)model->getVorticityBase()); });
      model->setElasticityMethodChangedCallback([this, model]() { reset(); m_gui->initSimulationParameterGUI(); getSceneLoader()->readMaterialParameterObject(model->getId(), (ParameterObject*)model->getElasticityBase()); });
    }

    m_gui->initSimulationParameterGUI();
    Simulation::getCurrent()->setSimulationMethodChangedCallback([this]() { reset(); m_gui->initSimulationParameterGUI(); getSceneLoader()->readParameterObject("Configuration", Simulation::getCurrent()->getTimeStep()); });
  }
  readParameters();
  setCommandLineParameter();
}

void SimulatorBase::runSimulation()
{
  m_boundarySimulator->initBoundaryData();

  if (getStateFile() != "")
    loadState(getStateFile());

  if (!m_useGUI)
  {
    const Real stopAt = getValue<Real>(SimulatorBase::STOP_AT);
    if (stopAt < 0.0)
    {
      LOG_ERR << "StopAt parameter must be set when starting without GUI.";
      exit(1);
    }

    while (true)
    {
      if (!timeStepNoGUI())
        break;
    }
  }

  if (m_useGUI)
    m_gui->run();
}

void SimulatorBase::cleanup()
{
  for (unsigned int i = 0; i < m_scene.boundaryModels.size(); i++)
    delete m_scene.boundaryModels[i];
  m_scene.boundaryModels.clear();

  for (unsigned int i = 0; i < m_scene.fluidModels.size(); i++)
    delete m_scene.fluidModels[i];
  m_scene.fluidModels.clear();

  for (unsigned int i = 0; i < m_scene.fluidBlocks.size(); i++)
    delete m_scene.fluidBlocks[i];
  m_scene.fluidBlocks.clear();

  for (unsigned int i = 0; i < m_scene.emitters.size(); i++)
    delete m_scene.emitters[i];
  m_scene.emitters.clear();

  for (unsigned int i = 0; i < m_scene.animatedFields.size(); i++)
    delete m_scene.animatedFields[i];
  m_scene.animatedFields.clear();

  if (m_useGUI)
    m_gui->cleanup();

  delete Simulation::getCurrent();
}

void SimulatorBase::readParameters()
{
  m_sceneLoader->readParameterObject("Configuration", this);
  m_sceneLoader->readParameterObject("Configuration", Simulation::getCurrent());
  m_sceneLoader->readParameterObject("Configuration", Simulation::getCurrent()->getTimeStep());

  Simulation *sim = Simulation::getCurrent();
  for (unsigned int i = 0; i < sim->numberOfFluidModels(); i++)
  {
    FluidModel *model = sim->getFluidModel(i);
    const std::string &key = model->getId();
    m_sceneLoader->readMaterialParameterObject(key, model);
    m_sceneLoader->readMaterialParameterObject(key, (ParameterObject*) model->getDragBase());
    m_sceneLoader->readMaterialParameterObject(key, (ParameterObject*) model->getSurfaceTensionBase());
    m_sceneLoader->readMaterialParameterObject(key, (ParameterObject*) model->getViscosityBase());
    m_sceneLoader->readMaterialParameterObject(key, (ParameterObject*) model->getVorticityBase());
    m_sceneLoader->readMaterialParameterObject(key, (ParameterObject*) model->getElasticityBase());

    for (auto material : m_scene.materials)
    {
      if (material->id == key)
      {
        setColorField(i, material->colorField);
        setColorMapType(i, material->colorMapType);
        setRenderMinValue(i, material->minVal);
        setRenderMaxValue(i, material->maxVal);
      }
    }
  }
}

void SimulatorBase::setCommandLineParameter()
{
  Simulation *sim = Simulation::getCurrent();
  if (m_paramTokens.size() != 3)
    return;

  setCommandLineParameter((ParameterObject*)sim);
  setCommandLineParameter((ParameterObject*)sim->getTimeStep());
  
  for (unsigned int i = 0; i < sim->numberOfFluidModels(); i++)
  {
    FluidModel *model = sim->getFluidModel(i);
    const std::string &key = model->getId();
    
    if (m_paramTokens[0] == key)
    {      
      setCommandLineParameter((ParameterObject*)model);
       setCommandLineParameter((ParameterObject*)model->getDragBase());
       setCommandLineParameter((ParameterObject*)model->getSurfaceTensionBase());
      setCommandLineParameter((ParameterObject*)model->getViscosityBase());
       setCommandLineParameter((ParameterObject*)model->getVorticityBase());
       setCommandLineParameter((ParameterObject*)model->getElasticityBase());
    }
  }
}

void SimulatorBase::setCommandLineParameter(GenParam::ParameterObject *paramObj)
{
  if (paramObj == nullptr)
    return;

  const unsigned int numParams = paramObj->numParameters();
  for (unsigned int j = 0; j < numParams; j++)
  {
    ParameterBase *paramBase = paramObj->getParameter(j);
    if (m_paramTokens[1] == paramBase->getName())
    {
      if (paramBase->getType() == RealParameterType)
      {
        const Real val = stof(m_paramTokens[2]);
        static_cast<NumericParameter<Real>*>(paramBase)->setValue(val);
      }
      else if (paramBase->getType() == ParameterBase::UINT32)
      {
        const unsigned int val = stoi(m_paramTokens[2]);
        static_cast<NumericParameter<unsigned int>*>(paramBase)->setValue(val);
      }
      else if (paramBase->getType() == ParameterBase::UINT16)
      {
        const unsigned short val = stoi(m_paramTokens[2]);
        static_cast<NumericParameter<unsigned short>*>(paramBase)->setValue(val);
      }
      else if (paramBase->getType() == ParameterBase::UINT8)
      {
        const unsigned char val = stoi(m_paramTokens[2]);
        static_cast<NumericParameter<unsigned char>*>(paramBase)->setValue(val);
      }
      else if (paramBase->getType() == ParameterBase::INT32)
      {
        const int val = stoi(m_paramTokens[2]);
        static_cast<NumericParameter<int>*>(paramBase)->setValue(val);
      }
      else if (paramBase->getType() == ParameterBase::INT16)
      {
        const short val = stoi(m_paramTokens[2]);
        static_cast<NumericParameter<short>*>(paramBase)->setValue(val);
      }
      else if (paramBase->getType() == ParameterBase::INT8)
      {
        const char val = stoi(m_paramTokens[2]);
        static_cast<NumericParameter<char>*>(paramBase)->setValue(val);
      }
      else if (paramBase->getType() == ParameterBase::ENUM)
      {
        const int val = stoi(m_paramTokens[2]);
        static_cast<EnumParameter*>(paramBase)->setValue(val);
      }
      else if (paramBase->getType() == ParameterBase::BOOL)
      {
        const bool val = stoi(m_paramTokens[2]);
        static_cast<BoolParameter*>(paramBase)->setValue(val);
      }
      else if (paramBase->getType() == RealVectorParameterType)
      {
         if (static_cast<VectorParameter<Real>*>(paramBase)->getDim() == 3)
         {
          vector<string> tokens;
          Utilities::StringTools::tokenize(m_paramTokens[2], tokens, ",");
          if (tokens.size() == 3)
          {
            Vector3r val(stof(tokens[0]), stof(tokens[1]), stof(tokens[2]));
            static_cast<VectorParameter<Real>*>(paramBase)->setValue(val.data());
          }
         }
      }
      else if (paramBase->getType() == ParameterBase::STRING)
      {
        const std::string val = m_paramTokens[2];
        static_cast<StringParameter*>(paramBase)->setValue(val);
      }
    }
  }
}


void SimulatorBase::buildModel()
{
  TimeManager::getCurrent()->setTimeStepSize(m_scene.timeStepSize);

  initFluidData();

  createEmitters();
  createAnimationFields();

  Simulation *sim = Simulation::getCurrent();

  if (sim->getTimeStep())
    sim->getTimeStep()->resize();

  if (!sim->is2DSimulation())
  {
    sim->setValue(Simulation::KERNEL_METHOD, Simulation::ENUM_KERNEL_PRECOMPUTED_CUBIC);
    sim->setValue(Simulation::GRAD_KERNEL_METHOD, Simulation::ENUM_GRADKERNEL_PRECOMPUTED_CUBIC);
  }
  else
  {
    sim->setValue(Simulation::KERNEL_METHOD, Simulation::ENUM_KERNEL_CUBIC_2D);
    sim->setValue(Simulation::GRAD_KERNEL_METHOD, Simulation::ENUM_GRADKERNEL_CUBIC_2D);
  }
}

void SimulatorBase::reset()
{
  Utilities::Timing::printAverageTimes();
  Utilities::Timing::reset();

  Utilities::Counting::printAverageCounts();
  Utilities::Counting::reset();

  Simulation::getCurrent()->reset();
  m_boundarySimulator->reset();
  if (m_gui)
    m_gui->reset();

  if (Simulation::getCurrent()->getValue<int>(Simulation::CFL_METHOD) != Simulation::ENUM_CFL_NONE)
    TimeManager::getCurrent()->setTimeStepSize(m_scene.timeStepSize);
  m_nextFrameTime = 0.0;
  m_nextFrameTimeState = 0.0;
  m_frameCounter = 1;
  m_isFirstFrame = true;
  m_isFirstFrameVTK = true;
#ifdef DL_OUTPUT
  m_nextTiming = 1.0;
#endif
}

void SimulatorBase::timeStep()
{
  const Real stopAt = getValue<Real>(SimulatorBase::STOP_AT);
  if (m_gui && (stopAt > 0.0) && (stopAt < TimeManager::getCurrent()->getTime()))
    m_gui->stop();

  const Real pauseAt = getValue<Real>(SimulatorBase::PAUSE_AT);
  if ((pauseAt > 0.0) && (pauseAt < TimeManager::getCurrent()->getTime()))
    setValue(SimulatorBase::PAUSE, true);

  if (getValue<bool>(SimulatorBase::PAUSE))
    return;

  // Simulation code
  Simulation *sim = Simulation::getCurrent();
  const bool sim2D = sim->is2DSimulation();
  const unsigned int numSteps = getValue<unsigned int>(SimulatorBase::NUM_STEPS_PER_RENDER);
  for (unsigned int i = 0; i < numSteps; i++)
  {
    START_TIMING("SimStep");
    Simulation::getCurrent()->getTimeStep()->step();
    STOP_TIMING_AVG;

    m_boundarySimulator->timeStep();

    step();

    INCREASE_COUNTER("Time step size", TimeManager::getCurrent()->getTimeStepSize());

    // Make sure that particles stay in xy-plane in a 2D simulation
    if (sim2D)
    {
      for (unsigned int i = 0; i < sim->numberOfFluidModels(); i++)
      {
        FluidModel *model = sim->getFluidModel(i);
        for (unsigned int i = 0; i < model->numActiveParticles(); i++)
        {
          model->getPosition(i)[2] = 0.0;
          model->getVelocity(i)[2] = 0.0;
        }
      }
    }

    if (m_timeStepCB)
      m_timeStepCB();
  }
}

bool SimulatorBase::timeStepNoGUI()
{
  const Real stopAt = getValue<Real>(SimulatorBase::STOP_AT);
  if ((stopAt > 0.0) && (stopAt < TimeManager::getCurrent()->getTime()))
    return false;

  // Simulation code
  Simulation *sim = Simulation::getCurrent();
  const bool sim2D = sim->is2DSimulation();

  START_TIMING("SimStep");
  Simulation::getCurrent()->getTimeStep()->step();
  STOP_TIMING_AVG;

  m_boundarySimulator->timeStep();

  step();

  INCREASE_COUNTER("Time step size", TimeManager::getCurrent()->getTimeStepSize());

  // Make sure that particles stay in xy-plane in a 2D simulation
  if (sim2D)
  {
    for (unsigned int i = 0; i < sim->numberOfFluidModels(); i++)
    {
      FluidModel *model = sim->getFluidModel(i);
      for (unsigned int i = 0; i < model->numActiveParticles(); i++)
      {
        model->getPosition(i)[2] = 0.0;
        model->getVelocity(i)[2] = 0.0;
      }
    }
  }
  if (m_timeStepCB)
    m_timeStepCB();
  return true;
}

void SimulatorBase::loadObj(const std::string &filename, TriangleMesh &mesh, const Vector3r &scale)
{
  std::vector<OBJLoader::Vec3f> x;
  std::vector<OBJLoader::Vec3f> normals;
  std::vector<MeshFaceIndices> faces;
  OBJLoader::Vec3f s = { (float)scale[0], (float)scale[1], (float)scale[2] };
  OBJLoader::loadObj(filename, &x, &faces, &normals, nullptr, s);

  mesh.release();
  const unsigned int nPoints = (unsigned int)x.size();
  const unsigned int nFaces = (unsigned int)faces.size();
  mesh.initMesh(nPoints, nFaces);
  for (unsigned int i = 0; i < nPoints; i++)
  {
    mesh.addVertex(Vector3r(x[i][0], x[i][1], x[i][2]));
  }
  for (unsigned int i = 0; i < nFaces; i++)
  {
    // Reduce the indices by one
    int posIndices[3];
    for (int j = 0; j < 3; j++)
    {
      posIndices[j] = faces[i].posIndices[j] - 1;
    }

    mesh.addFace(&posIndices[0]);
  }

  LOG_INFO << "Number of triangles: " << nFaces;
  LOG_INFO << "Number of vertices: " << nPoints;
}


void SimulatorBase::initFluidData()
{
  LOG_INFO << "Initialize fluid particles";

  Simulation *sim = Simulation::getCurrent();

  //////////////////////////////////////////////////////////////////////////
  // Determine number of different fluid IDs
  //////////////////////////////////////////////////////////////////////////
  std::map<std::string, unsigned int> fluidIDs;
  unsigned int index = 0;
  for (unsigned int i = 0; i < m_scene.fluidBlocks.size(); i++)
  {
    if (fluidIDs.find(m_scene.fluidBlocks[i]->id) == fluidIDs.end())
      fluidIDs[m_scene.fluidBlocks[i]->id] = index++;
  }
  for (unsigned int i = 0; i < m_scene.fluidModels.size(); i++)
  {
    if (fluidIDs.find(m_scene.fluidModels[i]->id) == fluidIDs.end())
      fluidIDs[m_scene.fluidModels[i]->id] = index++;
  }
  for (unsigned int i = 0; i < m_scene.emitters.size(); i++)
  {
    if (fluidIDs.find(m_scene.emitters[i]->id) == fluidIDs.end())
      fluidIDs[m_scene.emitters[i]->id] = index++;
  }
  const unsigned int numberOfFluidModels = static_cast<unsigned int>(fluidIDs.size());

  std::vector<std::vector<Vector3r>> fluidParticles;
  std::vector<std::vector<Vector3r>> fluidVelocities;
  fluidParticles.resize(numberOfFluidModels);
  fluidVelocities.resize(numberOfFluidModels);

  createFluidBlocks(fluidIDs, fluidParticles, fluidVelocities);

  std::string base_path = FileSystem::getFilePath(m_sceneFile);

  const bool useCache = getUseParticleCaching();
  std::string scene_path = FileSystem::getFilePath(getSceneFile());
  string cachePath = scene_path + "/Cache";

  unsigned int startIndex = 0;
  unsigned int endIndex = 0;
  for (unsigned int i = 0; i < m_scene.fluidModels.size(); i++)
  {
    const unsigned int fluidIndex = fluidIDs[m_scene.fluidModels[i]->id];

    std::string fileName;
    if (FileSystem::isRelativePath(m_scene.fluidModels[i]->samplesFile))
      fileName = base_path + "/" + m_scene.fluidModels[i]->samplesFile;
    else
      fileName = m_scene.fluidModels[i]->samplesFile;

    string ext = FileSystem::getFileExt(fileName);
    transform(ext.begin(), ext.end(), ext.begin(), ::toupper);
    if (ext == "OBJ")
    {
      // check if mesh file has changed
      std::string md5FileName = FileSystem::normalizePath(cachePath + "/" + FileSystem::getFileNameWithExt(fileName) + "_fluid.md5");
      bool md5 = false;
      if (useCache)
      {
        string md5Str = FileSystem::getFileMD5(fileName);
        if (FileSystem::fileExists(md5FileName))
          md5 = FileSystem::checkMD5(md5Str, md5FileName);
      }

      // Cache sampling
      std::string mesh_base_path = FileSystem::getFilePath(fileName);
      std::string mesh_file_name = FileSystem::getFileName(fileName);

      std::string mode = to_string(m_scene.fluidModels[i]->mode);
      const string scaleStr = real2String(m_scene.fluidModels[i]->scale[0]) + "_" + real2String(m_scene.fluidModels[i]->scale[1]) + "_" + real2String(m_scene.fluidModels[i]->scale[2]);
      const string resStr = to_string(m_scene.fluidModels[i]->resolutionSDF[0]) + "_" + to_string(m_scene.fluidModels[i]->resolutionSDF[1]) + "_" + to_string(m_scene.fluidModels[i]->resolutionSDF[2]);
      const string particleFileName = FileSystem::normalizePath(cachePath + "/" + mesh_file_name + "_fluid_" + real2String(m_scene.particleRadius) + "_m" + mode + "_s" + scaleStr + "_r" + resStr + ".bgeo");

      // check MD5 if cache file is available
      bool foundCacheFile = false;

      if (useCache)
        foundCacheFile = FileSystem::fileExists(particleFileName);

      if (useCache && foundCacheFile && md5)
      {
        PartioReaderWriter::readParticles(particleFileName, m_scene.fluidModels[i]->translation, m_scene.fluidModels[i]->rotation, 1.0, fluidParticles[fluidIndex], fluidVelocities[fluidIndex]);
        LOG_INFO << "Loaded cached fluid sampling: " << particleFileName;
      }

      if (!useCache || !foundCacheFile || !md5)
      {
        LOG_INFO << "Volume sampling of " << fileName;

        TriangleMesh mesh;
        loadObj(fileName, mesh, m_scene.fluidModels[i]->scale);

        bool invert = m_scene.fluidModels[i]->invert;
        int mode = m_scene.fluidModels[i]->mode;
        std::array<unsigned int, 3> resolutionSDF = m_scene.fluidModels[i]->resolutionSDF;

        LOG_INFO << "SDF resolution: " << resolutionSDF[0] << ", " << resolutionSDF[1] << ", " << resolutionSDF[2];
          
        START_TIMING("Volume sampling");
        Utilities::VolumeSampling::sampleMesh(mesh.numVertices(), mesh.getVertices().data(), mesh.numFaces(), mesh.getFaces().data(),
          m_scene.particleRadius, nullptr, resolutionSDF, invert, mode, fluidParticles[fluidIndex]);
        STOP_TIMING_AVG;

        fluidVelocities[fluidIndex].resize(fluidParticles[fluidIndex].size(), m_scene.fluidModels[i]->initialVelocity);

        // Cache sampling
        if (useCache && (FileSystem::makeDir(cachePath) == 0))
        {
          LOG_INFO << "Save particle sampling: " << particleFileName;
          PartioReaderWriter::writeParticles(particleFileName, (unsigned int)fluidParticles[fluidIndex].size(), fluidParticles[fluidIndex].data(), fluidVelocities[fluidIndex].data(), 0.0);
          FileSystem::writeMD5File(fileName, md5FileName);
        }

        // transform particles
        for (unsigned int j = 0; j < (unsigned int)fluidParticles[fluidIndex].size(); j++)
          fluidParticles[fluidIndex][j] = m_scene.fluidModels[i]->rotation * fluidParticles[fluidIndex][j] + m_scene.fluidModels[i]->translation;
      }
    }
    else
    {
      if (!PartioReaderWriter::readParticles(fileName, m_scene.fluidModels[i]->translation, m_scene.fluidModels[i]->rotation, m_scene.fluidModels[i]->scale[0], fluidParticles[fluidIndex], fluidVelocities[fluidIndex]))
        LOG_ERR << "File not found: " << fileName;
    }
    Simulation::getCurrent()->setValue(Simulation::PARTICLE_RADIUS, m_scene.particleRadius);
  }

  unsigned int nParticles = 0;
  for (auto it = fluidIDs.begin(); it != fluidIDs.end(); it++)
  {
    const unsigned int index = it->second;

    unsigned int maxEmitterParticles = 10000;
    for (auto material : m_scene.materials)
    {
      if (material->id == it->first)
        maxEmitterParticles = material->maxEmitterParticles;
    }
    sim->addFluidModel(it->first, (unsigned int)fluidParticles[index].size(), fluidParticles[index].data(), fluidVelocities[index].data(), maxEmitterParticles);
    nParticles += (unsigned int)fluidParticles[index].size();
  }

  m_colorField.resize(sim->numberOfFluidModels(), "velocity");
  m_colorMapType.resize(sim->numberOfFluidModels(), 0);
  m_renderMinValue.resize(sim->numberOfFluidModels(), 0.0);
  m_renderMaxValue.resize(sim->numberOfFluidModels(), 5.0);

  LOG_INFO << "Number of fluid particles: " << nParticles;
}

void SimulatorBase::createEmitters()
{
  Simulation *sim = Simulation::getCurrent();

  //////////////////////////////////////////////////////////////////////////
  // emitters
  //////////////////////////////////////////////////////////////////////////
  for (unsigned int i = 0; i < m_scene.emitters.size(); i++)
  {
    SceneLoader::EmitterData *ed = m_scene.emitters[i];

    FluidModel *model = nullptr;
    unsigned int j;
    for (j = 0; j < sim->numberOfFluidModels(); j++)
    {
      model = sim->getFluidModel(j);
      if (model->getId() == ed->id)
        break;
    }

    if (j < sim->numberOfFluidModels())
    {
      if (sim->is2DSimulation())
      {
        // in 2D convert all emitters to box emitters
        // and set width to 1
        if (ed->type == 1)
          ed->height = ed->width;
        ed->width = 1;
        ed->type = 0;
      }
      model->getEmitterSystem()->addEmitter(
        ed->width, ed->height,
        ed->x, ed->rotation,
        ed->velocity,
        ed->type);

      // Generate boundary geometry around emitters
      Emitter *emitter = model->getEmitterSystem()->getEmitters().back();
      SceneLoader::BoundaryData *emitterBoundary = new SceneLoader::BoundaryData();
      emitterBoundary->dynamic = false;
      emitterBoundary->isWall = false;
      emitterBoundary->color = { 0.2f, 0.2f, 0.2f, 1.0f };
      emitterBoundary->rotation = ed->rotation;
      const Real supportRadius = sim->getSupportRadius();
      const Vector3r & emitDir = ed->rotation.col(0);
      emitterBoundary->scale = Emitter::getSize(static_cast<Real>(ed->width), static_cast<Real>(ed->height), ed->type);
      const Vector3r pos = ed->x;
      emitterBoundary->translation = pos;
      emitterBoundary->samplesFile = "";
      emitterBoundary->mapInvert = false;
      emitterBoundary->mapResolution = Eigen::Matrix<unsigned int, 3, 1, Eigen::DontAlign>(20, 20, 20);
      emitterBoundary->mapThickness = 0.0;

      if (sim->is2DSimulation())
        emitterBoundary->scale[2] = 2 * supportRadius;

      if (ed->type == 0)
        emitterBoundary->meshFile = FileSystem::normalizePath(getExePath() + "/resources/emitter_boundary/EmitterBox.obj");
      else if (ed->type == 1)
        emitterBoundary->meshFile = FileSystem::normalizePath(getExePath() + "/resources/emitter_boundary/EmitterCylinder.obj");
      m_scene.boundaryModels.push_back(emitterBoundary);
      
      // reuse particles if they are outside of a bounding box
      bool emitterReuseParticles = false;
      Vector3r emitterBoxMin(-1.0, -1.0, -1.0);
      Vector3r emitterBoxMax(1.0, 1.0, 1.0);
      for (auto material : m_scene.materials)
      {
        if (material->id == model->getId())
        {
          emitterReuseParticles = material->emitterReuseParticles;
          emitterBoxMin = material->emitterBoxMin;
          emitterBoxMax = material->emitterBoxMax;
        }
      }

      if (emitterReuseParticles)
        model->getEmitterSystem()->enableReuseParticles(emitterBoxMin, emitterBoxMax);

      emitter->setEmitStartTime(ed->emitStartTime);
      emitter->setEmitEndTime(ed->emitEndTime);
    }
  }
}

void SimulatorBase::createAnimationFields()
{
  Simulation *sim = Simulation::getCurrent();

  //////////////////////////////////////////////////////////////////////////
  // animation fields
  //////////////////////////////////////////////////////////////////////////
  for (unsigned int i = 0; i < m_scene.animatedFields.size(); i++)
  {
    SceneLoader::AnimationFieldData *data = m_scene.animatedFields[i];

    sim->getAnimationFieldSystem()->addAnimationField(
        data->particleFieldName, 
        data->x, data->rotation, data->scale,
        data->expression, 
        data->shapeType);

    AnimationField *field = sim->getAnimationFieldSystem()->getAnimationFields().back();
    field->setStartTime(data->startTime);
    field->setEndTime(data->endTime);
  }
}


void SimulatorBase::createFluidBlocks(std::map<std::string, unsigned int> &fluidIDs, std::vector<std::vector<Vector3r>> &fluidParticles, std::vector<std::vector<Vector3r>> &fluidVelocities)
{
  for (unsigned int i = 0; i < m_scene.fluidBlocks.size(); i++)
  {
    const unsigned int fluidIndex = fluidIDs[m_scene.fluidBlocks[i]->id];
    const Real diam = static_cast<Real>(2.0)*m_scene.particleRadius;

    Real xshift = diam;
    Real yshift = diam;
    const Real eps = static_cast<Real>(1.0e-9);
    if (m_scene.fluidBlocks[i]->mode == 1)
      yshift = sqrt(static_cast<Real>(3.0)) * m_scene.particleRadius + eps;
    else if (m_scene.fluidBlocks[i]->mode == 2)
    {
      xshift = sqrt(static_cast<Real>(6.0)) * diam / static_cast<Real>(3.0) + eps;
      yshift = sqrt(static_cast<Real>(3.0)) * m_scene.particleRadius + eps;
    }

    Vector3r diff = m_scene.fluidBlocks[i]->box.m_maxX - m_scene.fluidBlocks[i]->box.m_minX;
    if (m_scene.fluidBlocks[i]->mode == 1)
    {
      diff[0] -= diam;
      diff[2] -= diam;
    }
    else if (m_scene.fluidBlocks[i]->mode == 2)
    {
      diff[0] -= xshift;
      diff[2] -= diam;
    }

    const int stepsX = (int)round(diff[0] / xshift) - 1;
    const int stepsY = (int)round(diff[1] / yshift) - 1;
    int stepsZ = (int)round(diff[2] / diam) - 1;

    Vector3r start = m_scene.fluidBlocks[i]->box.m_minX + static_cast<Real>(2.0)*m_scene.particleRadius*Vector3r::Ones();
    fluidParticles[fluidIndex].reserve(fluidParticles[fluidIndex].size() + stepsX*stepsY*stepsZ);
    fluidVelocities[fluidIndex].resize(fluidVelocities[fluidIndex].size() + stepsX*stepsY*stepsZ, m_scene.fluidBlocks[i]->initialVelocity);

    if (Simulation::getCurrent()->is2DSimulation())
    {
      stepsZ = 1;
      start[2] = 0.0;
    }

    for (int j = 0; j < stepsX; j++)
    {
      for (int k = 0; k < stepsY; k++)
      {
        for (int l = 0; l < stepsZ; l++)
        {
          Vector3r currPos = Vector3r(j*xshift, k*yshift, l*diam) + start;
          if (m_scene.fluidBlocks[i]->mode == 1)
          {
            if (k % 2 == 0)
              currPos += Vector3r(0, 0, m_scene.particleRadius);
            else
              currPos += Vector3r(m_scene.particleRadius, 0, 0);
          }
          else if (m_scene.fluidBlocks[i]->mode == 2)
          {
            currPos += Vector3r(0, 0, m_scene.particleRadius);

            Vector3r shift_vec(0, 0, 0);
            if ((j % 2) && !Simulation::getCurrent()->is2DSimulation())
            {
              shift_vec[2] += diam / (static_cast<Real>(2.0) * (k % 2 ? -1 : 1));
            }
            if (k % 2 == 0)
            {
              shift_vec[0] += xshift / static_cast<Real>(2.0);
            }
            currPos += shift_vec;
          }
          fluidParticles[fluidIndex].push_back(currPos);
        }
      }
    }
  }
}

void SimulatorBase::particleInfo(std::vector<std::vector<unsigned int>> &particles)
{
  Simulation *sim = Simulation::getCurrent();
  const int maxWidth = 25;
  for (unsigned int i = 0; i < sim->numberOfFluidModels(); i++)
  {
    FluidModel *model = sim->getFluidModel(i);
    if (particles[i].size() > 0)
    {
      LOG_INFO << "---------------------------------------------------------------------------";
      LOG_INFO << model->getId();
      LOG_INFO << "---------------------------------------------------------------------------";
    }
    for (unsigned int j = 0; j < particles[i].size(); j++)
    {
      unsigned int index = particles[i][j];
       LOG_INFO << std::left << std::setw(maxWidth) << std::setfill(' ') << "Index:" << index;
       for (unsigned int k = 0; k < model->numberOfFields(); k++)
       {
        const FieldDescription &field = model->getField(k);
        if (field.type == Scalar)
           LOG_INFO << std::left << std::setw(maxWidth) << std::setfill(' ') << field.name + ":" << *((Real*) field.getFct(index));
        else if (field.type == UInt)
          LOG_INFO << std::left << std::setw(maxWidth) << std::setfill(' ') << field.name + ":" << *((unsigned int*)field.getFct(index));
        else if (field.type == Vector3)
        {
          Eigen::Map<Vector3r> vec((Real*) field.getFct(index));
          LOG_INFO << std::left << std::setw(maxWidth) << std::setfill(' ') << field.name + ":" << vec.transpose();
        }
        else if (field.type == Vector6)
        {
          Eigen::Map<Vector6r> vec((Real*)field.getFct(index));
          LOG_INFO << std::left << std::setw(maxWidth) << std::setfill(' ') << field.name + ":" << vec.transpose();
        }
        else if (field.type == Matrix3)
        {
          Eigen::Map<Matrix3r> mat((Real*)field.getFct(index));
          LOG_INFO << std::left << std::setw(maxWidth) << std::setfill(' ') << field.name + ":" << mat.row(0);
          LOG_INFO << std::left << std::setw(maxWidth) << std::setfill(' ') << " " << mat.row(1);
          LOG_INFO << std::left << std::setw(maxWidth) << std::setfill(' ') << " " << mat.row(2);
        }
        else if (field.type == Matrix6)
        {
          Eigen::Map<Matrix6r> mat((Real*) field.getFct(index));
          LOG_INFO << std::left << std::setw(maxWidth) << std::setfill(' ') << field.name + ":" << mat.row(0);
          for (unsigned int k = 1; k < 6; k++)
            LOG_INFO << std::left << std::setw(maxWidth) << std::setfill(' ') << " " << mat.row(k);
        }
       }
      LOG_INFO << "---------------------------------------------------------------------------\n";
    }
  }
}

void SimulatorBase::rigidBodyExport()
{
  std::string exportPath = FileSystem::normalizePath(m_outputPath + "/rigid_bodies");
  std::string exportPathVTK = FileSystem::normalizePath(m_outputPath + "/vtk");
  if (m_enableRigidBodyExport)
  {
    FileSystem::makeDirs(exportPath);
    writeRigidBodiesBIN(exportPath);
  }
  if (m_enableRigidBodyVTKExport)
  {
    FileSystem::makeDirs(exportPathVTK);
    writeRigidBodiesVTK(exportPathVTK);
  }
}

void SimulatorBase::particleExport()
{  
  std::string partioExportPath = FileSystem::normalizePath(m_outputPath + "/partio");
  std::string vtkExportPath = FileSystem::normalizePath(m_outputPath + "/vtk");
  if (m_enablePartioExport)
    FileSystem::makeDirs(partioExportPath);
  if (m_enableVTKExport)
    FileSystem::makeDirs(vtkExportPath);

  Simulation *sim = Simulation::getCurrent();
  for (unsigned int i = 0; i < sim->numberOfFluidModels(); i++)
  {
    FluidModel *model = sim->getFluidModel(i);
    std::string fileName = "ParticleData";
    fileName = fileName + "_" + model->getId() + "_" + std::to_string(m_frameCounter);

    if (m_enablePartioExport)
    {
      std::string exportFileName = FileSystem::normalizePath(partioExportPath + "/" + fileName);
      writeParticlesPartio(exportFileName + ".bgeo", model);
    }
    if (m_enableVTKExport)
    {
      std::string exportFileName = FileSystem::normalizePath(vtkExportPath + "/" + fileName);
      writeParticlesVTK(exportFileName + ".vtk", model);
    }
  }
}


void SimulatorBase::writeParticlesPartio(const std::string &fileName, FluidModel *model)
{
  Partio::ParticlesDataMutable& particleData = *Partio::create();
  Partio::ParticleAttribute posAttr = particleData.addAttribute("position", Partio::VECTOR, 3);
  Partio::ParticleAttribute idAttr = particleData.addAttribute("id", Partio::INT, 1);

  // add attributes
  std::vector<std::string> attributes;
  StringTools::tokenize(m_particleAttributes, attributes, ";");

   std::map<unsigned int, int> attrMap;
   std::map<unsigned int, Partio::ParticleAttribute> partioAttrMap;
   for (unsigned int i = 0; i < attributes.size(); i++)
   {
     // position is exported anyway
    if (attributes[i] == "position")
    {
      attrMap[i] = -1;
      continue;
    }
 
     bool found = false;
     for (unsigned int j = 0; j < model->numberOfFields(); j++)
     {
       const FieldDescription &field = model->getField(j);
       if (field.name == attributes[i])
       {
         found = true;
         if (field.type == Scalar)
         {
           attrMap[i] = j;
           partioAttrMap[i] = particleData.addAttribute(attributes[i].c_str(), Partio::FLOAT, 1);
         }
        else if (field.type == UInt)
        {
          attrMap[i] = j;
          partioAttrMap[i] = particleData.addAttribute(attributes[i].c_str(), Partio::INT, 1);
        }
         else if (field.type == Vector3)
         {
           attrMap[i] = j;
           partioAttrMap[i] = particleData.addAttribute(attributes[i].c_str(), Partio::VECTOR, 3);
         }
         else
         {
           attrMap[i] = -1;
           LOG_WARN << "Only scalar and vector fields are currently supported by the partio exporter.";
         }
         break;
       }
     }
    if (!found)
    {
      attrMap[i] = -1;
      LOG_WARN << "Unknown field cannot be exported in partio file: " << attributes[i];
    }
   }

  const unsigned int numParticles = model->numActiveParticles();

  for (unsigned int i = 0; i < numParticles; i++)
  {
    Partio::ParticleIndex index = particleData.addParticle();
    float* pos = particleData.dataWrite<float>(posAttr, index);
    int* id = particleData.dataWrite<int>(idAttr, index);

    const Vector3r &x = model->getPosition(i);
    pos[0] = (float)x[0];
    pos[1] = (float)x[1];
    pos[2] = (float)x[2];
  
    id[0] = model->getParticleId(i);

     for (unsigned int j = 0; j < attributes.size(); j++)
     {
       const int fieldIndex = attrMap[j];
       if (fieldIndex != -1)
       {
         const FieldDescription &field = model->getField(fieldIndex);
         if (field.type == FieldType::Scalar)
         {
           float* val = particleData.dataWrite<float>(partioAttrMap[j], index);
           *val = (float) *((Real*) field.getFct(i));
         }
        else if (field.type == FieldType::UInt)
        {
          int* val = particleData.dataWrite<int>(partioAttrMap[j], index);
          *val = (int) *((unsigned int*)field.getFct(i));
        }
         else if (field.type == FieldType::Vector3)
         {
           float* val = particleData.dataWrite<float>(partioAttrMap[j], index);
          Eigen::Map<Vector3r> vec((Real*) field.getFct(i));
           val[0] = (float)vec[0];
           val[1] = (float)vec[1];
           val[2] = (float)vec[2];
         }
       }
     }
  }

  Partio::write(fileName.c_str(), particleData, true);
  particleData.release();
}

void SPH::SimulatorBase::writeParticlesVTK(const std::string &fileName, FluidModel *model)
{
  const unsigned int numParticles = model->numActiveParticles();
  if (0 == numParticles)
    return;

#ifdef USE_DOUBLE
  const char * real_str = " double\n";
#else 
  const char * real_str = " float\n";
#endif

  // Open the file
  std::ofstream outfile{ fileName, std::ios::binary };
  if (!outfile.is_open()) 
  {
    LOG_WARN << "Cannot open a file to save VTK particles.";
    return;
  }

  outfile << "# vtk DataFile Version 4.1\n";
  outfile << "SPlisHSPlasH particle data\n"; // title of the data set, (any string up to 256 characters+\n)
  outfile << "BINARY\n";
  outfile << "DATASET UNSTRUCTURED_GRID\n";

  // add attributes
  std::vector<std::string> attributes;
  StringTools::tokenize(m_particleAttributes, attributes, ";");

  //////////////////////////////////////////////////////////////////////////
  // positions and ids exported anyways
  attributes.erase(
    std::remove_if(attributes.begin(), attributes.end(), [](const std::string&s) { return (s == "position" || s == "id"); }),
    attributes.end());

  //////////////////////////////////////////////////////////////////////////
  // export position attribute as POINTS
  {
    std::vector<Vector3r> positions;
    positions.reserve(numParticles);
    for (unsigned int i = 0u; i < numParticles; i++)
      positions.emplace_back(model->getPosition(i));
    // swap endianess
    for (unsigned int i = 0; i < numParticles; i++)
      for (unsigned int c = 0; c < 3; c++)
        swapByteOrder(&positions[i][c]);
    // export to vtk
    outfile << "POINTS " << numParticles << real_str;
    outfile.write(reinterpret_cast<char*>(positions[0].data()), 3 * numParticles * sizeof(Real));
    outfile << "\n";
  }

  //////////////////////////////////////////////////////////////////////////
  // export particle IDs as CELLS
  {
    std::vector<Eigen::Vector2i> cells;
    cells.reserve(numParticles);
    unsigned int nodes_per_cell_swapped = 1;
    swapByteOrder(&nodes_per_cell_swapped);
    for (unsigned int i = 0u; i < numParticles; i++)
    {
      unsigned int idSwapped = model->getParticleId(i);
      swapByteOrder(&idSwapped);
      cells.emplace_back(nodes_per_cell_swapped, idSwapped);
    }

    // particles are cells with one element and the index of the particle
    outfile << "CELLS " << numParticles << " " << 2 * numParticles << "\n";
    outfile.write(reinterpret_cast<char*>(cells[0].data()), 2 * numParticles * sizeof(unsigned int));
    outfile << "\n";
  }
  //////////////////////////////////////////////////////////////////////////
  // export cell types
  {
    // the type of a particle cell is always 1
    std::vector<int> cellTypes;
    int cellTypeSwapped = 1;
    swapByteOrder(&cellTypeSwapped);
    cellTypes.resize(numParticles, cellTypeSwapped);
    outfile << "CELL_TYPES " << numParticles << "\n";
    outfile.write(reinterpret_cast<char*>(cellTypes.data()), numParticles * sizeof(int));
    outfile << "\n";
  }

  //////////////////////////////////////////////////////////////////////////
  // write additional attributes as per-particle data
  {
    outfile << "POINT_DATA " << numParticles << "\n";
    // write IDs
    outfile << "SCALARS id unsigned_int 1\n";
    outfile << "LOOKUP_TABLE id_table\n";
    // copy data
    std::vector<unsigned int> attrData;
    attrData.reserve(numParticles);
    for (unsigned int i = 0u; i < numParticles; i++)
      attrData.emplace_back(model->getParticleId(i));
    // swap endianess
    for (unsigned int i = 0; i < numParticles; i++)
      swapByteOrder(&attrData[i]);
    // export to vtk
    outfile.write(reinterpret_cast<char*>(attrData.data()), numParticles * sizeof(unsigned int));
    outfile << "\n";
  }

  //////////////////////////////////////////////////////////////////////////
  // per point fields (all attributes except for positions)
  const auto numFields = attributes.size();
  outfile << "FIELD FieldData " << std::to_string(numFields) << "\n";

  // iterate over attributes
  for (const std::string & a : attributes)
  {
    const FieldDescription & field = model->getField(a);

    std::string attrNameVTK;
    std::regex_replace(std::back_inserter(attrNameVTK), a.begin(), a.end(), std::regex("\\s+"), "_");

    if (field.type == FieldType::Scalar)
    {
      // write header information
      outfile << attrNameVTK << " 1 " << numParticles << real_str;

      // copy data
      std::vector<Real> attrData;
      attrData.reserve(numParticles);
      for (unsigned int i = 0u; i < numParticles; i++)
        attrData.emplace_back(*((Real*) field.getFct(i)));
      // swap endianess
      for (unsigned int i = 0; i < numParticles; i++)
        swapByteOrder(&attrData[i]);
      // export to vtk
      outfile.write(reinterpret_cast<char*>(attrData.data()), numParticles * sizeof(Real));
    }
    else if (field.type == FieldType::Vector3)
    {
      // write header information
      outfile << attrNameVTK << " 3 " << numParticles << real_str;

      // copy from partio data
      std::vector<Vector3r> attrData;
      attrData.reserve(numParticles);
      for (unsigned int i = 0u; i < numParticles; i++)
        attrData.emplace_back((Real*) field.getFct(i));
      // swap endianess
      for (unsigned int i = 0; i < numParticles; i++)
        for (unsigned int c = 0; c < 3; c++)
          swapByteOrder(&attrData[i][c]);
      // export to vtk
      outfile.write(reinterpret_cast<char*>(attrData[0].data()), 3 * numParticles * sizeof(Real));
    }
    // TODO support other field types
    else
    {
      LOG_WARN << "Skipping attribute " << a << ", because it is of unsupported type\n";
      continue;
    }
    // end of block
    outfile << "\n";
  }
  outfile.close();
}

void SimulatorBase::writeRigidBodiesBIN(const std::string &exportPath)
{
  std::string fileName = "rb_data_";
  fileName = fileName + std::to_string(m_frameCounter) + ".bin";
  std::string exportFileName = FileSystem::normalizePath(exportPath + "/" + fileName);

  Simulation *sim = Simulation::getCurrent();
  const unsigned int nBoundaryModels = sim->numberOfBoundaryModels();

  std::string scene_path = FileSystem::getFilePath(getSceneFile());

  // check if we have a static model
  bool isStatic = true;
  for (unsigned int i = 0; i < sim->numberOfBoundaryModels(); i++)
  {
    BoundaryModel *bm = sim->getBoundaryModel(i);
    if (bm->getRigidBodyObject()->isDynamic())
    {
      isStatic = false;
      break;
    }
  }

  BinaryFileWriter binWriter;
  if (m_isFirstFrame || !isStatic)
    binWriter.openFile(exportFileName.c_str());

  if (m_isFirstFrame)
  {
    binWriter.write(nBoundaryModels);

    for (unsigned int i = 0; i < m_scene.boundaryModels.size(); i++)
    {
      std::string meshFileName = m_scene.boundaryModels[i]->meshFile;
      if (FileSystem::isRelativePath(meshFileName))
        meshFileName = FileSystem::normalizePath(scene_path + "/" + meshFileName);

      const string fileName = Utilities::FileSystem::getFileNameWithExt(meshFileName);
      binWriter.write(fileName);
      Eigen::Vector3f s = m_scene.boundaryModels[i]->scale.template cast<float>();
      binWriter.writeMatrix(s);
      std::string targetFilePath = exportPath + "/" + fileName;
      if (!Utilities::FileSystem::fileExists(targetFilePath))
      {
        Utilities::FileSystem::copyFile(meshFileName, targetFilePath);
      }
      binWriter.write((char)m_scene.boundaryModels[i]->isWall);
      binWriter.writeMatrix(m_scene.boundaryModels[i]->color);
    }
  }

  if (m_isFirstFrame || !isStatic)
  {
    for (unsigned int i = 0; i < sim->numberOfBoundaryModels(); i++)
    {
      BoundaryModel *bm = sim->getBoundaryModel(i);
      const Vector3r &x = bm->getRigidBodyObject()->getWorldSpacePosition();
      const Eigen::Vector3f x_f = x.template cast<float>();
      binWriter.writeMatrix(x_f);

      const Matrix3r &R = bm->getRigidBodyObject()->getWorldSpaceRotation();
      const Eigen::Matrix3f R_f = R.template cast<float>();
      //const Eigen::Matrix3f RT = R.transpose().template cast<float>();
      binWriter.writeMatrix(R_f);
    }
    binWriter.closeFile();
  }

  m_isFirstFrame = false;
}

void SimulatorBase::writeRigidBodiesVTK(const std::string &exportPath)
{
  Simulation *sim = Simulation::getCurrent();
  const unsigned int nBoundaryModels = sim->numberOfBoundaryModels();

  // check if we have a static model
  bool isStatic = true;
  for (unsigned int i = 0; i < sim->numberOfBoundaryModels(); i++)
  {
    BoundaryModel *bm = sim->getBoundaryModel(i);
    if (bm->getRigidBodyObject()->isDynamic())
    {
      isStatic = false;
      break;
    }
  }

#ifdef USE_DOUBLE
  const char * real_str = " double\n";
#else 
  const char * real_str = " float\n";
#endif

  if (m_isFirstFrameVTK || !isStatic)
  {
    for (unsigned int i = 0; i < sim->numberOfBoundaryModels(); i++)
    {
      std::string fileName = "rb_data_";
      fileName = fileName + std::to_string(i) + "_" + std::to_string(m_frameCounter) + ".vtk";
      std::string exportFileName = FileSystem::normalizePath(exportPath + "/" + fileName);

      // Open the file
      std::ofstream outfile(exportFileName, std::ios::binary);
      if (!outfile)
      {
        LOG_WARN << "Cannot open a file to save VTK mesh.";
        return;
      }

      // Header
      outfile << "# vtk DataFile Version 4.2\n";
      outfile << "SPlisHSPlasH mesh data\n";
      outfile << "BINARY\n";
      outfile << "DATASET UNSTRUCTURED_GRID\n";

      BoundaryModel *bm = sim->getBoundaryModel(i);
      const std::vector<Vector3r> &vertices = bm->getRigidBodyObject()->getVertices();
      const std::vector<unsigned int> &faces = bm->getRigidBodyObject()->getFaces();
      int n_vertices = (int)vertices.size();
      int n_triangles = (int)faces.size() / 3;

      // Vertices
      {
        std::vector<Vector3r> positions;
        positions.reserve(n_vertices);
        for (int j = 0u; j < n_vertices; j++)
        {
          Vector3r x = vertices[j];
          swapByteOrder(&x[0]);
          swapByteOrder(&x[1]);
          swapByteOrder(&x[2]);
          positions.emplace_back(x);
        }
        // export to vtk
        outfile << "POINTS " << n_vertices << real_str;
        outfile.write(reinterpret_cast<char*>(positions[0].data()), 3 * n_vertices * sizeof(Real));
        outfile << "\n";
      }

      // Connectivity
      {
        std::vector<int> connectivity_to_write;
        connectivity_to_write.reserve(4 * n_triangles);
        for (int tri_i = 0; tri_i < n_triangles; tri_i++)
        {
          int val = 3;
          swapByteOrder(&val);
          connectivity_to_write.push_back(val);
          val = faces[3 * tri_i + 0];
          swapByteOrder(&val);
          connectivity_to_write.push_back(val);
          val = faces[3 * tri_i + 1];
          swapByteOrder(&val);
          connectivity_to_write.push_back(val);
          val = faces[3 * tri_i + 2];
          swapByteOrder(&val);
          connectivity_to_write.push_back(val);
        }
        // export to vtk
        outfile << "CELLS " << n_triangles << " " << 4 * n_triangles << "\n";
        outfile.write(reinterpret_cast<char*>(&connectivity_to_write[0]), connectivity_to_write.size() * sizeof(int));
        outfile << "\n";
      }

      // Cell types
      {
        outfile << "CELL_TYPES " << n_triangles << "\n";
        int cell_type_swapped = 5;
        swapByteOrder(&cell_type_swapped);
        std::vector<int> cell_type_arr(n_triangles, cell_type_swapped);
        outfile.write(reinterpret_cast<char*>(&cell_type_arr[0]), cell_type_arr.size() * sizeof(int));
        outfile << "\n";
      }
      outfile.close();
    }
  }

  m_isFirstFrameVTK = false;
}

void SimulatorBase::step()
{
  if (TimeManager::getCurrent()->getTime() >= m_nextFrameTime)
  {
    m_nextFrameTime += static_cast<Real>(1.0) / m_framesPerSecond;
    if (m_enablePartioExport || m_enableVTKExport)
      particleExport();
    if (m_enableRigidBodyExport || m_enableRigidBodyVTKExport)
      rigidBodyExport();
    m_frameCounter++;
  }
  if (TimeManager::getCurrent()->getTime() >= m_nextFrameTimeState)
  {
    m_nextFrameTimeState += static_cast<Real>(1.0) / m_framesPerSecondState;
    if (m_enableStateExport)
      saveState();
  }
#ifdef DL_OUTPUT
  if (TimeManager::getCurrent()->getTime() >= m_nextTiming)
  {
    LOG_INFO << "---------------------------------------------------------------------------";
    LOG_INFO << "Time: " << TimeManager::getCurrent()->getTime();
    Timing::printAverageTimes();
    Timing::printTimeSums();
    Counting::printAverageCounts();
    Counting::printCounterSums();
    m_nextTiming += 1.0;
  }
#endif
}


void SimulatorBase::updateBoundaryParticles(const bool forceUpdate = false)
{
  Simulation *sim = Simulation::getCurrent();
  SceneLoader::Scene &scene = getScene();
  const unsigned int nObjects = sim->numberOfBoundaryModels();
  for (unsigned int i = 0; i < nObjects; i++)
  {
    BoundaryModel_Akinci2012 *bm = static_cast<BoundaryModel_Akinci2012*>(sim->getBoundaryModel(i));
    RigidBodyObject *rbo = bm->getRigidBodyObject();
    if (rbo->isDynamic() || forceUpdate)
    {
      #pragma omp parallel default(shared)
      {
        #pragma omp for schedule(static)  
        for (int j = 0; j < (int)bm->numberOfParticles(); j++)
        {
          bm->getPosition(j) = rbo->getRotation() * bm->getPosition0(j) + rbo->getPosition();
          if (rbo->isDynamic())
            bm->getVelocity(j) = rbo->getAngularVelocity().cross(bm->getPosition(j) - rbo->getPosition()) + rbo->getVelocity();
          else
            bm->getVelocity(j).setZero();
        }
      }
      #ifdef GPU_NEIGHBORHOOD_SEARCH
      // copy the particle data to the GPU
      if (forceUpdate)
        sim->getNeighborhoodSearch()->update_point_sets();
      #endif 
    }
  }
}

void SPH::SimulatorBase::updateDMVelocity()
{
  Simulation *sim = Simulation::getCurrent();
  SceneLoader::Scene &scene = getScene();
  const unsigned int nObjects = sim->numberOfBoundaryModels();
  for (unsigned int i = 0; i < nObjects; i++)
  {
    BoundaryModel_Koschier2017 *bm = static_cast<BoundaryModel_Koschier2017*>(sim->getBoundaryModel(i));
    RigidBodyObject *rbo = bm->getRigidBodyObject();
    if (rbo->isDynamic())
    {
      const Real maxDist = bm->getMaxDist();
      const Vector3r x(maxDist, 0.0, 0.0);
      const Vector3r vel = rbo->getAngularVelocity().cross(x) + rbo->getVelocity();
      bm->setMaxVel(vel.norm());
    }
  }
}

void SPH::SimulatorBase::updateVMVelocity()
{
  Simulation *sim = Simulation::getCurrent();
  SceneLoader::Scene &scene = getScene();
  const unsigned int nObjects = sim->numberOfBoundaryModels();
  for (unsigned int i = 0; i < nObjects; i++)
  {
    BoundaryModel_Bender2019 *bm = static_cast<BoundaryModel_Bender2019*>(sim->getBoundaryModel(i));
    RigidBodyObject *rbo = bm->getRigidBodyObject();
    if (rbo->isDynamic())
    {
      const Real maxDist = bm->getMaxDist();
      const Vector3r x(maxDist, 0.0, 0.0);
      const Vector3r vel = rbo->getAngularVelocity().cross(x) + rbo->getVelocity();
      bm->setMaxVel(vel.norm());
    }
  }
}


std::string SimulatorBase::real2String(const Real r)
{
  string str = to_string(r);
  str.erase(str.find_last_not_of('0') + 1, std::string::npos);
  str.erase(str.find_last_not_of('.') + 1, std::string::npos);
  return str;
}

void SPH::SimulatorBase::saveState()
{
  std::string stateFilePath = FileSystem::normalizePath(m_outputPath + "/state");
  FileSystem::makeDirs(stateFilePath);

  string md5Str = FileSystem::getFileMD5(m_sceneFile);

  Simulation *sim = Simulation::getCurrent();

  const Real time = TimeManager::getCurrent()->getTime();
  const std::string timeStr = real2String(time);

  // Save additional data
  BinaryFileWriter binWriter;
  std::string exportFileName = FileSystem::normalizePath(stateFilePath + "/state_" + timeStr);
  binWriter.openFile(exportFileName + ".bin");
  binWriter.write(md5Str);

  binWriter.write(m_nextFrameTime);
  binWriter.write(m_nextFrameTimeState);
  binWriter.write(m_frameCounter);
  binWriter.write(m_isFirstFrame);
  
  writeParameterState(binWriter);
  TimeManager::getCurrent()->saveState(binWriter);
  Simulation::getCurrent()->saveState(binWriter);

  // fluid models
  for (unsigned int i = 0; i < sim->numberOfFluidModels(); i++)
  {
    FluidModel *model = sim->getFluidModel(i);
    std::string fileName = "particle";
    fileName = fileName + "_" + model->getId(); // +"_" + std::to_string(m_frameCounter);

    // Save particle data
    std::string expFileName = FileSystem::normalizePath(exportFileName + "_" + fileName);
    writeFluidParticlesState(expFileName + ".bgeo", model);
  }

  // boundary models
  if (m_firstState)
  {
    for (unsigned int i = 0; i < sim->numberOfBoundaryModels(); i++)
    {
      BoundaryModel *model = sim->getBoundaryModel(i);
      std::string fileName = "state_boundary";
      fileName = fileName + "_" + to_string(i); // +"_" + std::to_string(m_frameCounter);

      // Save particle data
      std::string expFileName = FileSystem::normalizePath(stateFilePath + "/" + fileName);
      writeBoundaryState(expFileName + ".bgeo", model);
    }
    m_firstState = false;
  }

  // dynamic bodies
  for (unsigned int i = 0; i < sim->numberOfBoundaryModels(); i++)
  {
    BoundaryModel *bm = sim->getBoundaryModel(i);
    if (bm->getRigidBodyObject()->isDynamic())
    {
      binWriter.writeMatrix(bm->getRigidBodyObject()->getPosition());
      binWriter.writeMatrix(bm->getRigidBodyObject()->getRotation());
      binWriter.writeMatrix(bm->getRigidBodyObject()->getVelocity());
      binWriter.writeMatrix(bm->getRigidBodyObject()->getAngularVelocity());
    }
  }
  binWriter.closeFile();

  LOG_INFO << "Saved state: " << exportFileName + ".bin";
}

#ifdef WIN32
void SPH::SimulatorBase::loadStateDialog()
{
  const std::string stateFilePath = FileSystem::normalizePath(m_outputPath + "/state");
  const std::string stateFileName = FileSystem::fileDialog(0, stateFilePath, "*.bin");
  if (stateFileName == "")
    return;
  loadState(stateFileName);
}
#endif 

void SPH::SimulatorBase::loadState(const std::string &stateFile)
{
  Simulation *sim = Simulation::getCurrent();
  string md5Str = FileSystem::getFileMD5(m_sceneFile);

  // Load additional data
  BinaryFileReader binReader;
  if (!binReader.openFile(stateFile))
    return;

  // check if scene file has changed
  std::string md5StrState;
  binReader.read(md5StrState);

  binReader.read(m_nextFrameTime);
  binReader.read(m_nextFrameTimeState);
  binReader.read(m_frameCounter);
  binReader.read(m_isFirstFrame);

  readParameterState(binReader);
  if (md5Str != md5StrState)
    LOG_WARN << "State was stored for another scene file.";
  TimeManager::getCurrent()->loadState(binReader);
  Simulation::getCurrent()->loadState(binReader);

  const std::string importFilePath = FileSystem::getFilePath(stateFile);
  const std::string importFileName = FileSystem::getFileName(stateFile);

  // fluid models
  for (unsigned int i = 0; i < sim->numberOfFluidModels(); i++)
  {
    FluidModel *model = sim->getFluidModel(i);
    std::string fileName = "particle";
    fileName = fileName + "_" + model->getId(); // +"_" + std::to_string(m_frameCounter);

    std::string impFileName = FileSystem::normalizePath(importFilePath + "/" + importFileName + "_" + fileName);
    readFluidParticlesState(impFileName + ".bgeo", model);
  }

  // boundary models
  for (unsigned int i = 0; i < sim->numberOfBoundaryModels(); i++)
  {
    BoundaryModel *model = sim->getBoundaryModel(i);
    std::string fileName = "state_boundary";
    fileName = fileName + "_" + to_string(i); // +"_" + std::to_string(m_frameCounter);

    // Save particle data
    std::string impFileName = FileSystem::normalizePath(importFilePath + "/" + fileName);
    readBoundaryState(impFileName + ".bgeo", model);
  }

  // dynamic bodies
  bool dynamic = false;
  for (unsigned int i = 0; i < sim->numberOfBoundaryModels(); i++)
  {
    BoundaryModel *bm = sim->getBoundaryModel(i);
    if (bm->getRigidBodyObject()->isDynamic())
    {
      Vector3r x;
      binReader.readMatrix(x);
      bm->getRigidBodyObject()->setPosition(x);

      Matrix3r R;
      binReader.readMatrix(R);
      bm->getRigidBodyObject()->setRotation(R);

      Vector3r v;
      binReader.readMatrix(v);
      bm->getRigidBodyObject()->setVelocity(v);

      binReader.readMatrix(v);
      bm->getRigidBodyObject()->setAngularVelocity(v);

      dynamic = true;
    }    
  }
  if (dynamic)
  {
    if (sim->getBoundaryHandlingMethod() == BoundaryHandlingMethods::Akinci2012)
      updateBoundaryParticles(true);
    else if (sim->getBoundaryHandlingMethod() == BoundaryHandlingMethods::Koschier2017)
      updateDMVelocity();
    else if (sim->getBoundaryHandlingMethod() == BoundaryHandlingMethods::Bender2019)
      updateVMVelocity();
  }
  binReader.closeFile();

  sim->performNeighborhoodSearchSort();
}

void SimulatorBase::writeParameterState(BinaryFileWriter &binWriter)
{
  writeParameterObjectState(binWriter, this);
  writeParameterObjectState(binWriter, Simulation::getCurrent());
  writeParameterObjectState(binWriter, Simulation::getCurrent()->getTimeStep());

  Simulation *sim = Simulation::getCurrent();
  for (unsigned int i = 0; i < sim->numberOfFluidModels(); i++)
  {
    FluidModel *model = sim->getFluidModel(i);
    writeParameterObjectState(binWriter, model);
    writeParameterObjectState(binWriter, (ParameterObject*)model->getDragBase());
    writeParameterObjectState(binWriter, (ParameterObject*)model->getSurfaceTensionBase());
    writeParameterObjectState(binWriter, (ParameterObject*)model->getViscosityBase());
    writeParameterObjectState(binWriter, (ParameterObject*)model->getVorticityBase());
    writeParameterObjectState(binWriter, (ParameterObject*)model->getElasticityBase());

    binWriter.write(getColorField(model->getPointSetIndex()));
    binWriter.write(getColorMapType(model->getPointSetIndex()));
    binWriter.write(getRenderMinValue(model->getPointSetIndex()));
    binWriter.write(getRenderMaxValue(model->getPointSetIndex()));
  }
}

void SimulatorBase::writeParameterObjectState(BinaryFileWriter &binWriter, GenParam::ParameterObject *paramObj)
{
  if (paramObj == nullptr)
    return;

  const unsigned int numParams = paramObj->numParameters();

  for (unsigned int i = 0; i < numParams; i++)
  {
    ParameterBase *paramBase = paramObj->getParameter(i);

    if (paramBase->getType() == RealParameterType)
      binWriter.write(static_cast<NumericParameter<Real>*>(paramBase)->getValue());
     else if (paramBase->getType() == ParameterBase::UINT32)
       binWriter.write(static_cast<NumericParameter<unsigned int>*>(paramBase)->getValue());
     else if (paramBase->getType() == ParameterBase::UINT16)
       binWriter.write(static_cast<NumericParameter<unsigned short>*>(paramBase)->getValue());
     else if (paramBase->getType() == ParameterBase::UINT8)
       binWriter.write(static_cast<NumericParameter<unsigned char>*>(paramBase)->getValue());
     else if (paramBase->getType() == ParameterBase::INT32)
       binWriter.write(static_cast<NumericParameter<int>*>(paramBase)->getValue());
     else if (paramBase->getType() == ParameterBase::INT16)
       binWriter.write(static_cast<NumericParameter<short>*>(paramBase)->getValue());
     else if (paramBase->getType() == ParameterBase::INT8)
       binWriter.write(static_cast<NumericParameter<char>*>(paramBase)->getValue());
     else if (paramBase->getType() == ParameterBase::ENUM)
       binWriter.write(static_cast<EnumParameter*>(paramBase)->getValue());
     else if (paramBase->getType() == ParameterBase::BOOL)
       binWriter.write(static_cast<BoolParameter*>(paramBase)->getValue());
     else if (paramBase->getType() == RealVectorParameterType)
     {
       VectorParameter<Real> *vec = static_cast<VectorParameter<Real>*>(paramBase);
       binWriter.writeBuffer((char*)vec->getValue(), vec->getDim() * sizeof(Real));;
     }
     else if (paramBase->getType() == ParameterBase::STRING)
       binWriter.write(static_cast<StringParameter*>(paramBase)->getValue());
  }
}

void SimulatorBase::readParameterState(BinaryFileReader &binReader)
{
  readParameterObjectState(binReader, this);
   readParameterObjectState(binReader, Simulation::getCurrent());
   readParameterObjectState(binReader, Simulation::getCurrent()->getTimeStep());
 
   Simulation *sim = Simulation::getCurrent();
   for (unsigned int i = 0; i < sim->numberOfFluidModels(); i++)
   {
     FluidModel *model = sim->getFluidModel(i);
     readParameterObjectState(binReader, model);
     readParameterObjectState(binReader, (ParameterObject*)model->getDragBase());
     readParameterObjectState(binReader, (ParameterObject*)model->getSurfaceTensionBase());
     readParameterObjectState(binReader, (ParameterObject*)model->getViscosityBase());
     readParameterObjectState(binReader, (ParameterObject*)model->getVorticityBase());
     readParameterObjectState(binReader, (ParameterObject*)model->getElasticityBase());
 
    std::string field;
    binReader.read(field);
    setColorField(model->getPointSetIndex(), field);
    int type;
    binReader.read(type);
    setColorMapType(model->getPointSetIndex(), type);
    Real v;
    binReader.read(v);
    setRenderMinValue(model->getPointSetIndex(), v);
    binReader.read(v);
    setRenderMaxValue(model->getPointSetIndex(), v);
   }
}

void SimulatorBase::readParameterObjectState(BinaryFileReader &binReader, GenParam::ParameterObject *paramObj)
{
  if (paramObj == nullptr)
    return;

  const unsigned int numParams = paramObj->numParameters();

  for (unsigned int i = 0; i < numParams; i++)
  {
    ParameterBase *paramBase = paramObj->getParameter(i);

    if (paramBase->getType() == RealParameterType)
    {
      Real val;
      binReader.read(val);
      static_cast<NumericParameter<Real>*>(paramBase)->setValue(val);
    }
    else if (paramBase->getType() == ParameterBase::UINT32)
    {
      unsigned int val;
      binReader.read(val);
      static_cast<NumericParameter<unsigned int>*>(paramBase)->setValue(val);
    }
    else if (paramBase->getType() == ParameterBase::UINT16)
    {
      unsigned short val;
      binReader.read(val);
      static_cast<NumericParameter<unsigned short>*>(paramBase)->setValue(val);
    }
    else if (paramBase->getType() == ParameterBase::UINT8)
    {
      unsigned char val;
      binReader.read(val);
      static_cast<NumericParameter<unsigned char>*>(paramBase)->setValue(val);
    }
    else if (paramBase->getType() == ParameterBase::INT32)
    {
      int val;
      binReader.read(val);
      static_cast<NumericParameter<int>*>(paramBase)->setValue(val);
    }
    else if (paramBase->getType() == ParameterBase::INT16)
    {
      short val;
      binReader.read(val);
      static_cast<NumericParameter<short>*>(paramBase)->setValue(val);
    }
    else if (paramBase->getType() == ParameterBase::INT8)
    {
      char val;
      binReader.read(val);
      static_cast<NumericParameter<char>*>(paramBase)->setValue(val);
    }
    else if (paramBase->getType() == ParameterBase::ENUM)
    {
      int val;
      binReader.read(val);
      static_cast<EnumParameter*>(paramBase)->setValue(val);
    }
     else if (paramBase->getType() == ParameterBase::BOOL)
    {
      bool val;
      binReader.read(val);
      static_cast<BoolParameter*>(paramBase)->setValue(val);
    }
     else if (paramBase->getType() == RealVectorParameterType)
     {
       VectorParameter<Real> *vec = static_cast<VectorParameter<Real>*>(paramBase);
       binReader.readBuffer((char*)vec->getValue(), vec->getDim() * sizeof(Real));;
     }
     else if (paramBase->getType() == ParameterBase::STRING)
    {
      std::string val;
      binReader.read(val);
      static_cast<StringParameter*>(paramBase)->setValue(val);
    }
  }
}


void SimulatorBase::writeFluidParticlesState(const std::string &fileName, FluidModel *model)
{
  Partio::ParticlesDataMutable& particleData = *Partio::create();

  std::vector<std::pair<unsigned int, Partio::ParticleAttribute>> partioAttr;
  for (unsigned int j = 0; j < model->numberOfFields(); j++)
  {
    const FieldDescription &field = model->getField(j);
    if (field.storeData)
    {
//      LOG_INFO << "Store field: " << field.name;
      if (field.type == Scalar)
      {
        partioAttr.push_back({ j, particleData.addAttribute(field.name.c_str(), Partio::FLOAT, 1) });
      }
      else if (field.type == UInt)
      {
        partioAttr.push_back({ j, particleData.addAttribute(field.name.c_str(), Partio::INT, 1) });
      }
      else if (field.type == Vector3)
      {
        partioAttr.push_back({ j, particleData.addAttribute(field.name.c_str(), Partio::VECTOR, 3) });
      }
      else
      {
        LOG_WARN << "Only scalar and vector fields are currently supported by the partio exporter.";
      }
    }
  }

  const unsigned int numParticles = model->numActiveParticles();

  for (unsigned int i = 0; i < numParticles; i++)
  {
    Partio::ParticleIndex index = particleData.addParticle();

    for (unsigned int j = 0; j < partioAttr.size(); j++)
    {
      const unsigned int fieldIndex = partioAttr[j].first;
      const Partio::ParticleAttribute &attr = partioAttr[j].second;

      const FieldDescription &field = model->getField(fieldIndex);
      if (field.type == FieldType::Scalar)
      {
        float* val = particleData.dataWrite<float>(attr, index);
        *val = (float)* ((Real*) field.getFct(i));
      }
      else if (field.type == FieldType::UInt)
      {
        int* val = particleData.dataWrite<int>(attr, index);
        *val = (int)* ((unsigned int*)field.getFct(i));
      }
      else if (field.type == FieldType::Vector3)
      {
        float* val = particleData.dataWrite<float>(attr, index);
        Eigen::Map<Vector3r> vec((Real*) field.getFct(i));
        val[0] = (float)vec[0];
        val[1] = (float)vec[1];
        val[2] = (float)vec[2];
      }
    }
  }

  Partio::write(fileName.c_str(), particleData, true);
  particleData.release();
}


void SimulatorBase::readFluidParticlesState(const std::string &fileName, FluidModel *model)
{
  if (!FileSystem::fileExists(fileName))
  {
    LOG_WARN << "File " << fileName << " does not exist.";
    return;
  }

  Partio::ParticlesDataMutable* data = Partio::read(fileName.c_str());
  if (!data)
  {
    LOG_WARN << "Partio file " << fileName << " not readable.";
    return;
  }

  std::vector<std::pair<unsigned int, Partio::ParticleAttribute>> partioAttr;
  for (int i = 0; i < data->numAttributes(); i++)
  {
    Partio::ParticleAttribute attr;
    data->attributeInfo(i, attr);
    for (unsigned int j = 0; j < model->numberOfFields(); j++)
    {
      const FieldDescription &field = model->getField(j);
      if (field.name == attr.name)
      {
        //LOG_INFO << "Read field: " << field.name;
        partioAttr.push_back({ j, attr});
      }
    }
  }

  for (unsigned int j = 0; j < partioAttr.size(); j++)
  {
    const unsigned int fieldIndex = partioAttr[j].first;
    const Partio::ParticleAttribute &attr = partioAttr[j].second;
 
    const FieldDescription &field = model->getField(fieldIndex);

    for (int i = 0; i < data->numParticles(); i++)
    {      
      if (field.type == FieldType::Scalar)
      {
        const float *value = data->data<float>(attr, i);
        *((Real*) field.getFct(i)) = value[0];
      }
      else if (field.type == FieldType::UInt)
      {
        const int *value = data->data<int>(attr, i);
        *((unsigned int*)field.getFct(i)) = value[0];
      }
      else if (field.type == FieldType::Vector3)
      {
        const float *value = data->data<float>(attr, i);
        Eigen::Map<Vector3r> vec((Real*) field.getFct(i));
        vec[0] = value[0];
        vec[1] = value[1];
        vec[2] = value[2];
      }
    }
  }
  data->release();
}


void SimulatorBase::writeBoundaryState(const std::string &fileName, BoundaryModel *bm)
{
  Simulation *sim = Simulation::getCurrent();
  if (sim->getBoundaryHandlingMethod() == BoundaryHandlingMethods::Akinci2012)
  {
    BoundaryModel_Akinci2012 *model = static_cast<BoundaryModel_Akinci2012*>(bm);
    Partio::ParticlesDataMutable& particleData = *Partio::create();
    const Partio::ParticleAttribute &attrX0 = particleData.addAttribute("position0", Partio::VECTOR, 3);
    const Partio::ParticleAttribute &attrX = particleData.addAttribute("position", Partio::VECTOR, 3);
    const Partio::ParticleAttribute &attrVel = particleData.addAttribute("velocity", Partio::VECTOR, 3);
    const Partio::ParticleAttribute &attrVol = particleData.addAttribute("volume", Partio::FLOAT, 1);

    const unsigned int numParticles = model->numberOfParticles();

    for (unsigned int i = 0; i < numParticles; i++)
    {
      Partio::ParticleIndex index = particleData.addParticle();

      float* val = particleData.dataWrite<float>(attrX0, index);
      const Vector3r &x0 = model->getPosition0(i);
      val[0] = (float)x0[0];
      val[1] = (float)x0[1];
      val[2] = (float)x0[2];

      val = particleData.dataWrite<float>(attrX, index);
      const Vector3r &x = model->getPosition(i);
      val[0] = (float)x[0];
      val[1] = (float)x[1];
      val[2] = (float)x[2];

      val = particleData.dataWrite<float>(attrVel, index);
      const Vector3r &v = model->getVelocity(i);
      val[0] = (float)v[0];
      val[1] = (float)v[1];
      val[2] = (float)v[2];

      val = particleData.dataWrite<float>(attrVol, index);
      val[0] = (float)model->getVolume(i);
    }

    Partio::write(fileName.c_str(), particleData, true);
    particleData.release();
  }
}


void SimulatorBase::readBoundaryState(const std::string &fileName, BoundaryModel *bm)
{
  Simulation *sim = Simulation::getCurrent();
  if (sim->getBoundaryHandlingMethod() == BoundaryHandlingMethods::Akinci2012)
  {
    if (!FileSystem::fileExists(fileName))
    {
      LOG_WARN << "File " << fileName << " does not exist.";
      return;
    }

    BoundaryModel_Akinci2012 *model = static_cast<BoundaryModel_Akinci2012*>(bm);
    Partio::ParticlesDataMutable* data = Partio::read(fileName.c_str());
    if (!data)
    {
      LOG_WARN << "Partio file " << fileName << " not readable.";
      return;
    }

    unsigned int pos0Index = 0xffffffff;
    unsigned int posIndex = 0xffffffff;
    unsigned int velIndex = 0xffffffff;
    unsigned int volIndex = 0xffffffff;

    for (int i = 0; i < data->numAttributes(); i++)
    {
      Partio::ParticleAttribute attr;
      data->attributeInfo(i, attr);
      if (attr.name == "position0")
        pos0Index = i;
      else if (attr.name == "position")
        posIndex = i;
      else if (attr.name == "velocity")
        velIndex = i;
      else if (attr.name == "volume")
        volIndex = i;
    }

    if ((pos0Index == 0xffffffff) ||
      (posIndex == 0xffffffff) ||
      (velIndex == 0xffffffff) ||
      (volIndex == 0xffffffff))
    {
      LOG_WARN << "File " << fileName << " does not has the correct attributes.";
      return;
    }

    Partio::ParticleAttribute attrX0;
    Partio::ParticleAttribute attrX;
    Partio::ParticleAttribute attrVel;
    Partio::ParticleAttribute attrVol;

    data->attributeInfo(pos0Index, attrX0);
    data->attributeInfo(posIndex, attrX);
    data->attributeInfo(velIndex, attrVel);
    data->attributeInfo(volIndex, attrVol);

    model->resize(data->numParticles());
    for (int i = 0; i < data->numParticles(); i++)
    {
      const float *pos0 = data->data<float>(attrX0, i);
      model->getPosition0(i) = Vector3r(pos0[0], pos0[1], pos0[2]);

      const float *pos = data->data<float>(attrX, i);
      model->getPosition(i) = Vector3r(pos[0], pos[1], pos[2]);

      const float *vel = data->data<float>(attrVel, i);
      model->getVelocity(i) = Vector3r(vel[0], vel[1], vel[2]);

      const float *vol = data->data<float>(attrVol, i);
      model->setVolume(i, vol[0]);
    }

    data->release();

    NeighborhoodSearch *neighborhoodSearch = Simulation::getCurrent()->getNeighborhoodSearch();
    neighborhoodSearch->update_point_sets();
    neighborhoodSearch->resize_point_set(model->getPointSetIndex(), &model->getPosition(0)[0], model->numberOfParticles());
  }
}


void SimulatorBase::initDensityMap(std::vector<Vector3r> &x, std::vector<unsigned int> &faces, const Utilities::SceneLoader::BoundaryData *boundaryData, const bool md5, const bool isDynamic, BoundaryModel_Koschier2017 *boundaryModel)
{
  Simulation *sim = Simulation::getCurrent();
  const Real supportRadius = sim->getSupportRadius();
  std::string scene_path = FileSystem::getFilePath(getSceneFile());
  std::string scene_file_name = FileSystem::getFileName(getSceneFile());
  SceneLoader::Scene &scene = getScene();
  const bool useCache = getUseParticleCaching();
  Discregrid::CubicLagrangeDiscreteGrid *densityMap;

  // if a map file is given, use this one
  if (boundaryData->mapFile != "")
  {
    std::string mapFileName = boundaryData->mapFile;
    if (FileSystem::isRelativePath(mapFileName))
      mapFileName = FileSystem::normalizePath(scene_path + "/" + mapFileName);
    densityMap = new Discregrid::CubicLagrangeDiscreteGrid(mapFileName);
    boundaryModel->setMap(densityMap);
    LOG_INFO << "Loaded density map: " << mapFileName;
    return;
  }

  string cachePath = scene_path + "/Cache";

  // Cache map
  std::string mesh_base_path = FileSystem::getFilePath(boundaryData->meshFile);
  std::string mesh_file_name = FileSystem::getFileName(boundaryData->meshFile);


  Eigen::Matrix<unsigned int, 3, 1> resolutionSDF = boundaryData->mapResolution;
  const string scaleStr = "s" + real2String(boundaryData->scale[0]) + "_" + real2String(boundaryData->scale[1]) + "_" + real2String(boundaryData->scale[2]);
  const string resStr = "r" + to_string(resolutionSDF[0]) + "_" + to_string(resolutionSDF[1]) + "_" + to_string(resolutionSDF[2]);
  const string invertStr = "i" + to_string((int)boundaryData->mapInvert);
  const string thicknessStr = "t" + real2String(boundaryData->mapThickness);
  const string kernelStr = "k" + to_string(sim->getKernel());
  string densityMapFileName = "";
  if (isDynamic)
    densityMapFileName = FileSystem::normalizePath(cachePath + "/" + mesh_file_name + "_db_dm_" + real2String(scene.particleRadius) + "_" + scaleStr + "_" + resStr + "_" + invertStr + "_" + thicknessStr + "_" + kernelStr + ".cdm");
  else 
    densityMapFileName = FileSystem::normalizePath(cachePath + "/" + mesh_file_name + "_sb_dm_" + real2String(scene.particleRadius) + "_" + scaleStr + "_" + resStr + "_" + invertStr + "_" + thicknessStr + "_" + kernelStr + ".cdm");

  // check MD5 if cache file is available
  bool foundCacheFile = false;

  if (useCache)
    foundCacheFile = FileSystem::fileExists(densityMapFileName);

  if (useCache && foundCacheFile && md5)
  {
    densityMap = new Discregrid::CubicLagrangeDiscreteGrid(densityMapFileName);
    boundaryModel->setMap(densityMap);
    LOG_INFO << "Loaded cached density map: " << densityMapFileName;
    return;
  }

  if (!useCache || !foundCacheFile || !md5)
  {
    //////////////////////////////////////////////////////////////////////////
    // Generate distance field of object using Discregrid
    //////////////////////////////////////////////////////////////////////////
#ifdef USE_DOUBLE
    Discregrid::TriangleMesh sdfMesh(&x[0][0], faces.data(), x.size(), faces.size() / 3);
#else
    // if type is float, copy vector to double vector
    std::vector<double> doubleVec;
    doubleVec.resize(3 * x.size());
    for (unsigned int i = 0; i < x.size(); i++)
      for (unsigned int j = 0; j < 3; j++)
        doubleVec[3 * i + j] = x[i][j];
    Discregrid::TriangleMesh sdfMesh(&doubleVec[0], faces.data(), x.size(), faces.size() / 3);
#endif

    Discregrid::MeshDistance md(sdfMesh);
    Eigen::AlignedBox3d domain;
    for (auto const& x_ : x)
    {
      domain.extend(x_.cast<double>());
    }
    const Real tolerance = boundaryData->mapThickness;
    domain.max() += (4.0*supportRadius + tolerance) * Eigen::Vector3d::Ones();
    domain.min() -= (4.0*supportRadius + tolerance) * Eigen::Vector3d::Ones();

    LOG_INFO << "Domain - min: " << domain.min()[0] << ", " << domain.min()[1] << ", " << domain.min()[2];
    LOG_INFO << "Domain - max: " << domain.max()[0] << ", " << domain.max()[1] << ", " << domain.max()[2];

    LOG_INFO << "Set SDF resolution: " << resolutionSDF[0] << ", " << resolutionSDF[1] << ", " << resolutionSDF[2];
    densityMap = new Discregrid::CubicLagrangeDiscreteGrid(domain, std::array<unsigned int, 3>({ resolutionSDF[0], resolutionSDF[1], resolutionSDF[2] }));
    auto func = Discregrid::DiscreteGrid::ContinuousFunction{};

    Real sign = 1.0;
    if (boundaryData->mapInvert)
      sign = -1.0;
    func = [&md, &sign, &tolerance](Eigen::Vector3d const& xi) {return sign * (md.signedDistanceCached(xi) - tolerance); };

    LOG_INFO << "Generate SDF";
    START_TIMING("SDF Construction");
    densityMap->addFunction(func, false);
    STOP_TIMING_AVG

    const bool sim2D = sim->is2DSimulation();

    //////////////////////////////////////////////////////////////////////////
    // Generate density map of object using Discregrid
    //////////////////////////////////////////////////////////////////////////
    if (sim2D)
      SimpleQuadrature::determineSamplePointsInCircle(supportRadius, 30);

    auto int_domain = Eigen::AlignedBox3d(Eigen::Vector3d::Constant(-supportRadius), Eigen::Vector3d::Constant(supportRadius));
    Real factor = 5.0;
    if (sim2D)
      factor = 1.75;
    auto density_func = [&](Eigen::Vector3d const& x)
    {
      auto d = densityMap->interpolate(0u, x);
      if (d > (1.0 + 1.0 / factor) * supportRadius)
      {
        return 0.0;
      }

      auto integrand = [&](Eigen::Vector3d const& xi)
      {
        if (xi.squaredNorm() > supportRadius*supportRadius)
          return 0.0;

        auto dist = densityMap->interpolate(0u, x + xi);

        // Linear function gamma
        if (dist > 1.0 / factor * supportRadius)
          return 0.0;
        return static_cast<double>((1.0 - factor * dist / supportRadius) * sim->W(xi.cast<Real>()));
      };

      double res = 0.0;
      if (sim2D)
        res = 0.8 * SimpleQuadrature::integrate(integrand);
      else
        res = 0.8 * GaussQuadrature::integrate(integrand, int_domain, 50);
      
      return res;
    };

    auto cell_diag = densityMap->cellSize().norm();
    std::cout << "Generate density map..." << std::endl;
    const bool no_reduction = true;
    START_TIMING("Density Map Construction");
    densityMap->addFunction(density_func, false, [&](Eigen::Vector3d const& x_)
    {
      if (no_reduction)
      {
        return true;
      }
      auto x = x_.cwiseMax(densityMap->domain().min()).cwiseMin(densityMap->domain().max());
      auto dist = densityMap->interpolate(0u, x);
      if (dist == std::numeric_limits<double>::max())
      {
        return false;
      }

      return fabs(dist) < 2.5 * supportRadius;
    });
    STOP_TIMING_PRINT;

    // reduction
    if (!no_reduction)
    {
      std::cout << "Reduce discrete fields...";
      densityMap->reduceField(0u, [&](const Eigen::Vector3d &, double v)
      {
        return fabs(v) < 2.5 * supportRadius;
      });
      densityMap->reduceField(1u, [&](const Eigen::Vector3d &, double v)->double
      {
        if (v == std::numeric_limits<double>::max())
          return false;
        return true;
      });
      std::cout << "DONE" << std::endl;
    }

    boundaryModel->setMap(densityMap);

    // Store cache file
    if (useCache && (FileSystem::makeDir(cachePath) == 0))
    {
      LOG_INFO << "Save density map: " << densityMapFileName;
      densityMap->save(densityMapFileName);
    }
  }
}

void SimulatorBase::initVolumeMap(std::vector<Vector3r> &x, std::vector<unsigned int> &faces, const Utilities::SceneLoader::BoundaryData *boundaryData, const bool md5, const bool isDynamic, BoundaryModel_Bender2019 *boundaryModel)
{
  Simulation *sim = Simulation::getCurrent();
  const Real supportRadius = sim->getSupportRadius();
  std::string scene_path = FileSystem::getFilePath(getSceneFile());
  std::string scene_file_name = FileSystem::getFileName(getSceneFile());
  SceneLoader::Scene &scene = getScene();
  const bool useCache = getUseParticleCaching();
  Discregrid::CubicLagrangeDiscreteGrid *volumeMap;

  // if a map file is given, use this one
  if (boundaryData->mapFile != "")
  {
    std::string mapFileName = boundaryData->mapFile;
    if (FileSystem::isRelativePath(mapFileName))
      mapFileName = FileSystem::normalizePath(scene_path + "/" + mapFileName);
    volumeMap = new Discregrid::CubicLagrangeDiscreteGrid(mapFileName);
    boundaryModel->setMap(volumeMap);
    LOG_INFO << "Loaded volume map: " << mapFileName;
    return;
  }

  string cachePath = scene_path + "/Cache";

  // Cache map
  std::string mesh_base_path = FileSystem::getFilePath(boundaryData->meshFile);
  std::string mesh_file_name = FileSystem::getFileName(boundaryData->meshFile);

  Eigen::Matrix<unsigned int, 3, 1> resolutionSDF = boundaryData->mapResolution;
  const string scaleStr = "s" + real2String(boundaryData->scale[0]) + "_" + real2String(boundaryData->scale[1]) + "_" + real2String(boundaryData->scale[2]);
  const string resStr = "r" + to_string(resolutionSDF[0]) + "_" + to_string(resolutionSDF[1]) + "_" + to_string(resolutionSDF[2]);
  const string invertStr = "i" + to_string((int)boundaryData->mapInvert);
  const string thicknessStr = "t" + real2String(boundaryData->mapThickness);
  string volumeMapFileName = "";
  if (isDynamic)
    volumeMapFileName = FileSystem::normalizePath(cachePath + "/" + mesh_file_name + "_db_vm_" + real2String(scene.particleRadius) + "_" + scaleStr + "_" + resStr + "_" + invertStr + "_" + thicknessStr + ".cdm");
  else 
    volumeMapFileName = FileSystem::normalizePath(cachePath + "/" + mesh_file_name + "_sb_vm_" + real2String(scene.particleRadius) + "_" + scaleStr + "_" + resStr + "_" + invertStr + "_" + thicknessStr + ".cdm");

  // check MD5 if cache file is available
  bool foundCacheFile = false;

  if (useCache)
    foundCacheFile = FileSystem::fileExists(volumeMapFileName);

  if (useCache && foundCacheFile && md5)
  {
    volumeMap = new Discregrid::CubicLagrangeDiscreteGrid(volumeMapFileName);
    boundaryModel->setMap(volumeMap);
    LOG_INFO << "Loaded cached volume map: " << volumeMapFileName;
    return;
  }

  if (!useCache || !foundCacheFile || !md5)
  {
    //////////////////////////////////////////////////////////////////////////
    // Generate distance field of object using Discregrid
    //////////////////////////////////////////////////////////////////////////
#ifdef USE_DOUBLE
    Discregrid::TriangleMesh sdfMesh(&x[0][0], faces.data(), x.size(), faces.size() / 3);
#else
    // if type is float, copy vector to double vector
    std::vector<double> doubleVec;
    doubleVec.resize(3 * x.size());
    for (unsigned int i = 0; i < x.size(); i++)
      for (unsigned int j = 0; j < 3; j++)
        doubleVec[3 * i + j] = x[i][j];
    Discregrid::TriangleMesh sdfMesh(&doubleVec[0], faces.data(), x.size(), faces.size() / 3);
#endif

    Discregrid::MeshDistance md(sdfMesh);
    Eigen::AlignedBox3d domain;
    for (auto const& x_ : x)
    {
      domain.extend(x_.cast<double>());
    }
    const Real tolerance = boundaryData->mapThickness;
    domain.max() += (4.0*supportRadius + tolerance) * Eigen::Vector3d::Ones();
    domain.min() -= (4.0*supportRadius + tolerance) * Eigen::Vector3d::Ones();

    LOG_INFO << "Domain - min: " << domain.min()[0] << ", " << domain.min()[1] << ", " << domain.min()[2];
    LOG_INFO << "Domain - max: " << domain.max()[0] << ", " << domain.max()[1] << ", " << domain.max()[2];

    LOG_INFO << "Set SDF resolution: " << resolutionSDF[0] << ", " << resolutionSDF[1] << ", " << resolutionSDF[2];
    volumeMap = new Discregrid::CubicLagrangeDiscreteGrid(domain, std::array<unsigned int, 3>({ resolutionSDF[0], resolutionSDF[1], resolutionSDF[2] }));
    auto func = Discregrid::DiscreteGrid::ContinuousFunction{};

    //volumeMap->setErrorTolerance(0.001);

    Real sign = 1.0;
    if (boundaryData->mapInvert)
      sign = -1.0;
    const Real particleRadius = sim->getParticleRadius();
    // subtract 0.5 * particle radius to prevent penetration of particles and the boundary
    func = [&md, &sign, &tolerance, &particleRadius](Eigen::Vector3d const& xi) {return sign * (md.signedDistanceCached(xi) - tolerance - 0.5*particleRadius); };

    LOG_INFO << "Generate SDF";
    START_TIMING("SDF Construction");
    volumeMap->addFunction(func, false);
    STOP_TIMING_PRINT

    //////////////////////////////////////////////////////////////////////////
    // Generate volume map of object using Discregrid
    //////////////////////////////////////////////////////////////////////////

    Simulation *sim = Simulation::getCurrent();
    const bool sim2D = sim->is2DSimulation();

    if (sim2D)
      SimpleQuadrature::determineSamplePointsInCircle(supportRadius, 30);
    auto int_domain = Eigen::AlignedBox3d(Eigen::Vector3d::Constant(-supportRadius), Eigen::Vector3d::Constant(supportRadius));
    Real factor = 1.0;
    if (sim2D)
      factor = 1.75;
    auto volume_func = [&](Eigen::Vector3d const& x)
    {
      auto dist = volumeMap->interpolate(0u, x);
      if (dist > (1.0 + 1.0 /*/ factor*/) * supportRadius)
      {
        return 0.0;
      }

      auto integrand = [&volumeMap, &x, &supportRadius, &factor, &sim, &sim2D](Eigen::Vector3d const& xi) -> double
      {
        if (xi.squaredNorm() > supportRadius*supportRadius)
          return 0.0;

        auto dist = volumeMap->interpolate(0u, x + xi);

        if (dist <= 0.0)
          return 1.0 - 0.1 * dist / supportRadius;
          if (dist < 1.0 / factor * supportRadius)
            return static_cast<double>(CubicKernel::W(factor * static_cast<Real>(dist)) / CubicKernel::W_zero());
         return 0.0;
      };

      double res = 0.0;
      if (sim2D)
        res = 0.8 * SimpleQuadrature::integrate(integrand);
      else
        res = 0.8 * GaussQuadrature::integrate(integrand, int_domain, 30);

      return res;
    };

    auto cell_diag = volumeMap->cellSize().norm();
    std::cout << "Generate volume map..." << std::endl;
    const bool no_reduction = true;
    START_TIMING("Volume Map Construction");
    volumeMap->addFunction(volume_func, false, [&](Eigen::Vector3d const& x_)
    {
      if (no_reduction)
      {
        return true;
      }
      auto x = x_.cwiseMax(volumeMap->domain().min()).cwiseMin(volumeMap->domain().max());
      auto dist = volumeMap->interpolate(0u, x);
      if (dist == std::numeric_limits<double>::max())
      {
        return false;
      }

      return fabs(dist) < 4.0 * supportRadius;
    });
    STOP_TIMING_PRINT;

    // reduction
    if (!no_reduction)
    {
      std::cout << "Reduce discrete fields...";
      volumeMap->reduceField(0u, [&](const Eigen::Vector3d &, double v)
      {
        return fabs(v) < 4.0 * supportRadius;
      });
      volumeMap->reduceField(1u, [&](const Eigen::Vector3d &, double v)->double
      {
        if (v == std::numeric_limits<double>::max())
          return false;        
        return true;
      });
      std::cout << "DONE" << std::endl;
    }

    boundaryModel->setMap(volumeMap);

    // Store cache file
    if (useCache && (FileSystem::makeDir(cachePath) == 0))
    {
      LOG_INFO << "Save volume map: " << volumeMapFileName;
      volumeMap->save(volumeMapFileName);
    }
  }

  // store maximal distance of a point to center of mass for CFL
  if (boundaryData->dynamic)
  {
    // determine center of mass
    Vector3r com;
    com.setZero();
    for (unsigned int i = 0; i < x.size(); i++)
    {
      com += x[i];
    }
    com /= static_cast<Real>(x.size());

    // determine point with maximal distance to center of mass
    Real maxDist = 0.0;
    for (unsigned int i = 0; i < x.size(); i++)
    {
      const Vector3r diff = x[i] - com;
      const Real dist = diff.norm();
      if (dist > maxDist)
      {
        maxDist = dist;
      }
    }
    boundaryModel->setMaxDist(maxDist);
  }
}
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-11-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 传输过程数值模拟学习笔记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档