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

  • 18bb18e
  • /
  • Tools
  • /
  • PartioViewer
  • /
  • PartioViewer.cpp
Raw File Download

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
content badge Iframe embedding
swh:1:cnt:193d88cfd217463a7bcbb95aafb86f0eb17e2b9d
directory badge Iframe embedding
swh:1:dir:95e98d0f5a81aa494f6e12fe92553a8ea0a3a3a4

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
Generate software citation in BibTex format (requires biblatex-software package)
Generating citation ...
Generate software citation in BibTex format (requires biblatex-software package)
Generating citation ...
PartioViewer.cpp
#include "PartioViewer.h"

#include "Utilities/OBJLoader.h"
#include "Utilities/FileSystem.h"
#include "Utilities/BinaryFileReaderWriter.h"
#include "Utilities/Version.h"
#include "extern/cxxopts/cxxopts.hpp"
#include "Utilities/Timing.h"
#include "extern/toojpeg/toojpeg.h"
#include "GUI/OpenGL/PartioViewer_OpenGL.h"
#ifdef USE_IMGUI
#include "GUI/imgui/PartioViewer_GUI_imgui.h"
#else
#include "GUI/TweakBar/PartioViewer_GUI_TweakBar.h"
#endif

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

FILE *PartioViewer::m_jpegFile = nullptr;


PartioViewer::PartioViewer()
{
	m_frameIndex = 1;
	m_startFrame = -1;
	m_endFrame = -1;
	m_firstRBIndex = 1;
	m_numberOfRigidBodies = 0;
	m_particleRadius = 0.025;
	m_planeNormal = Vector3f(0, 0, -1);
	m_planePoint.setZero();
	m_renderSequence = false;
	m_renderVideo = false;
	m_overWrite = true;
	m_ffmpegPath = "ffmpeg";
	m_rbDataFile = "";
	m_ffmpegPipe = nullptr;
	m_width = 1280;
	m_height = 960;
	m_fps = 25;
	m_doPause = true;
	m_usePlane = false;
	m_useRBData = false;
	m_jpegFile = nullptr;
}


PartioViewer::~PartioViewer()
{
}

