Skip to main content
  • Home
  • Development
  • Documentation
  • Donate
  • Operational login
  • Browse the archive

swh logo
SoftwareHeritage
Software
Heritage
Archive
Features
  • Search

  • Downloads

  • Save code now

  • Add forge now

  • Help

https://github.com/chunleili/nonNewtonCode
22 December 2023, 13:46:53 UTC
  • Code
  • Branches (2)
  • Releases (0)
  • Visits
    • Branches
    • Releases
    • HEAD
    • refs/heads/cmake-fix
    • refs/heads/main
    No releases to show
  • a715a39
  • /
  • Tools
  • /
  • FoamGenerator
  • /
  • main.cpp
Raw File Download
Take a new snapshot of a software origin

If the archived software origin currently browsed is not synchronized with its upstream version (for instance when new commits have been issued), you can explicitly request Software Heritage to take a new snapshot of it.

Use the form below to proceed. Once a request has been submitted and accepted, it will be processed as soon as possible. You can then check its processing state by visiting this dedicated page.
swh spinner

Processing "take a new snapshot" request ...

To reference or cite the objects present in the Software Heritage archive, permalinks based on SoftWare Hash IDentifiers (SWHIDs) must be used.
Select below a type of object currently browsed in order to display its associated SWHID and permalink.

  • content
  • directory
  • revision
  • snapshot
origin badgecontent badge Iframe embedding
swh:1:cnt:bc155b82c4379987ec5edb1deab2e57dd3a168f5
origin badgedirectory badge Iframe embedding
swh:1:dir:99f3585d88d7f11119e8739f2b64a8a4f7030e18
origin badgerevision badge
swh:1:rev:645503d647115a39bb73e84b424a72611e535fa2
origin badgesnapshot badge
swh:1:snp:8d20debb81c7f94e8fdd49c807a95451a3476519

This interface enables to generate software citations, provided that the root directory of browsed objects contains a citation.cff or codemeta.json file.
Select below a type of object currently browsed in order to generate citations for them.

  • content
  • directory
  • revision
  • snapshot
Generate software citation in BibTex format (requires biblatex-software package)
Generating citation ...
Generate software citation in BibTex format (requires biblatex-software package)
Generating citation ...
Generate software citation in BibTex format (requires biblatex-software package)
Generating citation ...
Generate software citation in BibTex format (requires biblatex-software package)
Generating citation ...
Tip revision: 645503d647115a39bb73e84b424a72611e535fa2 authored by chunleili on 08 December 2023, 02:41:15 UTC
Add files via upload
Tip revision: 645503d
main.cpp
#include "SPlisHSPlasH/Common.h"
#include <Eigen/Dense>
#include <iostream>
#include "Utilities/Timing.h"
#include "Utilities/FileSystem.h"
#include "extern/cxxopts/cxxopts.hpp"
#include "CompactNSearch.h"
#include "Utilities/StringTools.h"
#include "SPlisHSPlasH/SPHKernels.h"
#include "FoamKernel.h"
#include "Utilities/PartioReaderWriter.h"
#include "extern/partio/src/lib/Partio.h"
#include <array>
#include <future>
#include <random>

// Enable memory leak detection
#ifdef _DEBUG
#ifndef EIGEN_ALIGN
	#define new DEBUG_NEW 
#endif
#endif

INIT_TIMING
INIT_LOGGING

using namespace SPH;
using namespace Eigen;
using namespace std;
using namespace Utilities;

void generateFoamFiles();
void queryValues(Real &vDiffAvgMax, Real & omegaDiffAvgMax, Real &curvatureAvgMax, Real &energyAvgMax);
void determineValues();
void generateFoam();
bool readCurrentFrame();
void writeCurrentFrame();
void writeCurrentFrame_bgeo();
void writeCurrentFrame_vtk();

bool readParticles(const std::string &fileName, std::vector<Vector3r> &positions, std::vector<Vector3r> &velocities, std::vector<Vector3r> &angularVelocities);
void computeNormals();
void computeDensities();
void generateFoamParticles(const unsigned int index, const unsigned int numParticles, const unsigned int numTrappedAir, const unsigned int numWaveCrest, const unsigned int numVorticity, const Real I_ke);
void getOrthogonalVectors(const Vector3r &vec, Vector3r &x, Vector3r &y);
Real clampAndNormalize(const Real val, const Real minVal, const Real maxVal);
void advectFoamParticles();
void removeParticles();

std::vector<Vector3r> x_copy;
std::vector<unsigned char> particleType;
enum ParticleType {Foam, Spray, Bubbles, NUM_PARTICLE_TYPES};
enum GeneratedType {TrappedAir, WaveCrest, Vorticity, NUM_GENERATOR_TYPES};
constexpr unsigned int maxNumTypes = (NUM_PARTICLE_TYPES > NUM_GENERATOR_TYPES) ? NUM_PARTICLE_TYPES : NUM_GENERATOR_TYPES;
unsigned int numTypes = 0;
std::array<unsigned int, maxNumTypes> numParticlesOfType;
std::array<std::string, maxNumTypes> nameExtensions;
std::array<std::string, maxNumTypes> extendedFileNames;
std::array<std::vector<Vector3r>, maxNumTypes> particleCopies;
std::vector<Real> v_diff, curvature, omega_diff, energy;
std::string fileName_copy;
std::future<void> handle;
std::array<std::future<void>, maxNumTypes> handlesType;
const Real density0 = 1000.0;
string input = "";
string output = "";
int output_format = 0; // 0: bgeo, 1: vtk
unsigned int startFrame = 1;
unsigned int endFrame = 0xffffffff;
unsigned int currentFrame = 1;
unsigned int skipframes = 0;
string exePath;
Real particleRadius = 0.025;
Real timeStepSize = 0.02;
Real invDt;
Real supportRadius;
Real mass = 1.0;
Real foam_scale = 1000.0;
Real taMin, taMax, wcMin, wcMax, keMin, keMax;
Real k_ta = 4000;
Real k_wc = 100000;
Real k_buoyancy = 2.0;
Real k_drag = 0.8;
Real lifetimeMin = 2.0;
Real lifetimeMax = 5.0;
Vector3r bbMin(Vector3r::Constant(-std::numeric_limits<Real>::max()));
Vector3r bbMax(Vector3r::Constant(std::numeric_limits<Real>::max()));
enum class BbType { Kill, Lifesteal, Clamp };
BbType bbType = BbType::Lifesteal;
CompactNSearch::NeighborhoodSearch *neighborhoodSearch;
std::vector<Vector3r> x0, v0, normals;
std::vector<Real> densities;
std::vector<Vector3r> fx, fv;
std::vector<Real> flifetime;
bool queryMode = false;
bool automaticMode = true;
bool splitTypes = false;
bool splitGenerators = false;
enum KernelType { CubicSpline = 0, Ihmsen2012, NumKernelTypes };
KernelType kernelType = KernelType::CubicSpline;
Real(*kernelFct)(const Vector3r &) = CubicKernel::W;

std::random_device rand_device;
std::mt19937_64 random_generator(rand_device());
std::uniform_real_distribution<Real> uniform_distr_0_1(Real(0.0), Real(1.0));
Real max_v_diff = 0.0;
Real sum_v_diff = 0.0;
Real sum_max_v_diff = 0.0;
Real max_curvature = 0.0;
Real sum_curvature = 0.0;
Real sum_max_curvature = 0.0;
Real max_energy = 0.0;
Real sum_energy = 0.0;
Real sum_max_energy = 0.0;
unsigned int numberOfFrames;
Real inertia = 2.0;
Real k_vo = 4000;
Real voMin, voMax;
std::vector<Vector3r> omega0;
Real max_omega_diff = 0.0;
Real sum_omega_diff = 0.0;
Real sum_max_omega_diff = 0.0;

