mirror of https://github.com/CGAL/cgal
475 lines
12 KiB
C++
475 lines
12 KiB
C++
#include <CGAL/Simple_cartesian.h>
|
|
#include <iostream>
|
|
#include <fstream>
|
|
#include <sstream>
|
|
#include <algorithm>
|
|
|
|
#include <CGAL/radius_ratio.h>
|
|
#include <CGAL/min_dihedral_angle.h>
|
|
|
|
#ifndef CGAL_USE_QT
|
|
int main()
|
|
{
|
|
std::cerr << "This tool requires Qt.\n";
|
|
exit(1);
|
|
}
|
|
#else
|
|
#include <CGAL/IO/Qt_widget.h>
|
|
#include <qpainter.h>
|
|
#include <qapplication.h>
|
|
|
|
struct K : public CGAL::Simple_cartesian<double> {};
|
|
typedef K::Point_3 Point_3;
|
|
typedef K::Tetrahedron_3 Tetrahedron_3;
|
|
typedef K::Point_2 Point_2;
|
|
typedef K::Segment_2 Segment_2;
|
|
typedef K::Iso_rectangle_2 Rectangle_2;
|
|
|
|
/// Global variables
|
|
typedef std::map<std::string, std::string> String_options;
|
|
typedef std::map<std::string, double> Double_options;
|
|
|
|
String_options string_options;
|
|
Double_options double_options;
|
|
bool use_angle;
|
|
|
|
template <typename K>
|
|
typename K::FT
|
|
criterion(const typename K::Point_3& p0,
|
|
const typename K::Point_3& p1,
|
|
const typename K::Point_3& p2,
|
|
const typename K::Point_3& p3,
|
|
K k = K())
|
|
{
|
|
if(use_angle)
|
|
return CGAL::minimum_dihedral_angle(p0, p1, p2, p3, k);
|
|
else
|
|
return CGAL::radius_ratio(p0, p1, p2, p3, k);
|
|
}
|
|
|
|
void init_options()
|
|
{
|
|
string_options["tets"] = "";
|
|
string_options["noboite"] = "";
|
|
string_options["mesh"] = "";
|
|
string_options["cgal"] = "";
|
|
string_options["criterion"] = "RATIO";
|
|
double_options["scale"] = 1;
|
|
}
|
|
|
|
void usage(char *argv0, std::string error = "")
|
|
{
|
|
if( error != "" )
|
|
std:: cerr << "Error: " << error << std::endl;
|
|
std::cerr << "Usage:\n "
|
|
<< argv0
|
|
<< " [--scale x] (--tets|--noboite|--mesh) FILE\n"
|
|
<< "Options:\n"
|
|
<< " --scale SCALE "
|
|
<< "SCALE is a real number, which will be the\n"
|
|
<< " "
|
|
<< "the vertical scaling of the histogram.\n"
|
|
<< " --criterion ANGLE\n"
|
|
<< " --criterion RATIO "
|
|
<< "Choose between an min dihedral angle distribution\n"
|
|
<< " "
|
|
<< "or an aspect ratio distribution.\n"
|
|
<< " "
|
|
<< "Default: RATIO.\n"
|
|
<< " --tets FILE\n"
|
|
<< " --noboite FILE\n"
|
|
<< " --mesh FILE\n"
|
|
<< " --cgal FILE\n"
|
|
<< " "
|
|
<< "Read input file FILE.\n"
|
|
<< " "
|
|
<< "FILE must a .tets file, or a .noboite file,\n"
|
|
<< " a cgal file, or a .mesh file."
|
|
<< std::endl;
|
|
exit(1);
|
|
}
|
|
|
|
void parse_argv(int argc, char** argv, int extra_args = 0)
|
|
{
|
|
if (argc >=(2 + extra_args))
|
|
{
|
|
std::string arg = argv[1+extra_args];
|
|
if( arg.substr(0, 2) == "--" )
|
|
{
|
|
Double_options::iterator opt_it =
|
|
double_options.find(arg.substr(2, arg.length()-2));
|
|
if( opt_it != double_options.end() )
|
|
{
|
|
if( argc < (3 + extra_args) )
|
|
usage(argv[0],
|
|
(arg + " must be followed by a double!").c_str());
|
|
std::stringstream s;
|
|
double val;
|
|
s << argv[extra_args + 2];
|
|
s >> val;
|
|
if( !s )
|
|
usage(argv[0], ("Bad double after " + arg + "!").c_str());
|
|
opt_it->second = val;
|
|
parse_argv(argc, argv, extra_args + 2);
|
|
}
|
|
else
|
|
{
|
|
String_options::iterator opt_it =
|
|
string_options.find(arg.substr(2, arg.length()-2));
|
|
if( opt_it != string_options.end() )
|
|
{
|
|
if( argc < (3 + extra_args) )
|
|
usage(argv[0],
|
|
(arg + " must be followed by a string!").c_str());
|
|
std::string s = argv[extra_args + 2];
|
|
opt_it->second = s;
|
|
parse_argv(argc, argv, extra_args + 2);
|
|
}
|
|
else
|
|
usage(argv[0], ("Invalid option: " + arg + "!").c_str());
|
|
}
|
|
}
|
|
}
|
|
} // end parse_argv
|
|
|
|
void output_distribution_to_png(std::vector<double>& elements,
|
|
double max,
|
|
const int number_of_classes,
|
|
std::string filename)
|
|
{
|
|
const int number_of_cells = elements.size();
|
|
|
|
std::vector<int> distribution(number_of_classes);
|
|
|
|
for(int j=0;j<number_of_cells;j++)
|
|
{ // This block is (c) Pierre Alliez 2005
|
|
int index = number_of_classes-1;
|
|
// saturate highest value to last bin
|
|
|
|
if(elements[j] < max)
|
|
{
|
|
double dratio = elements[j]/max;
|
|
index = static_cast<int>(dratio*(double)number_of_classes);
|
|
}
|
|
distribution[index]++;
|
|
}
|
|
|
|
// const int max_occurrence = *std::max_element(distribution.begin(),
|
|
// distribution.end());
|
|
|
|
CGAL::Qt_widget *widget = new CGAL::Qt_widget();
|
|
qApp->setMainWidget(widget);
|
|
widget->resize(400, 400);
|
|
widget->set_window(0, 1, 0, 1, true); // x_min, x_max,
|
|
// y_min, y_max.
|
|
widget->show();
|
|
|
|
widget->lock();
|
|
*widget << CGAL::FillColor(CGAL::Color(200, 200, 200))
|
|
<< CGAL::Color(200, 200, 200)
|
|
<< Rectangle_2(Point_2(0, 0), Point_2(1,1));
|
|
|
|
if( number_of_classes == 0 ) return;
|
|
const double width = 1.0 / number_of_classes;
|
|
|
|
const double scale = double_options["scale"];
|
|
|
|
*widget << CGAL::FillColor(CGAL::black());
|
|
// *widget << Segment_2(Point_2(0., 0.), Point_2(1., 0.));
|
|
for(int k=0;k<number_of_classes;k++)
|
|
if(distribution[k]>0)
|
|
{
|
|
double height;
|
|
if(scale>0)
|
|
height = ( (distribution[k]+0.)/number_of_cells ) * scale;
|
|
else
|
|
height = ( std::log(distribution[k]+0.)/std::log(number_of_cells) ) * (-scale);
|
|
*widget << CGAL::black()
|
|
<< Rectangle_2(Point_2(k*width, 0),
|
|
Point_2((k+1)*width, height));
|
|
}
|
|
else
|
|
*widget << CGAL::red() << Segment_2(Point_2(k*width, 0),
|
|
Point_2((k+1)*width, 0));
|
|
|
|
widget->unlock();
|
|
if( widget->get_pixmap().save( QString(filename.c_str()),
|
|
"PNG") )
|
|
std::cerr << "Distribution saved to file " << filename
|
|
<< std::endl;
|
|
else
|
|
{
|
|
std::cerr << "Error: cannot save distribution to file "
|
|
<< filename << std::endl;
|
|
exit(1);
|
|
}
|
|
qApp->exec();
|
|
}
|
|
|
|
bool failed(const char* error)
|
|
{
|
|
std::cerr << error << std::endl;
|
|
return false;
|
|
}
|
|
|
|
bool read_tets(std::vector<double>& elements, std::istream& in)
|
|
{
|
|
// read header
|
|
int nb_vertices = 0;
|
|
int nb_tets = 0;
|
|
std::string head;
|
|
|
|
in >> nb_vertices >> head;
|
|
if( !in || head != "vertices" )
|
|
return false;
|
|
|
|
in >> nb_tets >> head;
|
|
if( !in || head != "tets" )
|
|
return false;
|
|
|
|
std::vector<Point_3> points;
|
|
points.reserve(nb_vertices);
|
|
|
|
// read points
|
|
for(int i=0;i<nb_vertices;i++)
|
|
{
|
|
float x,y,z;
|
|
in >> x >> y >> z;
|
|
if( !in )
|
|
return false;
|
|
points.push_back(Point_3(x,y,z));
|
|
}
|
|
|
|
// read tets
|
|
for(int i=0;i<nb_tets;i++)
|
|
{
|
|
int dummy, i0,i1,i2,i3;
|
|
in >> dummy >> i0 >> i1 >> i2 >> i3;
|
|
if( dummy != 4 || !in )
|
|
return false;
|
|
elements.push_back(criterion(points[i0],
|
|
points[i1],
|
|
points[i2],
|
|
points[i3],
|
|
K()));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool read_mesh(std::vector<double>& elements, std::istream& in)
|
|
{
|
|
// Header.
|
|
std::string head;
|
|
int version;
|
|
in >> head >> version;
|
|
if( head != "MeshVersionFormatted" ||
|
|
version != 1 ||
|
|
! in)
|
|
return failed("read_mesh: bad version");
|
|
|
|
int dimension;
|
|
in >> head >> dimension;
|
|
if( head != "Dimension" ||
|
|
dimension!= 3 ||
|
|
! in)
|
|
return failed("read_mesh: bad dimension");
|
|
|
|
// Vertices
|
|
int number_of_vertices;
|
|
in >> head >> number_of_vertices;
|
|
if( head != "Vertices" || ! in )
|
|
return failed("read_mesh: bad file (missing Vertices)");
|
|
std::vector<Point_3> points;
|
|
points.reserve(number_of_vertices);
|
|
for(int i = 0; i < number_of_vertices; ++i)
|
|
{
|
|
int dummy_i;
|
|
in >> points[i] >> dummy_i;
|
|
if( !in )
|
|
return failed("read_mesh: bad file (reading of vertices)");
|
|
}
|
|
|
|
// Facets
|
|
int number_of_facets_on_surface;
|
|
in >> head >> number_of_facets_on_surface;
|
|
if( !in || head != "Triangles" )
|
|
return failed("read_mesh: bad file (missing Triangles)");
|
|
for(int i = 0; i < 4 * number_of_facets_on_surface; ++i)
|
|
{
|
|
double dummy;
|
|
in >> dummy;
|
|
}
|
|
|
|
// Tetrahedra
|
|
int number_of_cells;
|
|
in >> head >> number_of_cells;
|
|
if( !in || head != "Tetrahedra")
|
|
return failed("read_mesh: bad file (missing Tetrahedra)");
|
|
for(int i = 0; i < number_of_cells; ++i)
|
|
{
|
|
int i0, i1, i2, i3, dummy;
|
|
in >> i0 >> i1 >> i2 >> i3 >> dummy;
|
|
if( !in )
|
|
return failed("read_mesh: bad file (reading of cells)");
|
|
elements.push_back(criterion(points[i0-1],
|
|
points[i1-1],
|
|
points[i2-1],
|
|
points[i3-1],
|
|
K()));
|
|
}
|
|
in >> head;
|
|
if ( !in || head != "End")
|
|
return failed("read_mesh: bad file (missing End)");
|
|
else
|
|
return true;
|
|
}
|
|
|
|
bool read_noboite(std::vector<double>& elements, std::istream& in)
|
|
{
|
|
int nb_vertices = 0;
|
|
int nb_tets = 0;
|
|
int nb_input_points;
|
|
int dummy;
|
|
|
|
in >> nb_tets >> nb_vertices >> nb_input_points
|
|
>> dummy
|
|
>> dummy
|
|
>> dummy
|
|
>> dummy
|
|
>> dummy
|
|
>> dummy
|
|
>> dummy
|
|
>> dummy
|
|
>> dummy
|
|
>> dummy
|
|
>> dummy
|
|
>> dummy
|
|
>> dummy
|
|
>> dummy;
|
|
|
|
if( !in )
|
|
return false;
|
|
|
|
std::vector<Point_3> points;
|
|
points.reserve(nb_vertices);
|
|
|
|
elements.clear();
|
|
elements.reserve(nb_tets);
|
|
|
|
// read tets
|
|
std::vector<int> tets;
|
|
tets.reserve(4 * nb_tets);
|
|
for(int i=0;i<nb_tets;i++)
|
|
{
|
|
int i0,i1,i2,i3;
|
|
in >> i0 >> i1 >> i2 >> i3;
|
|
if( !in )
|
|
return false;
|
|
tets.push_back(i0-1);
|
|
tets.push_back(i1-1);
|
|
tets.push_back(i2-1);
|
|
tets.push_back(i3-1);
|
|
}
|
|
|
|
|
|
// read points
|
|
for(int i=0;i<nb_vertices;i++)
|
|
{
|
|
double x,y,z;
|
|
in >> x >> y >> z;
|
|
if( !in )
|
|
return false;
|
|
points.push_back(Point_3(x,y,z));
|
|
}
|
|
|
|
// compute elements
|
|
for(int i = 0; i < 4 * nb_tets; i += 4)
|
|
{
|
|
elements.push_back(criterion(points[tets[i]],
|
|
points[tets[i+1]],
|
|
points[tets[i+2]],
|
|
points[tets[i+3]],
|
|
K()));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int main(int argc, char** argv)
|
|
{
|
|
QApplication app(argc, argv);
|
|
init_options();
|
|
parse_argv(argc, argv, 0);
|
|
|
|
if(string_options["criterion"] == "ANGLE")
|
|
use_angle = true;
|
|
else if(string_options["criterion"] == "RATIO")
|
|
use_angle = false;
|
|
else
|
|
usage(argv[0], "--criterion must be followed by ANGLE or RATIO.");
|
|
|
|
|
|
bool ghs = false;
|
|
bool tets = false;
|
|
bool mesh = false;
|
|
bool cgal = false;
|
|
std::string filename = "";
|
|
std::string ghs_filename = string_options["noboite"];
|
|
std::string tets_filename = string_options["tets"];
|
|
std::string mesh_filename = string_options["mesh"];
|
|
std::string cgal_filename = string_options["cgal"];
|
|
|
|
if(ghs_filename != "")
|
|
{
|
|
ghs = true;
|
|
filename = ghs_filename;
|
|
}
|
|
if(mesh_filename != "")
|
|
{
|
|
mesh = true;
|
|
filename = mesh_filename;
|
|
}
|
|
if(cgal_filename != "")
|
|
{
|
|
cgal = true;
|
|
filename = cgal_filename;
|
|
}
|
|
if(tets_filename != "")
|
|
{
|
|
tets = true;
|
|
filename = tets_filename;
|
|
}
|
|
|
|
std::vector<double> elements;
|
|
std::ifstream in_file(filename.c_str());
|
|
if(tets)
|
|
tets = read_tets(elements, in_file);
|
|
if(mesh)
|
|
mesh = read_mesh(elements, in_file);
|
|
if(cgal)
|
|
mesh = read_mesh(elements, in_file);
|
|
if(ghs)
|
|
ghs = read_noboite(elements, in_file);
|
|
std::stringstream png_name;
|
|
png_name << filename << "-scale-" << double_options["scale"];
|
|
if(use_angle)
|
|
png_name << "-angles";
|
|
else
|
|
png_name << "-ratios";
|
|
png_name << ".png";
|
|
|
|
std::cout << "min: " << *std::min_element(elements.begin(), elements.end())
|
|
<< "\nmax: " << *std::max_element(elements.begin(), elements.end())
|
|
<< "\n";
|
|
|
|
if(tets || mesh || ghs)
|
|
{
|
|
if(use_angle)
|
|
output_distribution_to_png(elements, 90., 100, png_name.str());
|
|
else
|
|
output_distribution_to_png(elements, 1., 100, png_name.str());
|
|
}
|
|
else
|
|
usage(argv[0], "cannot read file " + filename + "!");
|
|
}
|
|
#endif // CGAL_USE_QT
|