int PartioViewer::run(int argc, char **argv)
{
	m_argc = argc;
	m_argv = argv;

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

	LOG_INFO << "Git refspec: " << GIT_REFSPEC;
	LOG_INFO << "Git SHA1: " << GIT_SHA1;
	LOG_INFO << "Git status: " << GIT_LOCAL_STATUS;

	m_exePath = FileSystem::getProgramPath();
#ifdef USE_IMGUI
	m_gui = new PartioViewer_GUI_imgui(this);
#else
	m_gui = new PartioViewer_GUI_TweakBar(this);
#endif

	try
	{
		cxxopts::Options options(argv[0], "PartioViewer - Visualize partio data.");
		options
			.positional_help("[partio file]")
			.show_positional_help();

		options.add_options()
			("h,help", "Print help")
			("renderSequence", "Render a sequence from startFrame to endFrame as jpeg.")
			("renderVideo", "Render a sequence from startFrame to endFrame as video."
				"This function requires ffmpeg which must be in the PATH or the ffmpegPath parameter must be set.")
			("noOverwrite", "Do not overwrite existing frames when using --renderSequence option."
					"Existing frames are not loaded at all which accelerates the image sequence generation.")
			("o,outdir", "Output directory for images", cxxopts::value<std::string>())
			("rbData", "Rigid body data to visualize (bin file)", cxxopts::value<std::string>())
			("ffmpegPath", "Path of the ffmpeg excutable.", cxxopts::value<std::string>())
			("width", "Width of the image in pixels.", cxxopts::value<int>()->default_value("1024"))
			("height", "Height of the image in pixels.", cxxopts::value<int>()->default_value("768"))
			("fps", "Frame rate of video.", cxxopts::value<int>()->default_value("25"))
			("r,radius", "Particle radius", cxxopts::value<Real>()->default_value("0.025"))
			("s,startFrame", "Start frame (only used if value is >= 0)", cxxopts::value<int>()->default_value("-1"))
			("e,endFrame", "End frame (only used if value is >= 0)", cxxopts::value<int>()->default_value("-1"))
			("colorField", "Name of field that is used as default for the color.", cxxopts::value<std::string>()->default_value("velocity"))
			("colorMapType", "Default color map (0=None, 1=Jet, 2=Plasma, 3=CoolWarm, 4=BlueWhiteRed, 5=Seismic)", cxxopts::value<unsigned int>()->default_value("1"))
			("renderMinValue", "Default min value of field.", cxxopts::value<float>()->default_value("0.0"))
			("renderMaxValue", "Default max value of field.", cxxopts::value<float>()->default_value("10.0"))
			;

		m_gui->addOptions(options);

		options.add_options("invisible")
			("partio-file", "Partio file", cxxopts::value<std::vector<std::string>>());

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

		if (result.count("help"))
		{
			LOG_INFO << options.help({ "", "Group" });
			exit(0);
		}

		if (result.count("partio-file"))
		{
			std::vector<std::string> inputFile = result["partio-file"].as<std::vector<std::string>>();

			// if a directory is given as input, assume that the partio files are in directory partio
			// and rigid body data is in rigid_bodies
			if (inputFile.size() == 1)
			{
				// check if argument is a directory
				if (FileSystem::isDirectory(inputFile[0]))
				{
					std::string oDir = inputFile[0];
					std::string partioDir = FileSystem::normalizePath(inputFile[0] + "/partio");

					// check if partio directory exists
					if (FileSystem::isDirectory(partioDir))
					{
						// get all files in directory
						std::vector<std::string> fileNames;
						FileSystem::getFilesInDirectory(partioDir, fileNames);
						std::vector<std::string> fileBases;

						// filter *.bgeo files
						fileNames.erase(std::remove_if(fileNames.begin(), fileNames.end(), [](const std::string &s) { return FileSystem::getFileExt(s) != "bgeo"; }), fileNames.end());

						if (fileNames.size() > 0)
						{
							// create list of pairs <index, filename>
							std::vector<std::pair<unsigned int, std::string>> framePairs;
							framePairs.resize(fileNames.size());
							for (int i = 0; i < fileNames.size(); i++)
								framePairs[i] = { getFrameIndexFromFile(fileNames[i]), fileNames[i] };

							// sort list with respect to index
							std::sort(framePairs.begin(), framePairs.end());

							while (framePairs.size() > 0)
							{
								// found a new fluid phase
								fileBases.push_back(framePairs[0].second);

								// remove all other file names of this phase from the list
								std::string test = convertFileName(framePairs[0].second, "#");
								framePairs.erase(std::remove_if(framePairs.begin(), framePairs.end(), [&](const std::pair<unsigned int, std::string> &s) { return test == convertFileName(s.second, "#"); }), framePairs.end());
							}

							// generate list of input files
							inputFile.clear();
							inputFile.resize(fileBases.size());
							for (int i = 0; i < fileBases.size(); i++)
							{
								inputFile[i] = FileSystem::normalizePath(partioDir + "/" + fileBases[i]);
							}
						}
					}

					// only search for files if user did not define the file 
					if (!result.count("rbData"))
					{
						// check if directory exists
						std::string rbDir = FileSystem::normalizePath(oDir + "/rigid_bodies");
						if (FileSystem::isDirectory(rbDir))
						{
							// get all files in directory
							std::vector<std::string> fileNames;
							FileSystem::getFilesInDirectory(rbDir, fileNames);

							// filter *.bin files
							fileNames.erase(std::remove_if(fileNames.begin(), fileNames.end(), [](const std::string &s) { return FileSystem::getFileExt(s) != "bin"; }), fileNames.end());

							if (fileNames.size() > 0)
							{
								// create list of pairs <index, filename>
								std::vector<std::pair<unsigned int, std::string>> framePairs;
								framePairs.resize(fileNames.size());
								for (int i = 0; i < fileNames.size(); i++)
									framePairs[i] = { getFrameIndexFromFile(fileNames[i]), fileNames[i] };

								// sort list with respect to index
								std::sort(framePairs.begin(), framePairs.end());

								// get file with lowest index
								m_rbDataFile = FileSystem::normalizePath(rbDir + "/" + framePairs[0].second);
								m_useRBData = true;
							}
						}
					}
				}
			}


			getFluids().resize(inputFile.size());
			for (int i = 0; i < inputFile.size(); i++)
			{
				getFluids()[i].inputFile = inputFile[i];
				if (FileSystem::isRelativePath(inputFile[i]))
					getFluids()[i].inputFile = FileSystem::normalizePath(m_exePath + "/" + inputFile[i]);
				getFluids()[i].currentFile = getFluids()[i].inputFile;
			}
		}
		else
		{
			LOG_INFO << options.help({ "", "Group" });
			exit(0);
		}

		m_outPath = m_exePath;
		if (result.count("outdir"))
			m_outPath = result["outdir"].as<std::string>();

		if (result.count("rbData"))
		{
			m_rbDataFile = result["rbData"].as<std::string>();
			m_useRBData = true;
		}

		if (result.count("ffmpegPath"))
			m_ffmpegPath = result["ffmpegPath"].as<std::string>();

		m_particleRadius = 0.025;
		if (result.count("radius"))
			m_particleRadius = result["radius"].as<Real>();
		LOG_INFO << "Radius: " << m_particleRadius;

		if (result.count("startFrame"))
			m_startFrame = result["startFrame"].as<int>();
		LOG_INFO << "Start frame: " << m_startFrame;

		if (result.count("endFrame"))
			m_endFrame = result["endFrame"].as<int>();
		LOG_INFO << "End frame: " << m_endFrame;

		if (result.count("renderSequence"))
		{
			m_renderSequence = true;
			if (result.count("noOverwrite"))
				m_overWrite = false;
		}

		if (result.count("renderVideo"))
			m_renderVideo = true;

		if (result.count("width"))
			m_width = result["width"].as<int>();

		if (result.count("height"))
			m_height = result["height"].as<int>();

		if (result.count("fps"))
			m_fps = result["fps"].as<int>();

		m_defaultColorFieldName = "velocity";
		if (result.count("colorField"))
			m_defaultColorFieldName = result["colorField"].as<std::string>();

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

		m_defaultRenderMinValue = 0.0;
		if (result.count("renderMinValue"))
			m_defaultRenderMinValue = result["renderMinValue"].as<float>();

		m_defaultRenderMaxValue = 10.0;
		if (result.count("renderMaxValue"))
			m_defaultRenderMaxValue = result["renderMaxValue"].as<float>();

		m_gui->parseOptions(result);

		m_image.resize(3 * m_width * m_height);
	}
	catch (const cxxopts::OptionException& e)
	{
		LOG_INFO << "error parsing options: " << e.what();
		exit(1);
	}

	m_frameIndex = getFrameIndexFromFile(getFluids()[0].inputFile);
	if (m_frameIndex == 0xffffffff)
		m_frameIndex = 0;
	m_firstRBIndex = m_frameIndex;

	// read particle data
	if (!updateData())
	{
		LOG_ERR << "Unable to read partio file or did not find position data.";
		exit(1);
	}

	// set default color field
	for (auto i = 0; i < m_fluids.size(); i++)
	{
		if (m_fluids[i].partioData)
		{
			bool found = false;
			int idx = 0;
			int velIdx = 0;
			for (int j = 0; j < m_fluids[i].partioData->numAttributes(); j++)
			{
				Partio::ParticleAttribute attr;
				m_fluids[i].partioData->attributeInfo(j, attr);
				if ((attr.type == Partio::FLOAT) || (attr.type == Partio::VECTOR) || (attr.type == Partio::INT))
				{
					// select the default color field name
					if (attr.name == m_defaultColorFieldName)
					{
						m_fluids[i].m_colorField = idx;
						found = true;
						break;
					}
					if (attr.name == "velocity")
						velIdx = idx;

					idx++;
				}
			}
			// if default color field name was not found, use velocity as default
			if (!found)
				m_fluids[i].m_colorField = velIdx;
			m_fluids[i].m_colorMapType = m_defaultColorMapType;
			m_fluids[i].m_renderMinValue = m_defaultRenderMinValue;
			m_fluids[i].m_renderMaxValue = m_defaultRenderMaxValue;
		}
	}


	m_gui->init();

	initGUI();
	updateBoundingBox();

	if ((m_renderSequence || m_renderVideo) && (m_startFrame >= 0))
		setFrame(m_startFrame);

	m_gui->run();

	m_gui->cleanup();

	for (size_t i = 0; i < getFluids().size(); i++)
		getFluids()[i].partioData->release();
	Timing::printAverageTimes();
	Timing::printTimeSums();

	delete m_gui;

	return 0;
}