std::vector<std::vector<Vector3r>> fxPerThread, fvPerThread;
std::vector<std::vector<Real>> flifetimePerThread;
std::vector<std::vector<unsigned char>> particleTypePerThread;
std::vector<std::array<unsigned int, maxNumTypes>> numParticlesOfTypePerThread;

inline unsigned int numberOfPointSets() 
{
	return static_cast<unsigned int>(neighborhoodSearch->n_point_sets());
}

inline unsigned int numberOfNeighbors(const unsigned int pointSetIndex, const unsigned int index)
{
	return static_cast<unsigned int>(neighborhoodSearch->point_set(0).n_neighbors(pointSetIndex, index));
}

inline unsigned int getNeighbor(const unsigned int pointSetIndex, const unsigned int index, const unsigned int k)
{
	return neighborhoodSearch->point_set(0).neighbor(pointSetIndex, index, k);
}

inline unsigned int numberOfNeighbors(const unsigned int pointSetIndex, const unsigned int index, const unsigned int neighborPointSetIndex)
{
	return static_cast<unsigned int>(neighborhoodSearch->point_set(pointSetIndex).n_neighbors(neighborPointSetIndex, index)); 
} 			

inline unsigned int getNeighbor(const unsigned int pointSetIndex, const unsigned int index, const unsigned int neighborPointSetIndex, const unsigned int k)
{ 
	return neighborhoodSearch->point_set(pointSetIndex).neighbor(neighborPointSetIndex, index, k); 
}


// main 
int main( int argc, char **argv )
{
	REPORT_MEMORY_LEAKS;

	Utilities::logger.addSink(unique_ptr<Utilities::ConsoleSink>(new Utilities::ConsoleSink(Utilities::LogLevel::INFO)));
	exePath = FileSystem::getProgramPath();

	try
	{
		cxxopts::Options options(argv[0], "FoamGen - An implementation of Bender et al. \"Turbulent Micropolar SPH Fluids with Foam\", 2018\n\n"
			"By default the limits and the factors (ta, wc, vo) are determined automatically. The amount of generated foam particles "
			"is solely determined by the parameter foamscale. If the -no_auto flag is set, all parameters can be set manually.\n"
		);

		options.add_options()
			("i,input", "Input file (partio)", cxxopts::value<std::string>())
			("o,output", "Output file (partio or vtk)", cxxopts::value<std::string>())
			("q,query", "Query mode: determines max/avg values ")
			("no-auto", "Disable automatic mode. Limits and factors ta, wc, vo must be set manually.")
			("splittypes", "Output each foam type to a different file")
			("splitgenerators", "Output different foam files depending on which potential generated the foam. Overrides --splittypes.")
			("s,startframe", "Start frame", cxxopts::value<unsigned int>()->default_value("1"))
			("e,endframe", "End frame", cxxopts::value<unsigned int>())
			("r,radius", "Particle radius", cxxopts::value<Real>()->default_value("0.025"))
			("t,timestepsize", "Time step size", cxxopts::value<Real>()->default_value("0.02"))
			("k,kernel", "0: Cubic spline, 1: Ihmsen et al. 2012", cxxopts::value<int>()->default_value("0"))
			("l,limits", "Limits (min/max) for potentials (trapped air, wave crest, vorticity, kinetic energy)", cxxopts::value<std::string>()->default_value("5,20,2,8,5,20,5,50"))
			("lifetime", "Lifetime (min/max)", cxxopts::value<std::string>()->default_value("2.0,5.0"))
			("b,buoyancy", "Buoyancy", cxxopts::value<Real>()->default_value("2.0"))
			("d,drag", "Drag", cxxopts::value<Real>()->default_value("0.8"))
			("ta", "Trapped air factor", cxxopts::value<Real>()->default_value("4000"))
			("wc", "Wave crest factor", cxxopts::value<Real>()->default_value("50000"))
			("vo", "Vorticity factor", cxxopts::value<Real>()->default_value("4000"))
			("bbsize", "minimum and maximum coordinates of and axis aligned bounding-box (minX, minY, minZ, maxX, maxY, maxZ)", cxxopts::value<std::string>())
			("bbtype", "chose how the bounding-box is used [kill | lifesteal | clamp]. Use in combination with --bbsize.", cxxopts::value<std::string>())
			("skipframes", "number of frames to skip when writing foam", cxxopts::value<unsigned int>()->default_value("0"))
			("f,foamscale", "Global multiplier for number of generated foam particles", cxxopts::value<Real>()->default_value("1000"))
			("h,help", "Print help")
			;

		auto result = options.parse(argc, argv);

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

		queryMode = false;
		if (result.count("query"))
		{
			queryMode = true;
		}

		automaticMode = true;
		if (result.count("no-auto"))
		{
			automaticMode = false;
		}

		if (result.count("splitgenerators"))
		{
			splitGenerators = true;
			nameExtensions[GeneratedType::TrappedAir] = std::string("_TrappedAir");
			nameExtensions[GeneratedType::WaveCrest]  = std::string("_WaveCrest") ;
			nameExtensions[GeneratedType::Vorticity]  = std::string("_Vorticity") ;
			numTypes = GeneratedType::NUM_GENERATOR_TYPES;
		}
		if (result.count("splittypes") && !splitGenerators)
		{
			splitTypes = true;
			nameExtensions[ParticleType::Foam] = std::string("_foam");
			nameExtensions[ParticleType::Spray] = std::string("_spray");
			nameExtensions[ParticleType::Bubbles] = std::string("_bubbles");
			numTypes = ParticleType::NUM_PARTICLE_TYPES;
		}

		if (result.count("input"))
		{
			input = result["input"].as<std::string>();
			LOG_INFO << "Input = " << input;
		}
		if (result.count("output"))
		{
			output = result["output"].as<std::string>();
			LOG_INFO << "Output = " << output;

			output_format = 0;
			if (Utilities::StringTools::to_upper(FileSystem::getFileExt(output)) == "VTK")
				output_format = 1;
		}
		if (input == "")
		{
			LOG_ERR << "Input is missing!";
			exit(1);
		}
		if ((output == "") && !queryMode)
		{
			LOG_ERR << "Output is missing!";
			exit(1);
		}

		if (result.count("startframe"))
			startFrame = result["startframe"].as<unsigned int>();

		if (result.count("endframe"))
			endFrame = result["endframe"].as<unsigned int>();

		if (result.count("radius"))
			particleRadius = result["radius"].as<Real>();

		if (result.count("timestepsize"))
			timeStepSize = result["timestepsize"].as<Real>();

		if (result.count("kernel"))
		{
			int kt = result["kernel"].as<int>();
			if ((kt < 0) || (kt >= KernelType::NumKernelTypes))
			{
				kt = 0;
				LOG_WARN << "Wrong kernel type. Using cubic spline kernel.";
			}
			kernelType = static_cast<KernelType>(kt);

			if (kernelType == KernelType::CubicSpline)
				kernelFct = CubicKernel::W;
			else if (kernelType == KernelType::Ihmsen2012)
				kernelFct = FoamKernel::W;
		}

		if (result.count("buoyancy"))
			k_buoyancy = result["buoyancy"].as<Real>();

		if (result.count("drag"))
			k_drag = result["drag"].as<Real>();

		if (result.count("skipframes"))
			skipframes = result["skipframes"].as<unsigned int>();

		foam_scale = result["foamscale"].as<Real>();

		k_ta = result["ta"].as<Real>();
		k_wc = result["wc"].as<Real>();
		k_vo = result["vo"].as<Real>();
		{
			std::vector<string> tokens;
			std::string limitsStr = result["limits"].as<std::string>();
			StringTools::tokenize(limitsStr, tokens, ",");
			if (tokens.size() != 8)
			{
				LOG_ERR << "Wrong number of limit values!";
				exit(1);
			}
			taMin = stof(tokens[0]);
			taMax = stof(tokens[1]);
			wcMin = stof(tokens[2]);
			wcMax = stof(tokens[3]);
			voMin = stof(tokens[4]);
			voMax = stof(tokens[5]);
			keMin = stof(tokens[6]);
			keMax = stof(tokens[7]);
			LOG_INFO << "Limits: " << taMin << ", " << taMax << ", "
				<< wcMin << ", " << wcMax << ", "
				<< voMin << ", " << voMax << ", "
				<< keMin << ", " << keMax;
		}

		{
			std::vector<string> tokens;
			std::string ltStr = result["lifetime"].as<std::string>();
			StringTools::tokenize(ltStr, tokens, ",");
			if (tokens.size() != 2)
			{
				LOG_ERR << "Wrong number of lifetime values!";
				exit(1);
			}
			lifetimeMin = stof(tokens[0]);
			lifetimeMax = stof(tokens[1]);
			LOG_INFO << "Lifetime (min/max): " << lifetimeMin << ", " << lifetimeMax;
		}

		if (result.count("bbsize"))
		{
			std::vector<std::string> tokens;
			std::string limitsStr = result["bbsize"].as<std::string>();
			StringTools::tokenize(limitsStr, tokens, ",");
			if (tokens.size() != 6)
			{
				std::cerr << "Wrong number of bounding box values!\n";
				exit(1);
			}
			bbMin = Vector3r(stof(tokens[0]), stof(tokens[1]), stof(tokens[2]));
			bbMax = Vector3r(stof(tokens[3]), stof(tokens[4]), stof(tokens[5]));
			if ((bbMin.array() > bbMax.array()).any())
			{
				std::cerr << "Bounding-box: A min value is larger than its corresponding max value!\n";
				exit(1);
			}
		}
		if (result.count("bbtype"))
		{
			if (!result.count("bbsize"))
			{
				std::cerr << "No bounding-box given! Use --bbsize to set the size of the bounding box.\n";
				exit(1);
			}
			auto s = result["bbtype"].as<std::string>();
				if (s.compare("kill") == 0)
			{
				bbType = BbType::Kill;
			}
			else if (s.compare("lifesteal") == 0)
			{
				bbType = BbType::Lifesteal;
			}
			else if (s.compare("clamp") == 0)
			{
				bbType = BbType::Clamp;
			}
			else
			{
				std::cerr << "Unknown bounding-box type: " << s << "\n";
				exit(1);
			}
		}
		LOG_INFO << "Particle radius: " << particleRadius;
		LOG_INFO << "Drag: " << k_drag;
		LOG_INFO << "Buoyancy: " << k_buoyancy;
		LOG_INFO << "Trapped air factor: " << k_ta;
		LOG_INFO << "Wave crest factor: " << k_wc;
		LOG_INFO << "Vorticity factor: " << k_vo;
		LOG_INFO << "Bounding box: " << bbMin.transpose() << ", " << bbMax.transpose();
		LOG_INFO << "Bounding box type: " << (int) bbType;
		LOG_INFO << "Foam scale: " << foam_scale;
		LOG_INFO << "Time step size: " << timeStepSize;
		if (skipframes > 0)
			std::cout << "Skipping " << skipframes << " frame(s) between writes." << std::endl;
	}
	catch (const cxxopts::OptionException& e)
	{
		LOG_ERR << "error parsing options: " << e.what();
		exit(1);
	}

#ifdef _OPENMP
	const int maxThreads = omp_get_max_threads();
#else
	const int maxThreads = 1;
#endif

	fxPerThread.resize(maxThreads); 
	fvPerThread.resize(maxThreads);
	flifetimePerThread.resize(maxThreads); 
	particleTypePerThread.resize(maxThreads);
	numParticlesOfTypePerThread.resize(maxThreads);

	// init particle mass
	const Real diam = static_cast<Real>(2.0) * particleRadius;
	mass = static_cast<Real>(0.8) * diam*diam*diam * density0;

	// if in query mode, determine the max values per frame 
	Real vDiffAvgMax, omegaDiffAvgMax, curvatureAvgMax, energyAvgMax;
	if (queryMode)
		queryValues(vDiffAvgMax, omegaDiffAvgMax, curvatureAvgMax, energyAvgMax);
	else if (automaticMode)
	{
		queryValues(vDiffAvgMax, omegaDiffAvgMax, curvatureAvgMax, energyAvgMax);

		taMin = static_cast<Real>(0.1)*vDiffAvgMax;
		taMax = vDiffAvgMax;
		wcMin = static_cast<Real>(0.1)*curvatureAvgMax;
		wcMax = curvatureAvgMax;
		voMin = static_cast<Real>(0.1)*omegaDiffAvgMax;
		voMax = omegaDiffAvgMax;
		keMin = static_cast<Real>(0.1)*energyAvgMax;
		keMax = energyAvgMax;
		LOG_INFO << "Limits: " << taMin << ", " << taMax << ", "
			<< wcMin << ", " << wcMax << ", "
			<< voMin << ", " << voMax << ", "
			<< keMin << ", " << keMax;

		k_ta = 1.0;
		k_wc = 1.0;
		k_vo = 1.0;
		
		generateFoamFiles();
	}
	else
		generateFoamFiles();

	Timing::printAverageTimes();
	Timing::printTimeSums();

	return 0;
}

