You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1140 lines
35 KiB
1140 lines
35 KiB
#include <iostream>
|
|
#include <fstream>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <set>
|
|
#include <random>
|
|
#include <algorithm>
|
|
#include "cxxopts.hpp"
|
|
#include "Mesh3D.h"
|
|
#include "helper.h"
|
|
#include "happly.h"
|
|
#include "Tree.h"
|
|
|
|
using namespace MeshLib;
|
|
|
|
#define MIN_PATCH_AREA 1e-4
|
|
|
|
|
|
std::string GetFileExtension(const std::string& FileName)
|
|
{
|
|
if (FileName.find_last_of(".") != std::string::npos)
|
|
return FileName.substr(FileName.find_last_of(".") + 1);
|
|
return "";
|
|
}
|
|
|
|
bool load_feature_file(const char* filename, std::vector<std::pair<int, int>>& ungrouped_feature)
|
|
{
|
|
ungrouped_feature.clear();
|
|
std::ifstream ifs(filename);
|
|
if (!ifs.is_open())
|
|
{
|
|
std::cout << "Cannot Open Input Feature File" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
//fea file: first part
|
|
int pair_size = 0;
|
|
ifs >> pair_size;
|
|
std::pair<int, int> tmp_pair(-1, -1);
|
|
for (size_t i = 0; i < pair_size; i++)
|
|
{
|
|
ifs >> tmp_pair.first >> tmp_pair.second;
|
|
ungrouped_feature.push_back(tmp_pair);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void save_feature_file(const char* filename, const std::vector<std::pair<int, int>>& ungrouped_feature)
|
|
{
|
|
std::ofstream ofs(filename);
|
|
ofs << ungrouped_feature.size() << std::endl;
|
|
for (size_t i = 0; i < ungrouped_feature.size(); i++)
|
|
{
|
|
ofs << ungrouped_feature[i].first << " " << ungrouped_feature[i].second << std::endl;
|
|
}
|
|
|
|
ofs.close();
|
|
}
|
|
|
|
void save_conf_file(const char* filename, const std::string str, bool flag_convex = true)
|
|
{
|
|
std::ofstream ofs(filename);
|
|
ofs << "csg{\n list = ";
|
|
ofs << str << std::endl;
|
|
ofs << " flag_convex = " << int(flag_convex) << "," << std::endl;
|
|
ofs << "}";
|
|
|
|
ofs.close();
|
|
}
|
|
|
|
int check_mesh_edge_convex(Mesh3d* m, HE_edge<double>* he)
|
|
{
|
|
//return 0: smooth, 1: convex, 2: concave
|
|
int hetri_vertidsum = 0, hepairtri_vertidsum = 0;
|
|
|
|
if (m->is_on_boundary(he))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
HE_edge<double>* begin_edge = he;
|
|
HE_edge<double>* edge = he;
|
|
do
|
|
{
|
|
hetri_vertidsum += edge->vert->id;
|
|
edge = edge->next;
|
|
} while (edge != begin_edge);
|
|
|
|
begin_edge = he->pair;
|
|
edge = he->pair;
|
|
do
|
|
{
|
|
hepairtri_vertidsum += edge->vert->id;
|
|
edge = edge->next;
|
|
} while (edge != begin_edge);
|
|
|
|
int hetri_otherid = hetri_vertidsum - he->vert->id - he->pair->vert->id;
|
|
int hepairtri_otherid = hepairtri_vertidsum - he->vert->id - he->pair->vert->id;
|
|
double product = he->face->normal.Dot(m->get_vertices_list()->at(hetri_otherid)->pos - m->get_vertices_list()->at(hepairtri_otherid)->pos);
|
|
double face_cos_value = he->face->normal.Dot(he->pair->face->normal);
|
|
int res = 0;
|
|
if (product > 0.0)
|
|
{
|
|
if (face_cos_value < th_smooth_cos_value)
|
|
res = 1;
|
|
//return true;
|
|
}
|
|
else
|
|
{
|
|
if (face_cos_value < th_smooth_cos_value)
|
|
res = 2;
|
|
//return false;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
int main(int argc, char** argv)
|
|
{
|
|
try
|
|
{
|
|
cxxopts::Options options("FeaturedModelPointSample", "Point Sampling program for featured CAD models (author: Haoxiang Guo, Email: guohaoxiangxiang@gmail.com)");
|
|
options
|
|
.positional_help("[optional args]")
|
|
.show_positional_help()
|
|
.allow_unrecognised_options()
|
|
.add_options()
|
|
("i,input", "input mesh (obj/off format)", cxxopts::value<std::string>())
|
|
("f,feature", "input feature file (fea format)", cxxopts::value<std::string>())
|
|
("p,pointcloud", "input pointcloud", cxxopts::value<std::string>())
|
|
("pf", "face of input pointcloud", cxxopts::value<std::string>())
|
|
("o,output", "output mesh/points (obj/off/points/xyz format)", cxxopts::value<std::string>())
|
|
("k,mask", "output mask file (txt format)", cxxopts::value<std::string>())
|
|
("fs", "number of samples on feature edges(default: 10000)", cxxopts::value<int>())
|
|
("ns", "number of samples on non-feature faces(default: 40000)", cxxopts::value<int>())
|
|
("m,mode", "processing mode: 0 for normalization and 1 for feature sample", cxxopts::value<int>())
|
|
("c,color", "whether coloring is used, 0: not used, 1: used, default: 0", cxxopts::value<int>())
|
|
("mp", "maximum number of patches in each colored cluster, only work for csg, default -1(no upper bound)", cxxopts::value<int>())
|
|
("cot", "whether cotangent weight is used for sampling, 0: not used, 1: used, default: 0", cxxopts::value<int>())
|
|
("s,sigma", "sigma for noisy points position, default 0.0", cxxopts::value<double>())
|
|
("sn", "sigma for noisy points normal in degrees, default 0.0", cxxopts::value<double>())
|
|
("csg", "whether generating csg tree for model, default: 0", cxxopts::value<int>())
|
|
("convex", "whether the first layer is convex, default: 0", cxxopts::value<int>())
|
|
("r,repairturn", "whether the turn vertex are repaired, default: 1", cxxopts::value<int>())
|
|
("verbose", "verbose setting, default: 0", cxxopts::value<int>())
|
|
("strict", "treat all edges as either strictly convex or concave")
|
|
("repairtree", "repair tree feature")
|
|
("h,help", "print help");
|
|
|
|
auto result = options.parse(argc, argv);
|
|
if (result.count("help"))
|
|
{
|
|
std::cout << options.help({ "", "Group" }) << std::endl;
|
|
exit(0);
|
|
}
|
|
int n_nonfeature_sample = 50000;
|
|
int n_feature_sample = 0;
|
|
int min_sample_perpatch = 50;
|
|
double sigma = -1.0;
|
|
double sigma_n = -1.0;
|
|
assert(result.count("m"));
|
|
int processing_mode = result["m"].as<int>();
|
|
assert(result.count("i") && result.count("o"));
|
|
auto& inputfile = result["i"].as<std::string>();
|
|
auto& outputfile = result["o"].as<std::string>();
|
|
//output pts by colors
|
|
int last_dot = (int)outputfile.find_last_of(".");
|
|
auto output_prefix = outputfile.substr(0, last_dot);
|
|
int flag_csg = 0;
|
|
|
|
bool flag_sample_pts = true; //simply generate mask and csg tree of a given point cloud
|
|
if (result.count("p"))
|
|
{
|
|
flag_sample_pts = false;
|
|
}
|
|
|
|
std::string inputext = GetFileExtension(inputfile);
|
|
Mesh3d mesh;
|
|
if (inputext == "obj")
|
|
mesh.load_obj(inputfile.c_str());
|
|
else if (inputext == "off")
|
|
mesh.load_off(inputfile.c_str());
|
|
std::cout << "verts: " << mesh.get_vertices_list()->size() << " face: " << mesh.get_faces_list()->size() << std::endl;
|
|
bool flag_verbose = false;
|
|
if (result.count("verbose"))
|
|
{
|
|
flag_verbose = (bool)result["verbose"].as<int>();
|
|
}
|
|
|
|
bool flag_strict = false;
|
|
if (result.count("strict"))
|
|
{
|
|
flag_strict = true;
|
|
}
|
|
|
|
if (processing_mode == 0)
|
|
{
|
|
//normalization part begin
|
|
//[-0.9, 0.9]^3
|
|
std::vector<TinyVector<double, 3>> pts_nl(mesh.get_vertices_list()->size());
|
|
double max_range = mesh.xmax - mesh.xmin;
|
|
max_range = max_range < (mesh.ymax - mesh.ymin) ? (mesh.ymax - mesh.ymin) : max_range;
|
|
max_range = max_range < (mesh.zmax - mesh.zmin) ? (mesh.zmax - mesh.zmin) : max_range;
|
|
|
|
double xcenter = (mesh.xmin + mesh.xmax) / 2;
|
|
double ycenter = (mesh.ymin + mesh.ymax) / 2;
|
|
double zcenter = (mesh.zmin + mesh.zmax) / 2;
|
|
std::cout << "center " << xcenter << " " << ycenter << " " << zcenter << std::endl;
|
|
|
|
for (size_t i = 0; i < mesh.get_vertices_list()->size(); i++)
|
|
{
|
|
mesh.get_vertices_list()->at(i)->pos[0] = (mesh.get_vertices_list()->at(i)->pos[0] - xcenter) / max_range * 1.8;
|
|
mesh.get_vertices_list()->at(i)->pos[1] = (mesh.get_vertices_list()->at(i)->pos[1] - ycenter) / max_range * 1.8;
|
|
mesh.get_vertices_list()->at(i)->pos[2] = (mesh.get_vertices_list()->at(i)->pos[2] - zcenter) / max_range * 1.8;
|
|
}
|
|
|
|
//output mesh
|
|
std::string outputext = GetFileExtension(outputfile);
|
|
if (outputext == "obj")
|
|
{
|
|
mesh.write_obj(outputfile.c_str());
|
|
|
|
}
|
|
else if (outputext == "off")
|
|
{
|
|
mesh.write_off(outputfile.c_str());
|
|
}
|
|
return 1;
|
|
}
|
|
else if (processing_mode == 1)
|
|
{
|
|
//first sample feature parts then non-feature parts
|
|
//mask:
|
|
//feature: 0
|
|
//non feature: 1,2,3...indicating coloring
|
|
assert(result.count("f") && result.count("k"));
|
|
auto& inputfeaturefile = result["f"].as<std::string>();
|
|
auto& outputmaskfile = result["k"].as<std::string>();
|
|
if (result.count("fs"))
|
|
n_feature_sample = result["fs"].as<int>();
|
|
if (result.count("ns"))
|
|
n_nonfeature_sample = result["ns"].as<int>();
|
|
if (result.count("s"))
|
|
sigma = result["s"].as<double>();
|
|
if (result.count("sn"))
|
|
sigma_n = result["sn"].as<double>();
|
|
bool flag_repair_turn_features = true;
|
|
if (result.count("r"))
|
|
flag_repair_turn_features = result["r"].as<int>();
|
|
|
|
bool flag_repair_tree_features = false;
|
|
if (result.count("repairtree"))
|
|
{
|
|
flag_repair_tree_features = true;
|
|
}
|
|
|
|
|
|
if (result.count("csg"))
|
|
flag_csg = result["csg"].as<int>();
|
|
|
|
bool flag_skip_hanging_features = false; //not skipping hanging features
|
|
std::vector<int> sample_mask;
|
|
std::vector<std::pair<int, int>> ungrouped_features;
|
|
load_feature_file(inputfeaturefile.c_str(), ungrouped_features);
|
|
std::vector<TinyVector<double, 3>> sample_pts, sample_pt_normals;
|
|
std::vector<size_t> sample_pts_tris; //used for assign labels of sample pts
|
|
if (!flag_sample_pts)
|
|
{
|
|
auto& inputpcfile = result["p"].as<std::string>();
|
|
load_xyz_file(inputpcfile.c_str(), sample_pts, sample_pt_normals);
|
|
|
|
sample_pts_tris.resize(sample_pts.size(), 0);
|
|
if (result.count("pf"))
|
|
{
|
|
auto& inputpffile = result["pf"].as<std::string>();
|
|
|
|
std::ifstream ifs(inputpffile);
|
|
for (size_t ii = 0; ii < sample_pts.size(); ii++)
|
|
{
|
|
ifs >> sample_pts_tris[ii];
|
|
}
|
|
|
|
ifs.close();
|
|
}
|
|
else
|
|
{
|
|
//search id by aabb
|
|
std::vector<std::array<double, 3>> tri_verts;
|
|
std::vector<std::vector<size_t>> tri_faces;
|
|
get_mesh_vert_faces(mesh, tri_verts, tri_faces);
|
|
Eigen::MatrixXd input_pts(tri_verts.size(), 3);
|
|
Eigen::MatrixXi input_faces(tri_faces.size(), 3);
|
|
for (size_t i = 0; i < tri_verts.size(); i++)
|
|
{
|
|
for (size_t j = 0; j < 3; j++)
|
|
{
|
|
input_pts(i, j) = tri_verts[i][j];
|
|
}
|
|
}
|
|
|
|
for (size_t i = 0; i < tri_faces.size(); i++)
|
|
{
|
|
for (size_t j = 0; j < 3; j++)
|
|
{
|
|
input_faces(i, j) = tri_faces[i][j];
|
|
}
|
|
}
|
|
|
|
|
|
std::vector<vec3d> closest;
|
|
std::vector<double> dist;
|
|
compute_shortest_dist_AABB(input_pts, input_faces, sample_pts, closest, dist, sample_pts_tris);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//skip elements with no features
|
|
if (flag_csg && ungrouped_features.empty())
|
|
{
|
|
std::cout << "empty feature file: " << inputfeaturefile << std::endl;
|
|
return 1;
|
|
}
|
|
|
|
//feature check: no hanging feature
|
|
std::vector<std::vector<int>> feature_v2he(mesh.get_num_of_vertices()); //id of hes ematating from each vertex
|
|
|
|
std::vector<std::pair<int, int>> ungrouped_features_new;
|
|
for (size_t i = 0; i < ungrouped_features.size(); i++)
|
|
{
|
|
int id0 = ungrouped_features[i].first;
|
|
int id1 = ungrouped_features[i].second;
|
|
|
|
HE_edge<double>* begin_edge = mesh.get_vertices_list()->at(id0)->edge;
|
|
HE_edge<double>* edge = mesh.get_vertices_list()->at(id0)->edge;
|
|
bool flag_found = false;
|
|
do
|
|
{
|
|
if (id1 == edge->vert->id)
|
|
{
|
|
feature_v2he[id0].push_back(edge->id);
|
|
feature_v2he[id1].push_back(edge->pair->id);
|
|
flag_found = true;
|
|
break;
|
|
}
|
|
edge = edge->pair->next;
|
|
} while (edge != begin_edge);
|
|
//assert(flag_found == true);
|
|
if (flag_found == true)
|
|
{
|
|
ungrouped_features_new.push_back(ungrouped_features[i]);
|
|
}
|
|
}
|
|
|
|
if (ungrouped_features.size() != ungrouped_features_new.size())
|
|
ungrouped_features = ungrouped_features_new;
|
|
|
|
std::vector<size_t> turn_verts, hanging_verts;
|
|
|
|
for (size_t i = 0; i < mesh.get_num_of_vertices(); i++)
|
|
{
|
|
if (feature_v2he[i].size() == 1)
|
|
{
|
|
std::cout << "input file: " << inputfile << std::endl;
|
|
std::cout << "hanging vertex exists: " << i << std::endl;
|
|
std::ofstream ofs(inputfile + "hanging");
|
|
ofs.close();
|
|
hanging_verts.push_back(i);
|
|
}
|
|
else if (feature_v2he[i].size() == 2)
|
|
{
|
|
//check edge convex status
|
|
int flag_convex_edge0 = check_mesh_edge_convex(&mesh, mesh.get_edges_list()->at(feature_v2he[i][0]));
|
|
int flag_convex_edge1 = check_mesh_edge_convex(&mesh, mesh.get_edges_list()->at(feature_v2he[i][1]));
|
|
if (flag_convex_edge0 * flag_convex_edge1 != 0 && flag_convex_edge0 != flag_convex_edge1)
|
|
{
|
|
std::ofstream ofs(inputfile + "turn");
|
|
ofs.close();
|
|
std::cout << "input file: " << inputfile << std::endl;
|
|
std::cout << "turn vertex exists: " << i << std::endl;
|
|
turn_verts.push_back(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
//do not handle hanging vertex
|
|
if (flag_skip_hanging_features && !hanging_verts.empty())
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
//feature parts first
|
|
std::random_device rd;
|
|
std::mt19937 e2(rd());
|
|
std::uniform_real_distribution<double> unif_dist(0, 1);
|
|
|
|
std::vector<double> feature_length(ungrouped_features.size(), 0.0);
|
|
double total_feature_length = 0.0;
|
|
for (size_t i = 0; i < ungrouped_features.size(); i++)
|
|
{
|
|
int id0 = ungrouped_features[i].first;
|
|
int id1 = ungrouped_features[i].second;
|
|
feature_length[i] = (mesh.get_vertices_list()->at(id0)->pos - mesh.get_vertices_list()->at(id1)->pos).Length();
|
|
total_feature_length += feature_length[i];
|
|
}
|
|
|
|
std::vector<double> line_bound(ungrouped_features.size() + 1, 0.0);
|
|
for (size_t i = 0; i < ungrouped_features.size(); i++)
|
|
{
|
|
line_bound[i + 1] = line_bound[i] + feature_length[i] / total_feature_length;
|
|
}
|
|
|
|
//sampling
|
|
if (flag_sample_pts)
|
|
{
|
|
for (size_t i = 0; i < n_feature_sample; i++)
|
|
{
|
|
double u = unif_dist(e2);
|
|
auto iter = std::upper_bound(line_bound.begin(), line_bound.end(), u);
|
|
int fid = (int)std::distance(line_bound.begin(), iter);
|
|
assert(fid != ungrouped_features.size() + 1);
|
|
fid = std::max(0, fid - 1);
|
|
//sample
|
|
int id0 = ungrouped_features[fid].first;
|
|
int id1 = ungrouped_features[fid].second;
|
|
double s = unif_dist(e2);
|
|
sample_pts.push_back(s * mesh.get_vertices_list()->at(id0)->pos + (1.0 - s) * mesh.get_vertices_list()->at(id1)->pos);
|
|
sample_pt_normals.push_back(TinyVector<double, 3>(1.0, 0.0, 0.0));
|
|
sample_mask.push_back(0);
|
|
}
|
|
}
|
|
std::vector<TinyVector<size_t, 3>> tri_verts(mesh.get_faces_list()->size());
|
|
std::vector<TinyVector<double, 3>> tri_normals;
|
|
//get tri list
|
|
for (size_t i = 0; i < mesh.get_faces_list()->size(); i++)
|
|
{
|
|
HE_edge<double>* begin_edge = mesh.get_faces_list()->at(i)->edge;
|
|
HE_edge<double>* edge = mesh.get_faces_list()->at(i)->edge;
|
|
int local_id = 0;
|
|
do
|
|
{
|
|
tri_verts[i][local_id++] = edge->pair->vert->id;
|
|
edge = edge->next;
|
|
} while (edge != begin_edge);
|
|
tri_normals.push_back(mesh.get_faces_list()->at(i)->normal);
|
|
}
|
|
std::vector<TinyVector<double, 3>> vert_pos;
|
|
for (size_t i = 0; i < mesh.get_vertices_list()->size(); i++)
|
|
{
|
|
vert_pos.push_back(mesh.get_vertices_list()->at(i)->pos);
|
|
}
|
|
|
|
//cluster faces: assuming the features are all close
|
|
std::vector<bool> he_feature_flag(mesh.get_edges_list()->size(), false);
|
|
for (size_t i = 0; i < ungrouped_features.size(); i++)
|
|
{
|
|
int id0 = ungrouped_features[i].first;
|
|
int id1 = ungrouped_features[i].second;
|
|
//iterate over all verts emanating from id0
|
|
HE_edge<double>* edge = mesh.get_vertices_list()->at(id0)->edge;
|
|
do
|
|
{
|
|
if (edge->vert->id == id1)
|
|
{
|
|
break;
|
|
}
|
|
edge = edge->pair->next;
|
|
} while (edge != mesh.get_vertices_list()->at(id0)->edge);
|
|
assert(edge->vert->id == id1);
|
|
he_feature_flag[edge->id] = true;
|
|
he_feature_flag[edge->pair->id] = true;
|
|
}
|
|
|
|
std::vector<std::vector<int>> grouped_features; //grouped features: only one he of a pair is stored
|
|
std::vector<int> he2gid;
|
|
get_grouped_edges(mesh, he_feature_flag, feature_v2he, grouped_features, he2gid);
|
|
if (flag_repair_turn_features && !turn_verts.empty())
|
|
{
|
|
//repair turning-point features
|
|
std::vector<bool> flag_feature_points(mesh.get_vertices_list()->size(), false);
|
|
for (size_t i = 0; i < he_feature_flag.size(); i++)
|
|
{
|
|
if (he_feature_flag[i])
|
|
{
|
|
flag_feature_points[mesh.get_edges_list()->at(i)->vert->id] = true;
|
|
}
|
|
}
|
|
|
|
//repair turn vertex
|
|
for (size_t i = 0; i < turn_verts.size(); i++)
|
|
{
|
|
size_t cur_vert = turn_verts[i];
|
|
std::vector<bool> flag_feature_points_tmp = flag_feature_points;
|
|
size_t cur_group = he2gid[feature_v2he[cur_vert][0]];
|
|
for (auto heid : grouped_features[cur_group])
|
|
{
|
|
flag_feature_points_tmp[mesh.get_edges_list()->at(heid)->vert->id] = false;
|
|
flag_feature_points_tmp[mesh.get_edges_list()->at(heid)->pair->vert->id] = false;
|
|
}
|
|
|
|
//distance from cur_vert to all other verts
|
|
std::vector<size_t> prev_map;
|
|
std::vector<double> dist;
|
|
dijkstra_mesh(&mesh, cur_vert, prev_map, dist);
|
|
|
|
std::set<std::pair<double, size_t>> dist_id_set;
|
|
for (size_t j = 0; j < flag_feature_points_tmp.size(); j++)
|
|
{
|
|
if (flag_feature_points_tmp[j])
|
|
{
|
|
dist_id_set.insert(std::pair<double, size_t>(dist[j], j));
|
|
}
|
|
}
|
|
|
|
for (auto& dist_id : dist_id_set)
|
|
{
|
|
size_t nnvid = dist_id.second;
|
|
bool flag_usable = true;
|
|
std::vector<HE_edge<double>*> tmp_hes;
|
|
//add path from nnvid to cur_vert
|
|
while (nnvid != cur_vert)
|
|
{
|
|
size_t prev_vert = prev_map[nnvid];
|
|
HE_edge<double>* begin_edge = mesh.get_vertices_list()->at(nnvid)->edge;
|
|
HE_edge<double>* edge = begin_edge;
|
|
do
|
|
{
|
|
if (edge->vert->id == prev_vert)
|
|
{
|
|
break;
|
|
}
|
|
edge = edge->pair->next;
|
|
} while (edge != begin_edge);
|
|
assert(edge->vert->id == prev_vert);
|
|
if (he_feature_flag[edge->id])
|
|
{
|
|
flag_usable = false;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
tmp_hes.push_back(edge);
|
|
}
|
|
|
|
nnvid = prev_vert;
|
|
}
|
|
if (flag_usable)
|
|
{
|
|
for (auto he : tmp_hes)
|
|
{
|
|
ungrouped_features.push_back(std::pair<int, int>(he->vert->id, he->pair->vert->id));
|
|
he_feature_flag[he->id] = true;
|
|
he_feature_flag[he->pair->id] = true;
|
|
feature_v2he[he->vert->id].push_back(he->pair->id);
|
|
feature_v2he[he->pair->vert->id].push_back(he->id);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
//save repaired features
|
|
save_feature_file((output_prefix + "_repairturn.fea").c_str(), ungrouped_features);
|
|
}
|
|
|
|
std::vector<int> face_color_init(mesh.get_faces_list()->size(), -1); //starting from 1
|
|
std::vector<std::vector<int>> face_clusters;
|
|
std::vector<std::pair<int, int>> feature_twoface_colors;//color of faces on two sides of the feature, *.first < *.second
|
|
|
|
int start_id = 0;
|
|
int cluster_id = 1;
|
|
while (start_id != -1)
|
|
{
|
|
std::vector<int> onecluster;
|
|
std::queue<int> q;
|
|
q.push(start_id);
|
|
face_color_init[start_id] = cluster_id;
|
|
while (!q.empty())
|
|
{
|
|
int front = q.front();
|
|
q.pop();
|
|
onecluster.push_back(front);
|
|
HE_edge<double>* edge = mesh.get_faces_list()->at(front)->edge;
|
|
do
|
|
{
|
|
if (he_feature_flag[edge->id] == false)
|
|
{
|
|
//not feature
|
|
int pair_fid = edge->pair->face->id;
|
|
if (face_color_init[pair_fid] == -1)
|
|
{
|
|
q.push(pair_fid);
|
|
face_color_init[pair_fid] = cluster_id;
|
|
}
|
|
}
|
|
|
|
edge = edge->next;
|
|
} while (edge != mesh.get_faces_list()->at(front)->edge);
|
|
|
|
}
|
|
|
|
face_clusters.push_back(onecluster);
|
|
start_id = -1;
|
|
//find next start_id
|
|
for (size_t i = 0; i < face_color_init.size(); i++)
|
|
{
|
|
if (face_color_init[i] == -1)
|
|
{
|
|
start_id = i;
|
|
break;
|
|
}
|
|
}
|
|
cluster_id++;
|
|
}
|
|
std::vector<int> face_color = face_color_init; //starting from 1
|
|
//check face area
|
|
std::vector<double> tri_areas;
|
|
get_all_tri_area(mesh, tri_areas);
|
|
std::vector<double> patch_areas(cluster_id, 0.0);
|
|
for (size_t i = 0; i < face_color.size(); i++)
|
|
{
|
|
patch_areas[face_color[i]] += tri_areas[i];
|
|
}
|
|
|
|
|
|
if (*std::min_element(patch_areas.begin() + 1, patch_areas.end()) < MIN_PATCH_AREA)
|
|
{
|
|
std::cout << "too small patch area : " << *std::min_element(patch_areas.begin() + 1, patch_areas.end()) << std::endl;
|
|
std::ofstream ofs(output_prefix + "smallpatch");
|
|
ofs.close();
|
|
}
|
|
|
|
int n_color = cluster_id;
|
|
//coloring
|
|
bool flag_coloring = false;
|
|
if (result.count("c"))
|
|
flag_coloring = result["c"].as<int>();
|
|
|
|
|
|
|
|
bool flag_first_convex = false;
|
|
if (result.count("convex"))
|
|
flag_first_convex = result["convex"].as<int>();
|
|
|
|
if (flag_coloring && !flag_csg)
|
|
{
|
|
std::vector<std::set<size_t>> connectivity(cluster_id - 1);
|
|
//color - 1
|
|
//here assume faces on both sides of a feature belongs to df patches
|
|
for (size_t i = 0; i < he_feature_flag.size(); i++)
|
|
{
|
|
if (he_feature_flag[i])
|
|
{
|
|
HE_edge<double>* e1 = mesh.get_edges_list()->at(i);
|
|
HE_edge<double>* e2 = e1->pair;
|
|
if (face_color_init[e1->face->id] == face_color_init[e2->face->id]) continue;
|
|
connectivity[face_color_init[e1->face->id] - 1].insert(face_color_init[e2->face->id] - 1);
|
|
}
|
|
}
|
|
|
|
//print graph
|
|
std::cout << "graph:" << std::endl;
|
|
for (size_t i = 0; i < connectivity.size(); i++)
|
|
{
|
|
std::cout << i + 1 << ": ";
|
|
for (auto v : connectivity[i])
|
|
{
|
|
std::cout << v + 1 << " ";
|
|
}
|
|
std::cout << std::endl;
|
|
}
|
|
|
|
std::vector<std::vector<size_t>> colored_vertices;
|
|
greedy_graph_coloring(cluster_id - 1, connectivity, colored_vertices);
|
|
std::cout << "number of colors: " << colored_vertices.size() << std::endl;
|
|
n_color = colored_vertices.size() + 1;
|
|
//update face_color
|
|
for (size_t i = 0; i < colored_vertices.size(); i++)
|
|
{
|
|
for (size_t j = 0; j < colored_vertices[i].size(); j++)
|
|
{
|
|
size_t local_id = colored_vertices[i][j];
|
|
for (size_t k = 0; k < face_clusters[local_id].size(); k++)
|
|
{
|
|
face_color[face_clusters[local_id][k]] = i + 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (flag_csg)
|
|
{
|
|
//coloring is based on vertices
|
|
std::vector<std::set<size_t>> connectivity(cluster_id - 1);
|
|
std::vector<std::set<size_t>> connectivity_v(cluster_id - 1); //connectivity based on vertices
|
|
std::map<std::pair<size_t, size_t>, double> fp2product;
|
|
|
|
//construct csg tree by voting
|
|
std::map<std::pair<size_t, size_t>, int> flag_fpconvex; //0: smooth, 1: convex, 2:concave
|
|
//color - 1
|
|
|
|
//face pair convexity is determined by grouped edges
|
|
std::vector<std::array<int, 3>> ge2count(grouped_features.size(), std::array<int, 3>{0, 0, 0});
|
|
std::map<std::pair<size_t, size_t>, std::set<int>> fp2ge; //face pair to grouped edges
|
|
for (size_t i = 0; i < he_feature_flag.size(); i++)
|
|
{
|
|
if (he_feature_flag[i])
|
|
{
|
|
HE_edge<double>* e1 = mesh.get_edges_list()->at(i);
|
|
HE_edge<double>* e2 = e1->pair;
|
|
if (face_color_init[e1->face->id] != face_color_init[e2->face->id])
|
|
connectivity[face_color_init[e1->face->id] - 1].insert(face_color_init[e2->face->id] - 1);
|
|
else
|
|
continue;
|
|
size_t fid1 = face_color_init[e1->face->id], fid2 = face_color_init[e2->face->id]; //starting from zero
|
|
size_t minfid = std::min(fid1, fid2);
|
|
size_t maxfid = std::max(fid1, fid2);
|
|
|
|
//triangle face
|
|
size_t tfid1 = e1->face->id, tfid2 = e2->face->id;
|
|
size_t ev1 = e1->vert->id, ev2 = e2->vert->id;
|
|
size_t tv1 = tri_verts[tfid1][0] + tri_verts[tfid1][1] + tri_verts[tfid1][2] - ev1 - ev2;
|
|
size_t tv2 = tri_verts[tfid2][0] + tri_verts[tfid2][1] + tri_verts[tfid2][2] - ev1 - ev2;
|
|
double product = e1->face->normal.Dot(vert_pos[tv2] - vert_pos[tv1]);
|
|
|
|
std::pair<size_t, size_t> tmp_pair(minfid - 1, maxfid - 1);
|
|
auto it = fp2product.find(tmp_pair);
|
|
if (it == fp2product.end())
|
|
{
|
|
fp2product[tmp_pair] = product;
|
|
}
|
|
else
|
|
{
|
|
fp2product[tmp_pair] += product;
|
|
}
|
|
|
|
double tmp_cos = e1->face->normal.Dot(e2->face->normal);
|
|
|
|
int gid = he2gid[i];
|
|
if (fp2ge.find(tmp_pair) == fp2ge.end())
|
|
{
|
|
fp2ge[tmp_pair] = std::set<int>();
|
|
}
|
|
fp2ge[tmp_pair].insert(gid);
|
|
|
|
if (product < 0.0)
|
|
{
|
|
//convex
|
|
if (tmp_cos < th_smooth_cos_value)
|
|
{
|
|
ge2count[gid][1]++;
|
|
}
|
|
else
|
|
{
|
|
ge2count[gid][0]++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//concave
|
|
if (tmp_cos < th_smooth_cos_value)
|
|
{
|
|
ge2count[gid][2]++;
|
|
}
|
|
else
|
|
{
|
|
ge2count[gid][0]++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//init connectivity_v
|
|
connectivity_v = connectivity;
|
|
for (size_t i = 0; i < feature_v2he.size(); i++)
|
|
{
|
|
if (feature_v2he[i].size() > 2)
|
|
{
|
|
//vertex with degree larger than 3
|
|
HE_edge<double>* ve_begin = mesh.get_vertices_list()->at(i)->edge;
|
|
assert(ve_begin->pair->vert->id == i);
|
|
HE_edge<double>* ve_iter = ve_begin;
|
|
std::set<size_t> surounding_cs;
|
|
do
|
|
{
|
|
surounding_cs.insert(face_color_init[ve_iter->face->id] -1);
|
|
ve_iter = ve_iter->pair->next;
|
|
} while (ve_iter != ve_begin);
|
|
std::vector<size_t> surounding_cs_vector(surounding_cs.begin(), surounding_cs.end());
|
|
for (size_t it = 0; it < surounding_cs_vector.size() - 1; it++)
|
|
{
|
|
size_t cur_fid = surounding_cs_vector[it];
|
|
for (size_t it1 = it + 1; it1 < surounding_cs_vector.size(); it1++)
|
|
{
|
|
size_t n_fid = surounding_cs_vector[it1];
|
|
connectivity_v[cur_fid].insert(n_fid);
|
|
connectivity_v[n_fid].insert(cur_fid);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (flag_strict)
|
|
{
|
|
for (auto& p : fp2product)
|
|
{
|
|
if (p.second < 0.0)
|
|
{
|
|
flag_fpconvex[p.first] = 1;
|
|
}
|
|
else
|
|
{
|
|
flag_fpconvex[p.first] = 2;
|
|
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//get face pair connectivity by grouped edges
|
|
bool flag_valid_seg = true;
|
|
std::set<size_t> invalid_seg_patches;
|
|
std::vector<int> ge2convex(grouped_features.size(), 0);
|
|
for (size_t i = 0; i < grouped_features.size(); i++)
|
|
{
|
|
int maxid = -1, max_num = -1;
|
|
for (size_t ii = 0; ii < 3; ii++)
|
|
{
|
|
if (ge2count[i][ii] > max_num)
|
|
{
|
|
max_num = ge2count[i][ii];
|
|
maxid = ii;
|
|
}
|
|
}
|
|
ge2convex[i] = maxid;
|
|
}
|
|
|
|
for (auto& p : fp2ge)
|
|
{
|
|
std::set<int> cur_strict_convex_types;
|
|
|
|
for (auto gid : p.second)
|
|
{
|
|
if (ge2convex[gid] != 0)
|
|
cur_strict_convex_types.insert(ge2convex[gid]);
|
|
}
|
|
|
|
|
|
if (cur_strict_convex_types.size() >= 2)
|
|
{
|
|
flag_valid_seg = false;
|
|
invalid_seg_patches.insert(p.first.first);
|
|
invalid_seg_patches.insert(p.first.second);
|
|
}
|
|
else if (cur_strict_convex_types.size() == 0)
|
|
{
|
|
flag_fpconvex[p.first] = 0; //smooth
|
|
}
|
|
else
|
|
{
|
|
//only one left
|
|
flag_fpconvex[p.first] = *cur_strict_convex_types.begin();
|
|
}
|
|
}
|
|
|
|
if (!flag_valid_seg)
|
|
{
|
|
std::ofstream ofs(inputfile + "treefail");
|
|
ofs.close();
|
|
std::cout << "invalid segmentation " << output_prefix << std::endl;
|
|
repair_tree_features_maxflow(mesh, face_color, std::vector<size_t>(invalid_seg_patches.begin(), invalid_seg_patches.end()), ungrouped_features);
|
|
|
|
ofs.open(output_prefix + "_fixtree.fea");
|
|
ofs << ungrouped_features.size() << std::endl;
|
|
for (auto& pp : ungrouped_features)
|
|
{
|
|
ofs << pp.first << " " << pp.second << std::endl;
|
|
}
|
|
ofs.close();
|
|
mesh.write_obj((output_prefix + "_fixtree.obj").c_str());
|
|
|
|
return 1;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (flag_verbose)
|
|
{
|
|
std::cout << "dual graph ori:" << std::endl;
|
|
for (size_t i = 0; i < connectivity.size(); i++)
|
|
{
|
|
std::cout << i << ": ";
|
|
for (auto v : connectivity[i])
|
|
{
|
|
std::cout << v << " ";
|
|
}
|
|
std::cout << std::endl;
|
|
}
|
|
|
|
std::cout << "edge convex flag: " << std::endl;
|
|
for (auto& p : flag_fpconvex)
|
|
{
|
|
std::cout << "edge: " << p.first.first << "-" << p.first.second << " : " << p.second << std::endl;
|
|
}
|
|
}
|
|
//print graph
|
|
|
|
TreeNode<size_t> *tree = new TreeNode<size_t>;
|
|
;
|
|
bool flag_convex = true;
|
|
//convex flag is set to true by default, but if the model contains multiple component, then it is set to false
|
|
|
|
//if all features are concave, then flag_convex set to false
|
|
|
|
bool flag_with_convex = false;
|
|
|
|
for (const auto &it : flag_fpconvex)
|
|
{
|
|
if (it.second == 1)
|
|
{
|
|
flag_with_convex = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!flag_with_convex)
|
|
flag_convex = false;
|
|
|
|
std::vector<std::set<size_t>> components;
|
|
get_graph_component(connectivity, components);
|
|
|
|
bool flag_construct_tree = true;
|
|
std::vector<size_t> invalid_subgraph;
|
|
if (components.size() == 1)
|
|
{
|
|
flag_construct_tree = get_tree_from_convex_graph(connectivity, flag_fpconvex, flag_convex, tree, 0, invalid_subgraph);
|
|
|
|
//try with !flag_convex root node, no longer used.
|
|
/*if (!flag_construct_tree)
|
|
{
|
|
delete tree;
|
|
tree = new TreeNode<size_t>;
|
|
invalid_subgraph.clear();
|
|
flag_construct_tree = get_tree_from_convex_graph(connectivity, flag_fpconvex, !flag_convex, tree, 0, invalid_subgraph);
|
|
|
|
}*/
|
|
}
|
|
|
|
if (!flag_construct_tree)
|
|
{
|
|
std::ofstream ofs(inputfile + "treefail");
|
|
ofs.close();
|
|
std::cout << "tree construction failed for model " << inputfile << std::endl;
|
|
//failure case
|
|
if (flag_repair_tree_features) //to check for all feature
|
|
{
|
|
repair_tree_features_maxflow(mesh, face_color, invalid_subgraph, ungrouped_features);
|
|
ofs.open(output_prefix + "_fixtree.fea");
|
|
ofs << ungrouped_features.size() << std::endl;
|
|
for (auto& pp : ungrouped_features)
|
|
{
|
|
ofs << pp.first << " " << pp.second << std::endl;
|
|
}
|
|
ofs.close();
|
|
|
|
mesh.write_obj((output_prefix + "_fixtree.obj").c_str());
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
std::cout << "convex status: " << flag_convex << std::endl;
|
|
|
|
if (flag_coloring)
|
|
{
|
|
int max_patch_per_cluster = -1;
|
|
if (result.count("mp"))
|
|
max_patch_per_cluster = result["mp"].as<int>();
|
|
std::vector<size_t> cluster_color(connectivity_v.size(), -1);
|
|
n_color = tree_coloring<size_t>(tree, connectivity_v, cluster_color, 0, max_patch_per_cluster) + 1;
|
|
for (size_t i = 0; i < face_clusters.size(); i++)
|
|
{
|
|
size_t cc = cluster_color[i];
|
|
for (size_t j = 0; j < face_clusters[i].size(); j++)
|
|
{
|
|
face_color[face_clusters[i][j]] = cc + 1;
|
|
}
|
|
}
|
|
|
|
update_tree_color(cluster_color, tree);
|
|
|
|
}
|
|
|
|
std::string tree_str = convert_tree_to_string<size_t>(tree);
|
|
save_conf_file((output_prefix + "_csg.conf").c_str(), tree_str, flag_convex);
|
|
}
|
|
|
|
//sampling on triangles
|
|
std::vector<double> tri_mean_curvature_normalize(tri_verts.size(), 0.0);
|
|
if (result.count("cot"))
|
|
{
|
|
//compute tri_mean_curvature
|
|
std::vector<double> vert_curvature;
|
|
compute_vert_mean_curvature(vert_pos, tri_verts, vert_curvature);
|
|
for (size_t i = 0; i < tri_verts.size(); i++)
|
|
{
|
|
for (size_t j = 0; j < 3; j++)
|
|
{
|
|
tri_mean_curvature_normalize[i] += vert_curvature[tri_verts[i][j]];
|
|
}
|
|
}
|
|
auto maxele = std::max_element(begin(tri_mean_curvature_normalize), end(tri_mean_curvature_normalize));
|
|
auto minele = std::min_element(begin(tri_mean_curvature_normalize), end(tri_mean_curvature_normalize));
|
|
std::cout << "min curvature: " << *minele << " max curvature: " << *maxele << std::endl;
|
|
double diff = *maxele - *minele;
|
|
for (size_t i = 0; i < tri_verts.size(); i++)
|
|
{
|
|
tri_mean_curvature_normalize[i] = (tri_mean_curvature_normalize[i] - *minele) / diff;
|
|
}
|
|
}
|
|
//first sample at least min_pts_per_patch pts from each patch
|
|
if (flag_sample_pts)
|
|
{
|
|
int max_face_color = *std::max_element(face_color.begin(), face_color.end());
|
|
std::cout << "num of patches: " << max_face_color << std::endl;
|
|
for (size_t i = 1; i < max_face_color + 1; i++)
|
|
{
|
|
std::vector<TinyVector<size_t, 3>> cur_faces;
|
|
std::vector<TinyVector<double, 3>> cur_normals;
|
|
for (size_t j = 0; j < face_color.size(); j++)
|
|
{
|
|
if (face_color[j] == i)
|
|
{
|
|
cur_faces.push_back(tri_verts[j]);
|
|
cur_normals.push_back(tri_normals[j]);
|
|
}
|
|
}
|
|
|
|
std::vector<int> cur_face_mask(cur_faces.size(), i);
|
|
std::vector<TinyVector<double, 3>> cur_sample_pts, cur_sample_normals;
|
|
std::vector<int> cur_sample_masks;
|
|
sample_pts_from_mesh(vert_pos, cur_faces, cur_normals, cur_face_mask, min_sample_perpatch, cur_sample_pts, cur_sample_normals, cur_sample_masks, sigma, sigma_n);
|
|
|
|
sample_pts.insert(sample_pts.end(), cur_sample_pts.begin(), cur_sample_pts.end());
|
|
sample_pt_normals.insert(sample_pt_normals.end(), cur_sample_normals.begin(), cur_sample_normals.end());
|
|
sample_mask.insert(sample_mask.end(), cur_sample_masks.begin(), cur_sample_masks.end());
|
|
}
|
|
|
|
if (sample_mask.size() < n_nonfeature_sample)
|
|
{
|
|
//sample the rest
|
|
std::vector<TinyVector<double, 3>> cur_sample_pts, cur_sample_normals;
|
|
std::vector<int> cur_sample_masks;
|
|
sample_pts_from_mesh(vert_pos, tri_verts, tri_normals, face_color, n_nonfeature_sample - sample_mask.size(), cur_sample_pts, cur_sample_normals, cur_sample_masks, sigma, sigma_n);
|
|
sample_pts.insert(sample_pts.end(), cur_sample_pts.begin(), cur_sample_pts.end());
|
|
sample_pt_normals.insert(sample_pt_normals.end(), cur_sample_normals.begin(), cur_sample_normals.end());
|
|
sample_mask.insert(sample_mask.end(), cur_sample_masks.begin(), cur_sample_masks.end());
|
|
}
|
|
else
|
|
{
|
|
std::cout << "too many patches for model: " << inputfile << std::endl;
|
|
}
|
|
|
|
std::ofstream outputsamples(outputfile.c_str());
|
|
|
|
for (size_t i = 0; i < sample_pts.size(); i++)
|
|
{
|
|
outputsamples << sample_pts[i] << " " << sample_pt_normals[i] << std::endl;
|
|
}
|
|
|
|
outputsamples.close();
|
|
}
|
|
else
|
|
{
|
|
for (size_t i = 0; i < sample_pts_tris.size(); i++)
|
|
{
|
|
sample_mask.push_back(face_color[sample_pts_tris[i]]);
|
|
}
|
|
}
|
|
|
|
//output ply
|
|
std::vector<std::array<double, 3>> branch_color;
|
|
for (size_t i = 0; i < n_color; i++)
|
|
{
|
|
branch_color.push_back(std::array<double, 3>{ {unif_dist(e2), unif_dist(e2), unif_dist(e2)}});
|
|
}
|
|
|
|
std::vector<std::array<double, 3>> meshVertexPositions;
|
|
std::vector<std::array<double, 3>> meshVertexColors;
|
|
|
|
for (size_t i = 0; i < sample_pts.size(); i++)
|
|
{
|
|
meshVertexPositions.push_back(std::array<double, 3>{ {sample_pts[i][0], sample_pts[i][1], sample_pts[i][2]}});
|
|
meshVertexColors.push_back(branch_color[sample_mask[i]]);
|
|
}
|
|
|
|
|
|
// Create an empty object
|
|
happly::PLYData plyOut;
|
|
|
|
// Add mesh data (elements are created automatically)
|
|
plyOut.addVertexPositions(meshVertexPositions);
|
|
plyOut.addVertexColors(meshVertexColors);
|
|
|
|
// Write the object to file
|
|
plyOut.write(output_prefix + "_patch_"+ std::to_string(n_color - 1) +".ply", happly::DataFormat::ASCII);
|
|
|
|
std::ofstream outputmask(outputmaskfile.c_str());
|
|
for (size_t i = 0; i < sample_mask.size(); i++)
|
|
{
|
|
outputmask << sample_mask[i] << std::endl;
|
|
}
|
|
|
|
outputmask.close();
|
|
|
|
//output face seg id: starting from 0
|
|
outputmask.open(output_prefix + "_fid.txt");
|
|
for (size_t i = 0; i < face_color.size(); i++)
|
|
{
|
|
outputmask << face_color[i] - 1 << std::endl;
|
|
}
|
|
|
|
outputmask.close();
|
|
|
|
return 1;
|
|
}
|
|
|
|
}
|
|
catch (const cxxopts::OptionException& e)
|
|
{
|
|
std::cout << "error parsing options: " << e.what() << std::endl;
|
|
exit(1);
|
|
}
|
|
return 0;
|
|
}
|