void PartioViewer::timeStep()
{
	if (!m_renderSequence && !m_renderVideo)
	{
		if (!m_doPause)
		{
			if (!nextFrame())
				m_doPause = true;
		}
	}
	else if (m_renderVideo)
	{
		generateVideo();
		m_gui->stop();
	}
	else if (m_renderSequence)
	{
		generateSequence();
		m_gui->stop();
	}
}


void PartioViewer::updateScalarField()
{
	m_scalarField.resize(m_fluids.size());
	for (unsigned int fluidModelIndex = 0; fluidModelIndex < m_fluids.size(); fluidModelIndex++)
	{
		auto& fluid = m_fluids[fluidModelIndex];
		// Draw simulation model
		const unsigned int nParticles = fluid.partioData->numParticles();

		Partio::ParticleAttribute attr;
		fluid.partioData->attributeInfo(fluid.m_colorField, attr);

		m_scalarField[fluidModelIndex].resize(nParticles);
		for (unsigned int i = 0u; i < nParticles; i++)
		{
			if (attr.type == Partio::VECTOR)
			{
				const Eigen::Map<const Eigen::Vector3f> vec(fluid.partioData->data<float>(attr, i));
				m_scalarField[fluidModelIndex][i] = static_cast<float>(vec.norm());
			}
			else if (attr.type == Partio::FLOAT)
			{
				m_scalarField[fluidModelIndex][i] = *fluid.partioData->data<float>(attr, i);
			}
			else if (attr.type == Partio::INT)
			{
				m_scalarField[fluidModelIndex][i] = static_cast<float>(*fluid.partioData->data<int>(attr, i));
			}
		}
	}
}