/** Read the sequence of particle data files and determine for each frame the values 
* that are required to decide how many foam particles should be generated. 
* Finally, sum up the max values and compute the average wrt the number offrames. */
void queryValues(Real &vDiffAvgMax, Real & omegaDiffAvgMax, Real &curvatureAvgMax, Real &energyAvgMax)
{
	currentFrame = startFrame;
	numberOfFrames = 0;

	// Initialize neighborhood search
	supportRadius = static_cast<Real>(4.0) * particleRadius;

	// Initialize kernel
	FoamKernel::setRadius(supportRadius);
	CubicKernel::setRadius(supportRadius);

	invDt = static_cast<Real>(1.0) / timeStepSize;

	bool chk = true;
	bool pointSetAdded = false;
	START_TIMING("total");
	while (chk)
	{
		LOG_INFO << "Reading frame: " << currentFrame;
		chk = readCurrentFrame();
		if (!chk)
			LOG_ERR << "Failed to read frame";

		START_TIMING("iteration");
		if (neighborhoodSearch == nullptr)
		{
			neighborhoodSearch = new CompactNSearch::NeighborhoodSearch(supportRadius, false);
			neighborhoodSearch->set_radius(supportRadius);
			// Fluids 
			neighborhoodSearch->add_point_set(&x0[0][0], x0.size(), true, true);
		}
		else
			neighborhoodSearch->resize_point_set(0, &x0[0][0], x0.size());

		START_TIMING("neighborhoodSearch");
		neighborhoodSearch->find_neighbors();
		STOP_TIMING_AVG

		START_TIMING("determineValues");
		determineValues();
		STOP_TIMING_AVG

		STOP_TIMING_AVG

		numberOfFrames++;

		if (numberOfFrames % 10 == 0)
		{
			LOG_INFO << "v_diff     - max.: " << max_v_diff << ", avg.: " << sum_v_diff / (Real)numberOfFrames;
			LOG_INFO << "Curvature  - max.: " << max_curvature << ", avg.: " << sum_curvature / (Real)numberOfFrames;
			LOG_INFO << "Omega_diff - max.: " << max_omega_diff << ", avg.: " << sum_omega_diff / (Real)numberOfFrames;
			LOG_INFO << "Energy     - max.: " << max_energy << ", avg.: " << sum_energy / (Real)numberOfFrames;
		}

		currentFrame++;
		if (currentFrame > endFrame)
			break;
	}
	delete neighborhoodSearch;
	neighborhoodSearch = nullptr;

	STOP_TIMING_AVG;
	// average maxima
	vDiffAvgMax = sum_max_v_diff / (Real)numberOfFrames;
	omegaDiffAvgMax = sum_max_omega_diff / (Real)numberOfFrames;
	curvatureAvgMax = sum_max_curvature / (Real)numberOfFrames;
	energyAvgMax = sum_max_energy / (Real)numberOfFrames;
	LOG_INFO << "v_diff     - avg. max.: " << vDiffAvgMax;
	LOG_INFO << "Curvature  - avg. max.: " << curvatureAvgMax;
	LOG_INFO << "Omega_diff - avg. max.: " << omegaDiffAvgMax;
	LOG_INFO << "Energy     - avg. max.: " << energyAvgMax;
}


