Commit 17b97b51 authored by Jakub Klinkovský's avatar Jakub Klinkovský
Browse files

added polyhedral generator based on cinolib

parent bf89cba3
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -56,3 +56,10 @@ if( ZLIB_FOUND )
    target_include_directories(cgal-mesher PUBLIC ${ZLIB_INCLUDE_DIRS})
    target_link_libraries(cgal-mesher PUBLIC ${ZLIB_LIBRARIES})
endif()

# polyhedral-generator (based on cinolib; needs VTK for input)
set(cinolib_DIR "${CMAKE_SOURCE_DIR}/libs/cinolib")
set(CINOLIB_USES_VTK ON)
find_package(cinolib REQUIRED)
add_executable(polyhedral-generator polyhedral-generator.cpp)
target_link_libraries(polyhedral-generator cinolib)
+223 −0
Original line number Diff line number Diff line
#include <filesystem>
#include <string>
#include <sstream>

#include <cinolib/meshes/tetmesh.h>
#include <cinolib/dual_mesh.h>

#include "lyra/lyra.hpp"

template <typename T>
std::string fmt_default(T value)
{
	std::stringstream str;
	str << " Default value: " << value << ".";
	return str.str();
}

struct Parameters
{
	// pipeline options
	bool detect_features = false;
	double features_angle_bound = 60;

	void set_cli_options(lyra::cli & cli)
	{
		// pipeline options
		cli |= lyra::opt(detect_features)
				["-f"]["--detect-features"]
				("Before dual mesh generation, detect sharp features on the primal "
				 "mesh surface and preserve them on the dual mesh."
				 + fmt_default(detect_features));
		cli |= lyra::opt(features_angle_bound, "SCALAR")
				["--features-angle-bound"]
				("The maximum angle (in degrees) between the two normal vectors "
				 "of adjacent triangles. For an edge of the mesh, if the angle "
				 "between the two normal vectors of its incident facets is bigger "
				 "than the given bound, then the edge is considered as a feature "
				 "edge." + fmt_default(features_angle_bound));
	}

	void print_options(std::ostream & str)
	{
		str << "Pipeline parameters:\n";
		str << "- Detect features: " << detect_features << "\n";
		if (detect_features)
			str << "- Features angle bound: " << features_angle_bound << "\n";
		str.flush();
	}
};

void write_FPMA(const char                           * filename,
                const std::vector<cinolib::vec3d>    & verts,
                const std::vector<std::vector<uint>> & faces,
                const std::vector<std::vector<uint>> & polys,
                const std::vector<std::vector<bool>> & polys_winding)
{
	setlocale(LC_NUMERIC, "en_US.UTF-8"); // makes sure "." is the decimal separator

	FILE *fp = fopen(filename, "w");

	if(!fp)
	{
		std::cerr << "ERROR : " << __FILE__ << ", line " << __LINE__ << " : save_HEDRA() : couldn't write output file " << filename << std::endl;
		exit(-1);
	}

	uint nv = verts.size();
	uint nf = faces.size();
	uint np = polys.size();

	// write vertices
	fprintf(fp, "%d\n", nv);

	// http://stackoverflow.com/questions/16839658/printf-width-specifier-to-maintain-precision-of-floating-point-value
	for(const cinolib::vec3d & v : verts) fprintf(fp, "%.17g %.17g %.17g\n", v.x(), v.y(), v.z());

	fprintf(fp, "\n");

	// write faces
	fprintf(fp, "%d\n", nf);

	for(const std::vector<uint> & f : faces)
	{
		fprintf(fp, "%d ", static_cast<int>(f.size()));
		for(uint vid : f) fprintf(fp, "%d ", vid);
		fprintf(fp, "\n");
	}

	fprintf(fp, "\n");

	// write cells
	fprintf(fp, "%d\n", np);

	for(uint pid=0; pid<np; ++pid)
	{
		fprintf(fp, "%d ", static_cast<int>(polys.at(pid).size()));
		for(uint off=0; off<polys.at(pid).size(); ++off)
			fprintf(fp, "%d ",   polys.at(pid).at(off));
		fprintf(fp, "\n");
	}

	fprintf(fp, "\n");

	fclose(fp);
}