void PartioViewer::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;
}


bool PartioViewer::readPartioFile(const std::string &fileName, Partio::ParticlesDataMutable* &partioData, unsigned int &posIndex, const bool printInfo)
{
	if (!FileSystem::fileExists(fileName))
		return false;

	if (partioData)
		partioData->release();
	partioData = Partio::read(fileName.c_str());

	if (!partioData)
		return false;

	LOG_INFO << "Frame: " << m_frameIndex << ", number of particles: " << partioData->numParticles();

	posIndex = 0xffffffff;

	for (int i = 0; i < partioData->numAttributes(); i++)
	{
		Partio::ParticleAttribute attr;
		partioData->attributeInfo(i, attr);
		if (attr.name == "position")
			posIndex = i;
		if (printInfo)
			LOG_INFO << "Found attribute: " << attr.name;
	}
	if (posIndex == 0xffffffff)
		return false;

	return true;
}

bool PartioViewer::readRigidBodyData(std::string fileName, const bool first)
{
	BinaryFileReader binReader;
	if (!binReader.openFile(fileName))
		return false;

	if (first)
	{
		m_firstRBIndex = m_frameIndex;
		m_numberOfRigidBodies = 0;
		binReader.read(m_numberOfRigidBodies);
		m_boundaries.resize(m_numberOfRigidBodies);
		for (unsigned int i = 0; i < m_numberOfRigidBodies; i++)
		{
			std::string meshFileName = "";
			binReader.read(meshFileName);

			Eigen::Vector3f scale;
			binReader.readMatrix(scale);

			meshFileName = FileSystem::normalizePath(FileSystem::getFilePath(fileName) + "/" + meshFileName);

			loadObj(meshFileName, m_boundaries[i].mesh, scale.cast<Real>());
			m_boundaries[i].x0.resize(m_boundaries[i].mesh.numVertices());
			for (unsigned int j = 0; j < m_boundaries[i].x0.size(); j++)
				m_boundaries[i].x0[j] = m_boundaries[i].mesh.getVertices()[j];

			binReader.read(m_boundaries[i].isWall);
			binReader.readMatrix(m_boundaries[i].color);
		}
	}

	for (unsigned int i = 0; i < m_numberOfRigidBodies; i++)
	{
		binReader.readMatrix(m_boundaries[i].t);
		binReader.readMatrix(m_boundaries[i].R);
		for (unsigned int j = 0; j < m_boundaries[i].x0.size(); j++)
			m_boundaries[i].mesh.getVertices()[j] = m_boundaries[i].R.cast<Real>() * m_boundaries[i].x0[j] + m_boundaries[i].t.cast<Real>();

		m_boundaries[i].mesh.updateNormals();
		m_boundaries[i].mesh.updateVertexNormals();
	}

	binReader.closeFile();
	return true;
}