/** Compute values that are required to decide how many foam particles should be generated.\n\n
* 
* See:\n
* - Ihmsen et al., "Unified spray, foam and air bubbles for particle-based fluids", 2012 \n
* - Bender et al., "Turbulent Micropolar SPH Fluids with Foam", 2018 
*/
void determineValues()
{
	// compute the density for each particle
	computeDensities();
	// compute the particle normals which are required for the curvature term
	computeNormals();

	Real sumVDiff = 0.0;
	Real sumCurvature = 0.0;
	Real sumEnergy = 0.0;
	Real maxVDiff = 0.0;
	Real maxCurvature = 0.0;
	Real maxEnergy = 0.0;
	Real sumOmegaDiff = 0.0;
	Real maxOmegaDiff = 0.0;

	v_diff.resize(x0.size());
	omega_diff.resize(x0.size());
	curvature.resize(x0.size());
	energy.resize(x0.size());

	#pragma omp parallel default(shared)
	{
		#pragma omp for schedule(static)  
		for (int i = 0; i < (int)x0.size(); i++)
		{
			const Vector3r &xi = x0[i];
			const Vector3r &vi = v0[i];

			v_diff[i] = 0.0;
			curvature[i] = 0.0;
			omega_diff[i] = 0.0;

			//////////////////////////////////////////////////////////////////////////
			// Fluid
			//////////////////////////////////////////////////////////////////////////
			for (unsigned int j = 0; j < numberOfNeighbors(0, i); j++)
			{
				const unsigned int neighborIndex = getNeighbor(0, i, j);
				const Vector3r &xj = x0[neighborIndex];
				const Vector3r &vj = v0[neighborIndex];

				//////////////////////////////////////////////////////////////////////////
				// Trapped air potential 
				// Compute Eq. 2 in Ihmsen et al., "Unified spray, foam and air bubbles for particle-based fluids", 2012
				//////////////////////////////////////////////////////////////////////////
				Vector3r vivj = vi - vj;
				const Real vivjmag = vivj.norm();
				if (vivjmag > 1.0e-6)
					vivj = (1.0 / vivjmag) * vivj;

				Vector3r xixj = xi - xj;
				xixj.normalize();

				if (kernelType != KernelType::Ihmsen2012)
					v_diff[i] += mass / densities[j] * vivjmag * (static_cast<Real>(1.0) - vivj.dot(xixj)) * kernelFct(xi - xj);
				else
					v_diff[i] += vivjmag * (static_cast<Real>(1.0) - vivj.dot(xixj)) * FoamKernel::W(xi - xj);

				//////////////////////////////////////////////////////////////////////////
				// Wave crest curvature
				// Compute Eq. 4 in Ihmsen et al., "Unified spray, foam and air bubbles for particle-based fluids", 2012
				//////////////////////////////////////////////////////////////////////////
				if (-xixj.dot(normals[i]) < 0)
				{
					if (kernelType != KernelType::Ihmsen2012)
						curvature[i] += mass / densities[j] * (static_cast<Real>(1.0) - normals[i].dot(normals[neighborIndex])) * kernelFct(xi - xj);
					else
						curvature[i] += (static_cast<Real>(1.0) - normals[i].dot(normals[neighborIndex])) * FoamKernel::W(xi - xj);
				}

				//////////////////////////////////////////////////////////////////////////
				// Vorticity
				// Compute omaga^diff in Bender et al., "Turbulent Micropolar SPH Fluids with Foam", 2018
				//////////////////////////////////////////////////////////////////////////
				if (omega0.size() > 0)
				{
					const Vector3r &omegai = omega0[i];
					const Vector3r &omegaj = omega0[neighborIndex];
					if (kernelType != KernelType::Ihmsen2012)
						omega_diff[i] += mass / densities[j] * (omegai - omegaj).norm() * kernelFct(xi - xj);
					else
						omega_diff[i] += (omegai - omegaj).norm() * FoamKernel::W(xi - xj);
				}
			}

			//////////////////////////////////////////////////////////////////////////
			// Wave crest
			// Compute Eq. 7 in Ihmsen et al., "Unified spray, foam and air bubbles for particle-based fluids", 2012
			//////////////////////////////////////////////////////////////////////////
			Real delta = 0.0;
			Vector3r vi_normalized = vi;
			vi_normalized.normalize();
			if (vi_normalized.dot(normals[i]) >= 0.6)
				delta = 1.0;

			curvature[i] = delta * curvature[i];

			//////////////////////////////////////////////////////////////////////////
			// Kinetic energic
			//////////////////////////////////////////////////////////////////////////
			if (omega0.size() > 0)
				energy[i] = static_cast<Real>(0.5)*mass*vi.squaredNorm() + static_cast<Real>(0.5) * inertia * omega0[i].squaredNorm();
			else
				energy[i] = static_cast<Real>(0.5)*mass*vi.squaredNorm();
		}
	}

	//////////////////////////////////////////////////////////////////////////
	// Compute sum and max of all values
	//////////////////////////////////////////////////////////////////////////
	for (auto i = 0; i < x0.size(); i++)
	{
		sumVDiff += v_diff[i];
		maxVDiff = std::max(maxVDiff, v_diff[i]);

		if (omega0.size() > 0)
		{
			sumOmegaDiff += omega_diff[i];
			maxOmegaDiff = std::max(maxOmegaDiff, omega_diff[i]);
		}

		sumCurvature += curvature[i];
		maxCurvature = std::max(maxCurvature, curvature[i]);

		sumEnergy += energy[i];
		maxEnergy = std::max(maxEnergy, energy[i]);
	}

	sum_v_diff += sumVDiff / (Real)x0.size();
	max_v_diff = std::max(max_v_diff, maxVDiff);
	sum_max_v_diff += maxVDiff;

	if (omega0.size() > 0)
	{
		sum_omega_diff += sumOmegaDiff / (Real)x0.size();
		max_omega_diff = std::max(max_omega_diff, maxOmegaDiff);
		sum_max_omega_diff += maxOmegaDiff;
	}

	sum_curvature += sumCurvature / (Real)x0.size();
	max_curvature = std::max(max_curvature, maxCurvature);
	sum_max_curvature += maxCurvature;

	sum_energy += sumEnergy / (Real)x0.size();
	max_energy = std::max(max_energy, maxEnergy);
	sum_max_energy += maxEnergy;
}

/** Generate a foam file for each frame.
 */