class MyPolyhedralmesh : public cinolib::Polyhedralmesh<>
{
public:
	// hack to expose attributes that we need for FPMA export
	auto my_get_verts()
	{
		return this->verts;
	}
	auto my_get_faces()
	{
		return this->faces;
	}
	auto my_get_polys()
	{
		return this->polys;
	}
	auto my_get_polys_face_winding()
	{
		return this->polys_face_winding;
	}
};

void generateDual(
		const std::filesystem::path & input_file,
		const std::filesystem::path & output_file,
		const Parameters & args)
{
	// load the input mesh
	cinolib::Tetmesh<> m_tet;
	m_tet.load(input_file.string().c_str());

	// detect feature edges
	if (args.detect_features) {
		// convert degrees to radians
		const double angle_threshold = args.features_angle_bound * 3.1415926535897932384626433 / 180.;
		m_tet.edge_mark_sharp_creases(angle_threshold);
	}

	// make polyhedral (voronoi) mesh
	//cinolib::Polyhedralmesh<> m_poly;
	MyPolyhedralmesh m_poly;
	cinolib::dual_mesh(m_tet, m_poly, args.detect_features);

	// write the polyhedral mesh
	//m_poly.save("output.hedra");  // only .hedra is supported for output
	write_FPMA(output_file.string().c_str(), m_poly.my_get_verts(), m_poly.my_get_faces(), m_poly.my_get_polys(), m_poly.my_get_polys_face_winding());
}

int main(int argc, char * argv[])
{
	Parameters args;
	std::filesystem::path input_file;
	std::filesystem::path output_path = ".";
	bool show_help = false;

	auto cli = lyra::cli();
	cli |= lyra::help(show_help).description("polyhedral mesh generator based on the cinolib library");

	args.set_cli_options(cli);

	// positional arguments (should be generally defined after optional arguments)
	cli |= lyra::arg(input_file, "input_file")
			("Input file of a tetrahedral mesh (supported formats: VTK, VTU, TET, MESH).").required();
	cli |= lyra::arg(output_path, "output_path")
			("Output path. If it is an existing directory, the stem of "
			 "input_file (i.e., base file name without suffix) is appended to "
			 "output_path. Otherwise, output_path denotes the output file and "
			 "it must end with the \".fpma\" suffix." + fmt_default(output_path));

	auto result = cli.parse({ argc, argv });

	// check that the arguments where valid
	if (!result) {
		std::cerr << "Error in command line: " << result.errorMessage() << std::endl;
		std::cerr << cli << std::endl;
		return EXIT_FAILURE;
	}

	// show the help when asked for.
	if (show_help) {
		std::cout << cli << std::endl;
		return EXIT_SUCCESS;
	}

	// check if the input file exists
	if (!std::filesystem::exists(input_file)) {
		std::cerr << "Error: input_file \"" + input_file.string() + "\" does not exist." << std::endl;
		return EXIT_FAILURE;
	}

	// check if output_path is an existing directory
	if (std::filesystem::is_directory(output_path))
		output_path /= (input_file.stem().string() + ".fpma");
	// ensure that output_path ends with ".fpma"
	else if (output_path.extension() != ".fpma") {
		std::cerr << "Error: output_path \"" + output_path.string() + "\" does not end with \".fpma\"." << std::endl;
		return EXIT_FAILURE;
	}

	std::cout << "Input file: " << input_file << "\n";
	std::cout << "Output file: " << output_path << "\n";
	args.print_options(std::cout);

	try {
		generateDual(input_file, output_path, args);
	}
	catch (std::logic_error & e) {
		std::cerr << "Logic error: " << e.what() << std::endl;
		return EXIT_FAILURE;
	}
	catch (std::runtime_error & e) {
		std::cerr << "Runtime error: " << e.what() << std::endl;
		return EXIT_FAILURE;
	}

	return EXIT_SUCCESS;
}