unsigned int PartioViewer::getFrameIndexFromFile(const std::string &inputFileName)
{
	std::string fileName = inputFileName;

	std::smatch match;
	std::string tmp = fileName;
	std::string frameNumber = "";
	while (regex_search(tmp, match, std::regex("([0-9]+)")))
	{
		frameNumber = match.str();
		tmp = match.suffix();
	}
	if (frameNumber != "")
		return (unsigned int)std::stoi(frameNumber);
	else
		return 0xffffffff;
}

std::string PartioViewer::convertFileName(const std::string &inputFileName, const std::string currentFrame)
{
	std::string fileName = inputFileName;

	std::smatch match;
	std::string tmp = fileName;
	std::string frameNumber = "";
	while (regex_search(tmp, match, std::regex("([0-9]+)")))
	{
		frameNumber = match.str();
		tmp = match.suffix();
	}
	std::string res = "";
	if (frameNumber != "")
		res = frameNumber;
	else
		return "";

	std::string::size_type pos1 = fileName.find_last_of(res) - res.size() + 1;

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


bool PartioViewer::nextFrame()
{
	m_frameIndex++;
	if (!updateData())
	{
		LOG_INFO << "File with index " << m_frameIndex << " does not exist.";
		m_frameIndex--;
		return false;
	}
	m_gui->update();
	return true;
}

bool PartioViewer::prevFrame()
{
	if (m_frameIndex > 0)
	{
		m_frameIndex--;
		if (!updateData())
		{
			LOG_INFO << "File with index " << m_frameIndex << " does not exist.";
			m_frameIndex++;
			return false;
		}
	}
	m_gui->update();
	return true;
}


bool PartioViewer::setFrame(const unsigned int index)
{
	unsigned int lastIndex = m_frameIndex;
	m_frameIndex = index;
	if (!updateData())
	{
		LOG_INFO << "File with index " << m_frameIndex << " does not exist.";
		m_frameIndex = lastIndex;
		return false;
	}
	m_gui->update();
	return true;
}

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


bool PartioViewer::updateData()
{
	bool chk = false;
	for (size_t i = 0; i < getFluids().size(); i++)
	{
		getFluids()[i].currentFile = convertFileName(getFluids()[i].inputFile, to_string(m_frameIndex));
		LOG_INFO << getFluids()[i].currentFile;
		if (readPartioFile(getFluids()[i].currentFile, getFluids()[i].partioData, getFluids()[i].posIndex))
			chk = true;

		updateBoundingBox();

		if (m_usePlane)
		{
			Partio::ParticleAttribute posAttr;
			getFluids()[i].partioData->attributeInfo(getFluids()[i].posIndex, posAttr);
			const float* partioX = getFluids()[i].partioData->data<float>(posAttr, 0);
			getFluids()[i].visibleParticles.clear();
			getFluids()[i].visibleParticles.reserve(getFluids()[i].partioData->numParticles());
			Vector3f normal = m_planeNormal;
			normal.normalize();
			for (auto j = 0; j < getFluids()[i].partioData->numParticles(); j++)
			{
				const Eigen::Map<const Eigen::Vector3f> vec(&partioX[3 * j]);
				if ((vec.dot(normal) - m_planePoint.dot(normal)) > 0)
					getFluids()[i].visibleParticles.push_back(j);
			}
		}
	}
	if (chk)
	{
		if (m_useRBData)
		{
			string currentFile = convertFileName(m_rbDataFile, to_string(m_frameIndex));
			LOG_INFO << currentFile;
			readRigidBodyData(currentFile, m_frameIndex == m_firstRBIndex);
		}
		updateScalarField();
	}
	return chk;
}

bool PartioViewer::imagesExist(const unsigned int frameIndex)
{
	bool res = true;
	for (size_t i = 0; i < getFluids().size(); i++)
	{
		const std::string currentFile = convertFileName(getFluids()[i].inputFile, to_string(m_frameIndex));
		const std::string imageFileName = FileSystem::normalizePath(m_outPath + "/" + FileSystem::getFileName(currentFile) + ".jpg");
		if (!FileSystem::fileExists(imageFileName))
			res = false;
	}
	return res;
}

void PartioViewer::addImageDataToVideo()
{
	fwrite(m_image.data(), m_width*m_height * 3, 1, m_ffmpegPipe);
}

void PartioViewer::saveImageData()
{
	FileSystem::makeDirs(m_outPath);
	std::string fileName = FileSystem::normalizePath(m_outPath + "/" + FileSystem::getFileName(getFluids()[0].currentFile) + ".jpg");
	LOG_INFO << "Write file: " << fileName;
	saveImage(fileName, m_image.data(), m_width, m_height);
}

void PartioViewer::saveFrame()
{
	m_gui->render();
	PartioViewer_OpenGL::getImage(m_width, m_height, m_image.data());
	saveImageData();
}

void PartioViewer::outputJpg(unsigned char oneByte)
{
	fputc(oneByte, m_jpegFile);
}


void PartioViewer::saveImage(const std::string &fileName, const void *pixels, unsigned int width, unsigned int height)
{
	m_jpegFile = fopen(fileName.c_str(), "wb");
	TooJpeg::writeJpeg(outputJpg, pixels, width, height);
	fclose(m_jpegFile);
}


void PartioViewer::generateSequence()
{
	m_width = m_gui->getWidth();
	m_height = m_gui->getHeight();

	// height must be divisible by 2
	if (m_height % 2 == 1)
		m_height--;
	// width must be divisible by 2
	if (m_width % 2 == 1)
		m_width--;

	m_image.resize(3 * m_width*m_height);

	if ((m_startFrame >= 0) && (!imagesExist(m_startFrame)))
		setFrame(m_startFrame);

	bool chk = true;
	while (chk && ((m_frameIndex <= m_endFrame) || (m_endFrame < 0)))
	{
		if (m_overWrite || !imagesExist(m_frameIndex))
		{
			if (!updateData())
			{
				m_frameIndex--;
				chk = false;
				break;
			}

			m_gui->render();

			PartioViewer_OpenGL::getImage(m_width, m_height, m_image.data());
			saveImageData();
		}
		m_frameIndex++;
	}
}


void PartioViewer::generateVideo()
{
	m_width = m_gui->getWidth();
	m_height = m_gui->getHeight();

	// height must be divisible by 2
	if (m_height % 2 == 1)
		m_height--;
	// width must be divisible by 2
	if (m_width % 2 == 1)
		m_width--;

	// open pipe to ffmpeg
	stringstream sstm;

	FileSystem::makeDirs(m_outPath);
	std::string videoFile = FileSystem::normalizePath(m_outPath + "/output.mp4");
	sstm << m_ffmpegPath << " -y -hide_banner -nostats -loglevel panic -r " << m_fps << " -f rawvideo -vcodec rawvideo -pix_fmt rgb24 -s "
		<< m_width << "x" << m_height << " -i - -threads 0 -preset fast -pix_fmt yuv420p -crf 21 " << videoFile << " > ffmpeg.log";

	#ifdef WIN32
	if (!(m_ffmpegPipe = _popen(sstm.str().c_str(), "wb")))
		#else
	if (!(m_ffmpegPipe = popen(sstm.str().c_str(), "w")))
		#endif
	{
		LOG_ERR << "Cannot open pipe to ffmpeg.";
		return;
	}

	m_image.resize(3 * m_width*m_height);

	if (m_startFrame >= 0)
		setFrame(m_startFrame);

	bool chk = true;
	while (chk && ((m_frameIndex <= m_endFrame) || (m_endFrame < 0)))
	{
		m_gui->render();

		PartioViewer_OpenGL::getImage(m_width, m_height, m_image.data());
		addImageDataToVideo();
		chk = nextFrame();
	}
	fflush(m_ffmpegPipe);
	fclose(m_ffmpegPipe);
	m_ffmpegPipe = nullptr;
	LOG_INFO << "Generated video: " << videoFile;
}


void PartioViewer::particleInfo()
{
	for (size_t i = 0; i < getFluids().size(); i++)
	{
		for (unsigned int k = 0; k < getFluids()[i].selectedParticles.size(); k++)
		{
			unsigned int index = getFluids()[i].selectedParticles[k];
			std::cout << "Index: " << index << "\n";

			for (int j = 0; j < getFluids()[i].partioData->numAttributes(); j++)
			{
				Partio::ParticleAttribute attr;
				getFluids()[i].partioData->attributeInfo(j, attr);

				if (attr.type == Partio::FLOAT)
					std::cout << attr.name << ": " << *getFluids()[i].partioData->data<float>(attr, index) << "\n";
				else if (attr.type == Partio::INT)
					std::cout << attr.name << ": " << *getFluids()[i].partioData->data<int>(attr, index) << "\n";
				else if (attr.type == Partio::VECTOR)
				{
					const float *v = getFluids()[i].partioData->data<float>(attr, index);
					Vector3r vec(v[0], v[1], v[2]);
					std::cout << attr.name << ": " << vec.transpose() << "\n";
				}
			}
			std::cout << "\n";
		}
	}
}

void PartioViewer::updateBoundingBox()
{
	m_fluidBoundingBox.setEmpty();
	for (size_t i = 0; i < m_fluids.size(); i++)
	{
		if (m_fluids[i].partioData)
		{
			//	const Real r2 = particleRadius*0.5;
			Partio::ParticleAttribute posAttr;
			m_fluids[i].partioData->attributeInfo(m_fluids[i].posIndex, posAttr);
			const float* partioX = m_fluids[i].partioData->data<float>(posAttr, 0);
			for (int j = 0; j < m_fluids[i].partioData->numParticles(); j++)
			{
				const Eigen::Map<const Eigen::Vector3f> vec(&partioX[3 * j]);
				if (!std::isfinite(vec[0]) || !std::isfinite(vec[1]) || !std::isfinite(vec[2]))
					LOG_WARN << "Warning: Fluid " << i << ", Particle " << j << ": " << vec.transpose();
				else
					m_fluidBoundingBox.extend(vec);
			}
		}
	}
	m_fluidBoundingBox.extend(m_fluidBoundingBox.max() + m_particleRadius * Vector3f::Ones());
	m_fluidBoundingBox.extend(m_fluidBoundingBox.min() - m_particleRadius * Vector3f::Ones());
}

void PartioViewer::initGUI()
{
	m_gui->initParameterGUI();
}

void PartioViewer::setPlanePoint(const Eigen::Vector3f &val)
{
	m_planePoint = val; 
	if (m_usePlane)
		updateData();
}

void PartioViewer::setPlaneNormal(const Eigen::Vector3f &val)
{
	m_planeNormal = val;
	if (m_usePlane)
		updateData();
}

void PartioViewer::setUsePlane(bool val)
{
	m_usePlane = val;
	if (m_usePlane)
		updateData();
}

void SPH::PartioViewer::reset()
{
	setFrame(m_firstRBIndex);
}

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