void generateFoamFiles()
{
	currentFrame = startFrame;

	// Initialize kernels
	supportRadius = static_cast<Real>(4.0) * particleRadius;
	FoamKernel::setRadius(supportRadius);
	CubicKernel::setRadius(supportRadius);

	invDt = static_cast<Real>(1.0) / timeStepSize;

#ifdef _OPENMP
	const int maxThreads = omp_get_max_threads();
#else
	const int maxThreads = 1;
#endif


	bool chk = true;
	bool first = true;
	bool pointSetAdded = false;
	START_TIMING("total");
	while (chk)
	{
		// Read current frame of fluid particles
		LOG_INFO << "Reading frame: " << currentFrame;
		chk = readCurrentFrame();

		if (first)
		{
			// init foam particle vectors
			fx.reserve(10 * x0.size());
			fv.reserve(10 * x0.size());
			flifetime.reserve(10 * x0.size());
			first = false;

			for (int i = 0; i < maxThreads; i++)
			{
				fxPerThread.reserve(10 * x0.size());
				fvPerThread.reserve(10 * x0.size());
				flifetimePerThread.reserve(10 * x0.size());
			}
		}

		// init neighborhood search
		if (neighborhoodSearch == NULL)
		{
			neighborhoodSearch = new CompactNSearch::NeighborhoodSearch(supportRadius, false);
			neighborhoodSearch->set_radius(supportRadius);
			// Fluid 
			neighborhoodSearch->add_point_set(&x0[0][0], x0.size(), true, true);
			neighborhoodSearch->set_active(0u, 0u, true);
			pointSetAdded = true;
		}
		else
			neighborhoodSearch->resize_point_set(0, &x0[0][0], x0.size());

		// find the fluid neighbors of each fluid particle
		START_TIMING("neighborhoodSearch - advection");
		neighborhoodSearch->find_neighbors();
		STOP_TIMING_AVG

		// remove foam particles which exceeded their lifetime
		START_TIMING("removeParticles");
		removeParticles();
		STOP_TIMING_AVG;

		// advect each foam particle by performing the time integration
		START_TIMING("advectFoamParticles");
		advectFoamParticles();
		STOP_TIMING_AVG

		// write the foam particles
		START_TIMING("writeCurrentFrame");
		writeCurrentFrame();
		STOP_TIMING_AVG;

		// generate new foam particles 
		START_TIMING("generateFoam");
		generateFoam();
		STOP_TIMING_AVG;

		currentFrame++;
		if (currentFrame > endFrame)
			break;
	}

	delete neighborhoodSearch;
	neighborhoodSearch = NULL;

	STOP_TIMING_AVG;
}

/** Generate new foam particles using the algorithm described in :\n
* - Ihmsen et al., "Unified spray, foam and air bubbles for particle-based fluids", 2012 \n
* - Bender et al., "Turbulent Micropolar SPH Fluids with Foam", 2018 
*/
void generateFoam()
{
	// Compute the density for each fluid particle
	computeDensities();
	// Compute the normals for the fluid particles which is needed to compute the curvature
	computeNormals();

#ifdef _OPENMP
	const int maxThreads = omp_get_max_threads();
#else
	const int maxThreads = 1;
#endif

	if (splitGenerators)
	{
		for (int g = 0; g < NUM_GENERATOR_TYPES; ++g)
		{
			numParticlesOfType[g] = 0;
			for (int i=0; i < maxThreads; i++)
			{
				numParticlesOfTypePerThread[i][g] = 0;
			}
		}
	}

	#pragma omp parallel default(shared)
	{
		#pragma omp for schedule(static)  
		for (int i = 0; i < (int)x0.size(); i++)
		{
			const Vector3r &xi = x0[i];
			const Vector3r &vi = v0[i]; 

			Real v_diff = 0.0;
			Real curvature = 0.0;
			Real omega_diff = 0.0;

			// Only generate foam if we have enough neighbors
			if (numberOfNeighbors(0, i) < 15)
				continue;

			//////////////////////////////////////////////////////////////////////////
			// Fluid
			//////////////////////////////////////////////////////////////////////////
			for (unsigned int j = 0; j < numberOfNeighbors(0, i); j++)
			{
				const unsigned int neighborIndex = getNeighbor(0, i, j);
				const Vector3r &xj = x0[neighborIndex];
				const Vector3r &vj = v0[neighborIndex];

				//////////////////////////////////////////////////////////////////////////
				// Trapped air potential 
				// Compute Eq. 2 in Ihmsen et al., "Unified spray, foam and air bubbles for particle-based fluids", 2012
				//////////////////////////////////////////////////////////////////////////
				Vector3r vivj = vi - vj;
				const Real vivjmag = vivj.norm();
				if (vivjmag > 1.0e-6)
					vivj = (1.0 / vivjmag) * vivj;

				Vector3r xixj = xi - xj;
				xixj.normalize();

				if (kernelType != KernelType::Ihmsen2012)
					v_diff += mass / densities[j] * vivjmag * (static_cast<Real>(1.0) - vivj.dot(xixj)) * kernelFct(xi - xj);
				else
					v_diff += vivjmag * (static_cast<Real>(1.0) - vivj.dot(xixj)) * FoamKernel::W(xi - xj);

				//////////////////////////////////////////////////////////////////////////
				// Wave crest curvature
				// Compute Eq. 4 in Ihmsen et al., "Unified spray, foam and air bubbles for particle-based fluids", 2012
				//////////////////////////////////////////////////////////////////////////
				if (-xixj.dot(normals[i]) < 0)
				{
					if (kernelType != KernelType::Ihmsen2012)
						curvature += mass / densities[j] * (static_cast<Real>(1.0) - normals[i].dot(normals[neighborIndex])) * kernelFct(xi - xj);
					else
						curvature += (static_cast<Real>(1.0) - normals[i].dot(normals[neighborIndex])) * FoamKernel::W(xi - xj);
				}

				//////////////////////////////////////////////////////////////////////////
				// Vorticity
				// Compute omaga^diff in Bender et al., "Turbulent Micropolar SPH Fluids with Foam", 2018
				//////////////////////////////////////////////////////////////////////////
				if (omega0.size() > 0)
				{
					const Vector3r &omegai = omega0[i];
					const Vector3r &omegaj = omega0[neighborIndex];
					if (kernelType != KernelType::Ihmsen2012)
						omega_diff += mass / densities[j] * (omegai - omegaj).norm() * kernelFct(xi - xj);
					else
						omega_diff += (omegai - omegaj).norm() * FoamKernel::W(xi - xj);
				}
			}

			//////////////////////////////////////////////////////////////////////////
			// Trapped air potential 
			//////////////////////////////////////////////////////////////////////////
			const Real I_ta = clampAndNormalize(v_diff, taMin, taMax);

			//////////////////////////////////////////////////////////////////////////
			// Vorticity potential 
			//////////////////////////////////////////////////////////////////////////
			Real I_vo = 0.0;
			if (omega0.size() > 0)
				I_vo = clampAndNormalize(omega_diff, voMin, voMax);

			//////////////////////////////////////////////////////////////////////////
			// Wave crest
			// Compute Eq. 7 in Ihmsen et al., "Unified spray, foam and air bubbles for particle-based fluids", 2012
			//////////////////////////////////////////////////////////////////////////
			Real delta = 0.0;
			Vector3r vi_normalized = vi;
			vi_normalized.normalize();
			if (vi_normalized.dot(normals[i]) >= 0.6)
				delta = 1.0;
			const Real I_wc = clampAndNormalize(delta * curvature, wcMin, wcMax);

			//////////////////////////////////////////////////////////////////////////
			// Kinetic energy
			//////////////////////////////////////////////////////////////////////////
			Real I_ke = 0.0;
			unsigned int nd = 0;
			unsigned int nv = 0;
			if (omega0.size() > 0)
			{
				I_ke = clampAndNormalize(static_cast<Real>(0.5)*mass*vi.squaredNorm() + static_cast<Real>(0.5) * inertia * omega0[i].squaredNorm(), keMin, keMax);
				nd = (unsigned int)(foam_scale * I_ke * (k_ta * I_ta + k_wc * I_wc + k_vo * I_vo) * timeStepSize + 0.5);
				nv = (unsigned int)(foam_scale * I_ke * k_vo * I_vo * timeStepSize + 0.5);
			}
			else
			{
				I_ke = clampAndNormalize(static_cast<Real>(0.5)*mass*vi.squaredNorm(), keMin, keMax);
				nd = (unsigned int) std::max((foam_scale * I_ke * (k_ta * I_ta + k_wc * I_wc) * timeStepSize + static_cast<Real>(0.5)), static_cast<Real>(0.0));
				nv = 0;
			}

			const unsigned int nt = (unsigned int)(foam_scale * I_ke * k_ta * I_ta * timeStepSize + 0.5);
			const unsigned int nw = (unsigned int)(foam_scale * I_ke * k_wc * I_wc * timeStepSize + 0.5);

			//////////////////////////////////////////////////////////////////////////
			// Generate foam particles
			////////////////////////////////////////////////////////////////////////// 
			if (splitGenerators)
				generateFoamParticles(i, nt + nw + nv, nt, nw, nv, I_ke);
			else
				generateFoamParticles(i, nd, -1, -1, -1, I_ke);
		}
	}

	// Merge particles that have been generated in different threads
	for (int j = 0; j < maxThreads; j++)
	{
		for (int i = 0; i < fxPerThread[j].size(); i++)
		{
			fx.push_back(fxPerThread[j][i]);
			fv.push_back(fvPerThread[j][i]);
			flifetime.push_back(flifetimePerThread[j][i]);
			particleType.push_back(particleTypePerThread[j][i]);
		}
		fxPerThread[j].clear();
		fvPerThread[j].clear();
		flifetimePerThread[j].clear();
		particleTypePerThread[j].clear();

		for (int i = 0; i < 3; i++)
		{
			numParticlesOfType[i] += numParticlesOfTypePerThread[j][i];
		}
	}

	LOG_INFO << "# foam particles: " << fx.size();
}

std::string zeroPadding(const unsigned int number, const unsigned int length) 
{
	ostringstream out;
	out << std::internal << std::setfill('0') << std::setw(length) << number;
	return out.str();
}

/** Substitute the placeholder in the file name with the current frame number.
*/
std::string convertFileName(const std::string &inputFileName, const unsigned int currentFrame)
{
	std::string fileName = inputFileName;
	std::string::size_type pos1 = fileName.find_first_of("#", 0);
	if (pos1 == std::string::npos)
	{
		LOG_ERR << "# missing in file name.";
		exit(1);
	}
	std::string::size_type pos2 = fileName.find_first_not_of("#", pos1);
	std::string::size_type length = pos2 - pos1;

	std::string numberStr = zeroPadding(currentFrame, (unsigned int)length);
	fileName.replace(pos1, length, numberStr);
	return fileName;
}

/** Read fluid particles of current frame. 
*/
bool readCurrentFrame()
{
	std::string fileName = convertFileName(input, currentFrame);

	x0.clear();
	v0.clear();
	omega0.clear();
	return readParticles(fileName, x0, v0, omega0);
}

void writeCurrentFrame()
{
	if (output_format == 1)
		writeCurrentFrame_vtk();
	else
		writeCurrentFrame_bgeo();
}

/** Write foam particles of current frame.
*/
void writeCurrentFrame_bgeo()
{
	if (currentFrame % (1 + skipframes))
	{
		std::cout << "Skipping write of frame: " << currentFrame << std::endl;
		return;
	}
	else
	{
		std::cout << "Writing frame: " << currentFrame << std::endl;
	}

	std::string fileName = output;
	FileSystem::makeDir(FileSystem::getFilePath(FileSystem::normalizePath(output)));

	// local references for lambda capture
	Utilities::Logger & logger = Utilities::logger;
	const auto & numOfType = numParticlesOfType;
	auto & handles = handlesType;
	const auto & particleData = fx;
	const auto & particleTypes = particleType;
	const auto & nameExt = nameExtensions;
	// helper lambda to write particles depending on type
	auto writeByType = [&](const char type) {		
		extendedFileNames[type] = convertFileName(fileName, currentFrame / (1 + skipframes));
		const std::string::size_type pos = extendedFileNames[type].rfind(".");
		if (pos != std::string::npos)
			extendedFileNames[type].insert(pos, nameExt[type]);

		LOG_INFO << "Writing: " << extendedFileNames[type];
		if (handles[type].valid())
			handles[type].wait();
		particleCopies[type].clear();
		particleCopies[type].reserve(numOfType[type]);
		for (int i = 0; i < (int)particleData.size(); i++)
		{
			if (particleTypes[i] == type)
				particleCopies[type].push_back(particleData[i]);
		}
		handles[type] = std::async(std::launch::async, [type] { PartioReaderWriter::writeParticles(extendedFileNames[type], (unsigned int)particleCopies[type].size(), particleCopies[type].data(), nullptr, 0.0); });
	};

	if (splitGenerators)
	{
		writeByType(GeneratedType::TrappedAir);
		writeByType(GeneratedType::WaveCrest );
		writeByType(GeneratedType::Vorticity );
	}
	else if (splitTypes)
	{
		writeByType(ParticleType::Foam   );
		writeByType(ParticleType::Spray  );
		writeByType(ParticleType::Bubbles);
	}
	else
	{
		std::string fileName = convertFileName(output, currentFrame / (1 + skipframes));

		LOG_INFO << "Writing: " << fileName;

		if (handle.valid())
			handle.wait();

		x_copy.resize(fx.size());
		//v_copy.resize(fv.size());
		fileName_copy = fileName;
		for (int i = 0; i < (int)fx.size(); i++)
		{
			x_copy[i] = fx[i];
		}

		handle = std::async(std::launch::async, [] { PartioReaderWriter::writeParticles(fileName_copy, (unsigned int)x_copy.size(), x_copy.data(), nullptr, 0.0); });
	}
}

// VTK expects big endian
template<typename T>
inline void swapByteOrder(T* v)
{
	constexpr size_t n = sizeof(T);
	uint8_t* bytes = reinterpret_cast<uint8_t*>(v);
	for (unsigned int c = 0u; c < n / 2; c++)
		std::swap(bytes[c], bytes[n - c - 1]);
}

void writeParticlesVTK(const std::string& fileName, const unsigned int numParticles, const Vector3r* particlePositions)
{
	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";

	//////////////////////////////////////////////////////////////////////////
	// export position attribute as POINTS
	{
		std::vector<Vector3r> positions;
		positions.reserve(numParticles);
		for (unsigned int i = 0u; i < numParticles; i++)
			positions.emplace_back(particlePositions[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 = 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(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";
	}

	outfile.close();
}

/** Write foam particles of current frame.
*/
void writeCurrentFrame_vtk()
{
	if (currentFrame % (1 + skipframes))
	{
		std::cout << "Skipping write of frame: " << currentFrame << std::endl;
		return;
	}
	else
	{
		std::cout << "Writing frame: " << currentFrame << std::endl;
	}

	std::string fileName = output;
	FileSystem::makeDir(FileSystem::getFilePath(FileSystem::normalizePath(output)));

	// local references for lambda capture
	Utilities::Logger& logger = Utilities::logger;
	const auto& numOfType = numParticlesOfType;
	auto& handles = handlesType;
	const auto& particleData = fx;
	const auto& particleTypes = particleType;
	const auto& nameExt = nameExtensions;
	// helper lambda to write particles depending on type
	auto writeByType = [&](const char type) {
		extendedFileNames[type] = convertFileName(fileName, currentFrame / (1 + skipframes));
		const std::string::size_type pos = extendedFileNames[type].rfind(".");
		if (pos != std::string::npos)
			extendedFileNames[type].insert(pos, nameExt[type]);

		LOG_INFO << "Writing: " << extendedFileNames[type];
		if (handles[type].valid())
			handles[type].wait();
		particleCopies[type].clear();
		particleCopies[type].reserve(numOfType[type]);
		for (int i = 0; i < (int)particleData.size(); i++)
		{
			if (particleTypes[i] == type)
				particleCopies[type].push_back(particleData[i]);
		}
		handles[type] = std::async(std::launch::async, [type] { writeParticlesVTK(extendedFileNames[type], (unsigned int)particleCopies[type].size(), particleCopies[type].data()); });
	};

	if (splitGenerators)
	{
		writeByType(GeneratedType::TrappedAir);
		writeByType(GeneratedType::WaveCrest);
		writeByType(GeneratedType::Vorticity);
	}
	else if (splitTypes)
	{
		writeByType(ParticleType::Foam);
		writeByType(ParticleType::Spray);
		writeByType(ParticleType::Bubbles);
	}
	else
	{
		std::string fileName = convertFileName(output, currentFrame / (1 + skipframes));

		LOG_INFO << "Writing: " << fileName;

		if (handle.valid())
			handle.wait();

		x_copy.resize(fx.size());
		//v_copy.resize(fv.size());
		fileName_copy = fileName;
		for (int i = 0; i < (int)fx.size(); i++)
		{
			x_copy[i] = fx[i];
		}

		handle = std::async(std::launch::async, [] { writeParticlesVTK(fileName_copy, (unsigned int)x_copy.size(), x_copy.data()); });
	}
}

/** Clamp and normalize value. Compute Eq. 1 in 
* Ihmsen et al., "Unified spray, foam and air bubbles for particle-based fluids", 2012
*/
Real clampAndNormalize(const Real val, const Real minVal, const Real maxVal)
{
	return (std::min(val, maxVal) - std::min(val, minVal)) / (maxVal - minVal);
}

/** Compute normals of fluid particles using a color field.
*/
void computeNormals()
{
	normals.resize(x0.size());

	// Compute normals
	#pragma omp parallel default(shared)
	{
		#pragma omp for schedule(static)  
		for (int i = 0; i < (int)x0.size(); i++)
		{
			const Vector3r &xi = x0[i];
			Vector3r &ni = normals[i];
			ni.setZero();
			
			// We are only interested in surface particles
			if (numberOfNeighbors(0, i) > 20)
				continue;

			//////////////////////////////////////////////////////////////////////////
			// Fluid
			//////////////////////////////////////////////////////////////////////////
			for (unsigned int j = 0; j < numberOfNeighbors(0, i); j++)
			{
				const unsigned int neighborIndex = getNeighbor(0, i, j);
				const Vector3r &xj = x0[neighborIndex];
				const Real density_j = densities[neighborIndex];
				ni -= mass / density_j * CubicKernel::gradW(xi - xj);
			}
			ni.normalize();
		}
	}

}

/** Compute densities of the fluid particles.
*/
void computeDensities()
{
	densities.resize(x0.size());

	#pragma omp parallel default(shared)
	{
		#pragma omp for schedule(static)  
		for (int i = 0; i < (int)x0.size(); i++)
		{
			Real &density = densities[i];

			// Compute current density for particle i
			density = mass * CubicKernel::W_zero();
			const Vector3r &xi = x0[i];

			//////////////////////////////////////////////////////////////////////////
			// Fluid
			//////////////////////////////////////////////////////////////////////////
			for (unsigned int j = 0; j < numberOfNeighbors(0, i); j++)
			{
				const unsigned int neighborIndex = getNeighbor(0, i, j);
				const Vector3r &xj = x0[neighborIndex];
				density += mass * CubicKernel::W(xi - xj);
			}
		}
	}
}

/** Returns two orthogonal vectors to vec which are also orthogonal to each other.
*/
void getOrthogonalVectors(const Vector3r &vec, Vector3r &x, Vector3r &y)
{
	// Get plane vectors x, y
	Vector3r v(1, 0, 0);

	// Check, if v has same direction as vec
	if (fabs(v.dot(vec)) > 0.999)
		v = Vector3r(0, 1, 0);

	x = vec.cross(v);
	y = vec.cross(x);
	x.normalize();
	y.normalize();
}

/** Generate new foam particles for the fluid particle with the given index. 
* The particles are generated in a cylinder as described in 
* Ihmsen et al., "Unified spray, foam and air bubbles for particle-based fluids", 2012
*/
void generateFoamParticles(const unsigned int index, const unsigned int numParticles, const unsigned int numTrappedAir, const unsigned int numWaveCrest, const unsigned int numVorticity, const Real I_ke)
{
	Vector3r e1, e2;
	const Vector3r v = v0[index]; 
	Vector3r vn = v; 
	vn.normalize();
	getOrthogonalVectors(vn, e1, e2);

	e1 = particleRadius * e1;
	e2 = particleRadius * e2;

	for (unsigned int i = 0; i < numParticles; i++)
	{
		// Generate a random distribution of the foam particles in a cylinder.
		const Real Xr = uniform_distr_0_1(random_generator);
		const Real Xtheta = uniform_distr_0_1(random_generator);
		const Real Xh = uniform_distr_0_1(random_generator);

		const Real r = particleRadius * sqrt(Xr);
		const Real theta = Xtheta * static_cast<Real>(2.0 * M_PI);
		const Real h = (Xh- static_cast<Real>(0.5)) * timeStepSize * v.norm();

		const Vector3r xd = x0[index] + r * cos(theta) * e1 + r * sin(theta) * e2 + h * vn;
		const Vector3r vd = r * cos(theta) * e1 + r * sin(theta) * e2 + v;
		
		unsigned char generatorType = GeneratedType::TrappedAir;
		if (i >= numTrappedAir)
			generatorType = GeneratedType::WaveCrest;
		if (i >= (numTrappedAir + numWaveCrest))
			generatorType = GeneratedType::Vorticity;


#ifdef _OPENMP
		int tid = omp_get_thread_num();
#else
		int tid = 0;
#endif
		fxPerThread[tid].push_back(xd);
		fvPerThread[tid].push_back(vd);
		if (splitGenerators)
		{
			particleTypePerThread[tid].push_back(generatorType);
			numParticlesOfTypePerThread[tid][generatorType]++;
		}
		else
		{
			particleTypePerThread[tid].push_back(ParticleType::Foam); // default, actual value will be set during advection
		}
		Real lt = lifetimeMin + I_ke / keMax * uniform_distr_0_1(random_generator) *(lifetimeMax - lifetimeMin);
		flifetimePerThread[tid].push_back(lt);
	}
}

/** Avect all foam particles according to 
* Ihmsen et al., "Unified spray, foam and air bubbles for particle-based fluids", 2012
*/
void advectFoamParticles()
{
	Vector3r g(0.0, -9.81, 0.0);
	if (splitTypes)
	{
		numParticlesOfType[ParticleType::Foam] = 0;
		numParticlesOfType[ParticleType::Spray] = 0;
		numParticlesOfType[ParticleType::Bubbles] = 0;
	}

	std::vector<std::vector<unsigned int>> neighbors;
	neighbors.reserve(100);

	#pragma omp parallel default(shared), private(neighbors)
	{
		#pragma omp for schedule(static)  
		for (int i = 0; i < (int) fx.size(); i++)
		{
			const Vector3r &xi = fx[i];

			// determine particle type
			neighborhoodSearch->find_neighbors(xi.data(), neighbors);
			const unsigned int numFluidNeighbors = (unsigned int) neighbors[0].size();

			unsigned char ftype = ParticleType::Foam;
			if (numFluidNeighbors < 6)
				ftype = ParticleType::Spray;
			else if (numFluidNeighbors > 20)
				ftype = ParticleType::Bubbles;

			// Fix particle type if classified as foam because of boundary.
			// If a particle is close to the boundary the number of fluid neighbors will be lower than for a particle inside the fluid.
			// A corrected number of fluid neighbors is estimated by adding the current number of fluid neighbors scaled by the volume
			// fraction of the cap of the sphere making up the neighborhood that lies outside of the boundary.
			if (ftype == ParticleType::Foam)
			{
				auto correctedFluidNeighbors = numFluidNeighbors;
				// Used to compute the volume of the cap of the neighborhood sphere that lies outside the boundary.
				auto unitSphereCapVolume = [&](const Real h)
				{
					return h * h * (M_PI / 3) * (3 - h);
				};
				for (auto j = 0u; j < 3; ++j)
				{
					Real d;
					d = std::abs(xi[j] - bbMin[j]);
					if (d < supportRadius)
						correctedFluidNeighbors += (decltype(correctedFluidNeighbors))(std::ceil(correctedFluidNeighbors * unitSphereCapVolume(1 - d / supportRadius)));
					d = std::abs(xi[j] - bbMax[j]);
					if (d < supportRadius)
						correctedFluidNeighbors += (decltype(correctedFluidNeighbors))(std::ceil(correctedFluidNeighbors * unitSphereCapVolume(1 - d / supportRadius)));
					if (correctedFluidNeighbors > 20)
					{
						ftype = ParticleType::Bubbles;
						break;
					}
				}
			}

			if (splitTypes)
			{
				particleType[i] = ftype;
				numParticlesOfType[ftype]++;
			}

			// handle bounding box
			if (((xi.array() < bbMin.array() || xi.array() > bbMax.array()).any()))
			{
				if (bbType == BbType::Kill)
				{
					flifetime[i] = 0;
				}
				else if (bbType == BbType::Lifesteal)
				{
					flifetime[i] -= static_cast<Real>(1000.0) * timeStepSize;
				}
				// clamp particle position to bounding box and reflect velocity on box wall
				else if (bbType == BbType::Clamp)
				{
					const Vector3r bbNormal = ((fx[i].array() < bbMin.array()).cast<Real>() - (fx[i].array() > bbMax.array()).cast<Real>()).matrix().normalized();
					const Vector3r vReflect = fv[i] - 2 * bbNormal.dot(fv[i]) * bbNormal;
					fv[i] = Real(0.25) * vReflect;
					fx[i] = (fx[i].array() < bbMin.array()).select(bbMin, fx[i]);
					fx[i] = (fx[i].array() > bbMax.array()).select(bbMax, fx[i]);
					// also lifesteal
					//flifetime[i] -= 1000.0 * timeStepSize;
				}
			}

			// advect particle
			if (ftype == ParticleType::Spray)
			{
				// spray
				fv[i] += timeStepSize * g;
				//fv[i] *= 0.99;
				fx[i] += timeStepSize * fv[i];

// 				if (numFluidNeighbors < 2)
// 					flifetime[i] -= 15.0*timeStepSize;
// 				else
// 					flifetime[i] -= 2.0*timeStepSize;
			}
			else if ((ftype == ParticleType::Foam) || (ftype == ParticleType::Bubbles))
			{
				Vector3r vf = Vector3r::Zero();
				Real sumK = 0.0;
				for (unsigned int j = 0; j < numFluidNeighbors; j++)
				{
					const unsigned int neighborIndex = neighbors[0][j];
					const Vector3r &xj = x0[neighborIndex];
					const Real K = CubicKernel::W(xi - xj);
					const Vector3r v = v0[neighborIndex]; 
					vf += v * K;
					sumK += K;
				}
				vf = (1.0 / sumK) * vf;

				if (ftype == ParticleType::Foam)
				{
					// foam
					//fv[i] = vf;
					fx[i] += timeStepSize * vf;
					flifetime[i] -= timeStepSize;
				}
				else if (ftype == ParticleType::Bubbles)
				{
					// bubbles
					fv[i] += timeStepSize * (-k_buoyancy * g + k_drag * invDt * (vf - fv[i]));
					fx[i] += timeStepSize * fv[i];

					//flifetime[i] -= 0.333*timeStepSize;
				}
			}
		}
	}
	if (splitTypes || splitGenerators)
		for (unsigned int i = 0; i < numTypes; i++)
			LOG_INFO << "#" << nameExtensions[i] << ": " << numParticlesOfType[i];
}

/** Remove foam particles which are at the end of their lifetime.
*/
void removeParticles()
{
	unsigned int removedParticles = 0;
	for (int i = 0; i < (int)fx.size(); i++)
	{
		if (flifetime[i] <= 0.0)
		{
			removedParticles++;
		}
		else
		{
			fx[i - removedParticles] = fx[i];
			fv[i - removedParticles] = fv[i];
			flifetime[i - removedParticles] = flifetime[i];
			particleType[i - removedParticles] = particleType[i];
		}
	}
	if (removedParticles > 0)
	{
		fx.resize(fx.size() - removedParticles);
		fv.resize(fv.size() - removedParticles);
		flifetime.resize(flifetime.size() - removedParticles);
		particleType.resize(particleType.size() - removedParticles);
	}
}

/** Read particle data from a partio file.
*/
bool readParticles(const std::string &fileName, std::vector<Vector3r> &positions, std::vector<Vector3r> &velocities, std::vector<Vector3r> &angularVelocities)
{
	if (!FileSystem::fileExists(fileName))
		return false;

	Partio::ParticlesDataMutable* data = Partio::read(fileName.c_str());
	if (!data)
		return false;

	unsigned int posIndex = 0xffffffff;
	unsigned int velIndex = 0xffffffff;
	unsigned int omegaIndex = 0xffffffff;

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

	Partio::ParticleAttribute attr;

	if (posIndex != 0xffffffff)
	{
		unsigned int fSize = (unsigned int)positions.size();
		positions.resize(fSize + data->numParticles());
		data->attributeInfo(posIndex, attr);
		for (int i = 0; i < data->numParticles(); i++)
		{
			const float *pos = data->data<float>(attr, i);
			Vector3r x(pos[0], pos[1], pos[2]);
			positions[i + fSize] = x;
		}
	}

	if (velIndex != 0xffffffff)
	{
		unsigned int fSize = (unsigned int)velocities.size();
		velocities.resize(fSize + data->numParticles());
		data->attributeInfo(velIndex, attr);
		for (int i = 0; i < data->numParticles(); i++)
		{
			const float *vel = data->data<float>(attr, i);
			Vector3r v(vel[0], vel[1], vel[2]);
			velocities[i + fSize] = v;
		}
	}
	else
	{
		unsigned int fSize = (unsigned int)velocities.size();
		velocities.resize(fSize + data->numParticles());
		for (int i = 0; i < data->numParticles(); i++)
			velocities[i + fSize].setZero();
	}

	if (omegaIndex != 0xffffffff)
	{
		unsigned int fSize = (unsigned int)angularVelocities.size();
		angularVelocities.resize(fSize + data->numParticles());
		data->attributeInfo(omegaIndex, attr);
		for (int i = 0; i < data->numParticles(); i++)
		{
			const float *vel = data->data<float>(attr, i);
			Vector3r v(vel[0], vel[1], vel[2]);
			angularVelocities[i + fSize] = v;
		}
	}

	data->release();
	return true;
}

back to top

Software Heritage — Copyright (C) 2015–2025, The Software Heritage developers. License: GNU AGPLv3+.
The source code of Software Heritage itself is available on our development forge.
The source code files archived by Software Heritage are available under their own copyright and licenses.
Terms of use: Archive access, API— Content policy— Contact— JavaScript license information— Web API