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.
451 lines
13 KiB
451 lines
13 KiB
#include <iostream>
|
|
#include <fstream>
|
|
#include "Mesh3D.h"
|
|
#include "TinyVector.h"
|
|
#include "happly/happly.h"
|
|
#include "cxxopts.hpp"
|
|
|
|
#define SHORTEST_EDGE_LENGTH 1e-6
|
|
|
|
|
|
#define th_smooth_cos_value 0.939692 //for grouping case, this angle is used to distinguish sharp and non-sharp feauture
|
|
|
|
|
|
using namespace MeshLib;
|
|
|
|
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);
|
|
}
|
|
|
|
//not consider grouped feature
|
|
return true;
|
|
|
|
}
|
|
|
|
void select_features_by_angle(Mesh3d* m, double th_angle, std::vector<std::pair<int, int>> &ungrouped_features, std::vector<int> &ungrouped_feature_eid)
|
|
{
|
|
ungrouped_features.clear();
|
|
ungrouped_feature_eid.clear();
|
|
const double angle = th_angle * 3.1415926535897932384626433832795 / 180;
|
|
std::vector<bool> he_flag(m->get_num_of_edges(), false);
|
|
for (size_t i = 0; i < m->get_num_of_edges(); i++)
|
|
{
|
|
HE_edge<double>* he = m->get_edge(i);
|
|
if (!he_flag[he->id])
|
|
{
|
|
if (!m->is_on_boundary(he) && acos(he->face->normal.Dot(he->pair->face->normal)) > angle)
|
|
{
|
|
he_flag[he->id] = true;
|
|
he_flag[he->pair->id] = true;
|
|
ungrouped_features.push_back(std::pair<int, int>(he->vert->id, he->pair->vert->id));
|
|
ungrouped_feature_eid.push_back(he->id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void output_pts_xyz(const std::vector<TinyVector<double, 3>>& pts, const char* filename)
|
|
{
|
|
std::ofstream ofs(filename);
|
|
for (size_t i = 0; i < pts.size(); i++)
|
|
{
|
|
ofs << pts[i] << std::endl;
|
|
}
|
|
|
|
ofs.close();
|
|
}
|
|
|
|
void get_grouped_edges(Mesh3d& mesh, const std::vector<bool>& he_feature_flag, const std::vector<std::vector<int>>& feature_v2he, std::vector<std::vector<int>>& grouped_features, std::vector<int>& he2gid)
|
|
{
|
|
grouped_features.clear();
|
|
he2gid.clear();
|
|
he2gid.resize(mesh.get_num_of_edges(), -1);
|
|
int groupid = 0;
|
|
while (true)
|
|
{
|
|
int first_he = -1;
|
|
for (size_t i = 0; i < he_feature_flag.size(); i++)
|
|
{
|
|
if (he_feature_flag[i] && he2gid[i] == -1)
|
|
{
|
|
first_he = i;
|
|
break;
|
|
}
|
|
}
|
|
if (first_he == -1)
|
|
{
|
|
break;
|
|
}
|
|
|
|
std::vector<int> onegroup;
|
|
std::queue<size_t> q;
|
|
q.push(first_he);
|
|
he2gid[first_he] = groupid;
|
|
he2gid[mesh.get_edges_list()->at(first_he)->pair->id] = groupid;
|
|
while (!q.empty())
|
|
{
|
|
size_t curhe = q.front();
|
|
onegroup.push_back(curhe);
|
|
q.pop();
|
|
size_t curhe_pair = mesh.get_edges_list()->at(curhe)->pair->id;
|
|
std::vector<size_t> twoverts;
|
|
twoverts.push_back(mesh.get_edges_list()->at(curhe)->vert->id);
|
|
twoverts.push_back(mesh.get_edges_list()->at(curhe)->pair->vert->id);
|
|
std::vector<size_t> twoedges;
|
|
twoedges.push_back(curhe_pair);
|
|
twoedges.push_back(curhe);
|
|
for (size_t i = 0; i < 2; i++)
|
|
{
|
|
//only accept points with degree 2
|
|
if (feature_v2he[twoverts[i]].size() == 2)
|
|
{
|
|
assert(twoedges[i] == feature_v2he[twoverts[i]][0] || twoedges[i] == feature_v2he[twoverts[i]][1]);
|
|
size_t other_he = feature_v2he[twoverts[i]][0] + feature_v2he[twoverts[i]][1] - twoedges[i];
|
|
if (he2gid[other_he] == -1)
|
|
{
|
|
q.push(other_he);
|
|
he2gid[other_he] = groupid;
|
|
he2gid[mesh.get_edges_list()->at(other_he)->pair->id] = groupid;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
grouped_features.push_back(onegroup);
|
|
groupid++;
|
|
}
|
|
}
|
|
|
|
void select_real_sharp_features(Mesh3d& mesh, std::vector<std::pair<int, int>>& ungrouped_features)
|
|
{
|
|
std::vector<bool> he_feature_flag(mesh.get_edges_list()->size(), false);
|
|
std::vector<std::vector<int>> feature_v2he(mesh.get_num_of_vertices()); //id of hes ematating from each vertex
|
|
|
|
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);
|
|
}
|
|
|
|
std::vector<std::vector<int>> grouped_features;
|
|
std::vector<int> he2gid;
|
|
|
|
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);
|
|
}
|
|
|
|
//step1: get he_feature_flag
|
|
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);
|
|
if (edge->vert->id == id1)
|
|
{
|
|
he_feature_flag[edge->id] = true;
|
|
he_feature_flag[edge->pair->id] = true;
|
|
}
|
|
}
|
|
//step 2: get grouped feature
|
|
get_grouped_edges(mesh, he_feature_flag, feature_v2he, grouped_features, he2gid);
|
|
//step 3: select real sharp features
|
|
std::vector<std::pair<int, int>> ungrouped_features_update;
|
|
std::vector<std::array<int, 3>> ge2count(grouped_features.size(), std::array<int, 3>{0, 0, 0});
|
|
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;
|
|
|
|
//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]);
|
|
double tmp_cos = e1->face->normal.Dot(e2->face->normal);
|
|
|
|
int gid = he2gid[i];
|
|
|
|
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]++;
|
|
}
|
|
}
|
|
}
|
|
|
|
//grouped feature no duplicate
|
|
}
|
|
|
|
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;
|
|
if (maxid != 0)
|
|
{
|
|
for (auto eid : grouped_features[i])
|
|
{
|
|
int v0 = mesh.get_edge(eid)->vert->id;
|
|
int v1 = mesh.get_edge(eid)->pair->vert->id;
|
|
ungrouped_features_update.push_back(std::pair<int, int>(v0, v1));
|
|
}
|
|
}
|
|
}
|
|
|
|
ungrouped_features = ungrouped_features_update;
|
|
}
|
|
|
|
int main(int argc, char** argv)
|
|
{
|
|
try
|
|
{
|
|
cxxopts::Options options("SimpleSample", "Sampling feature points from points on mesh (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/ply format)", cxxopts::value<std::string>())
|
|
("f,feature", "input feature file (fea format)", cxxopts::value<std::string>())
|
|
("o,output", "output points (ptangle format)", cxxopts::value<std::string>())
|
|
("a", "angle threshold in degree for detecting features, default(30)", cxxopts::value<double>())
|
|
("s", "length of line segment for sampling, default(4e-3)", cxxopts::value<double>())
|
|
("g,group", "group feature edge");
|
|
("h,help", "print help");
|
|
|
|
auto result = options.parse(argc, argv);
|
|
if (result.count("help"))
|
|
{
|
|
std::cout << options.help({ "", "Group" }) << std::endl;
|
|
exit(0);
|
|
}
|
|
bool flag_feature_flag = true;
|
|
double len_seg = 4e-3;
|
|
double th_angle = 30;
|
|
bool flag_group_features = false; //group input feature to decide its convexity
|
|
if (result.count("s"))
|
|
{
|
|
len_seg = result["s"].as<double>();
|
|
}
|
|
if (result.count("a"))
|
|
{
|
|
//angle is used only when no feature files are given
|
|
th_angle = result["a"].as<double>();
|
|
}
|
|
|
|
if (result.count("g"))
|
|
{
|
|
flag_group_features = true;
|
|
}
|
|
|
|
auto& inputfile = result["i"].as<std::string>();
|
|
auto& outputfile = result["o"].as<std::string>();
|
|
|
|
int last_dot = (int)outputfile.find_last_of(".");
|
|
auto output_prefix = outputfile.substr(0, last_dot);
|
|
|
|
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());
|
|
else if (inputext == "ply")
|
|
{
|
|
happly::PLYData plyIn(inputfile);
|
|
std::vector<std::array<double, 3>> vPos = plyIn.getVertexPositions();
|
|
std::vector<std::vector<size_t>> fInd = plyIn.getFaceIndices<size_t>();
|
|
mesh.load_mesh(vPos, fInd);
|
|
}
|
|
std::cout << "verts: " << mesh.get_vertices_list()->size() << " face: " << mesh.get_faces_list()->size() << std::endl;
|
|
|
|
std::vector<std::pair<int, int>> ungrouped_features;
|
|
std::vector<int> ungrouped_feature_eid;
|
|
if (result.count("f"))
|
|
{
|
|
auto& inputfeaturefile = result["f"].as<std::string>();
|
|
load_feature_file(inputfeaturefile.c_str(), ungrouped_features);
|
|
|
|
if (flag_group_features)
|
|
{
|
|
//repair and group features
|
|
select_real_sharp_features(mesh, ungrouped_features);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//select feature by angle
|
|
select_features_by_angle(&mesh, th_angle, ungrouped_features, ungrouped_feature_eid);
|
|
}
|
|
|
|
//sample points
|
|
std::vector<TinyVector<double, 3>> sample_pts;
|
|
std::vector<double> pts_angle;
|
|
//for (auto& pair : ungrouped_features)
|
|
for (size_t i = 0; i < ungrouped_features.size(); i++)
|
|
{
|
|
auto pair = ungrouped_features[i];
|
|
TinyVector<double, 3> pos0 = mesh.get_vertex(pair.first)->pos;
|
|
TinyVector<double, 3> pos1 = mesh.get_vertex(pair.second)->pos;
|
|
double len = (pos0 - pos1).Length();
|
|
if (len > SHORTEST_EDGE_LENGTH)
|
|
{
|
|
//sample at least one points
|
|
double angle = 0.0;
|
|
if (ungrouped_feature_eid.empty())
|
|
{
|
|
bool flag = false;
|
|
|
|
HE_edge<double>* begin_edge = mesh.get_vertex(pair.first)->edge;
|
|
HE_edge<double>* edge = begin_edge;
|
|
do
|
|
{
|
|
if (edge->vert->id == pair.second)
|
|
{
|
|
flag = true;
|
|
angle = acos(edge->face->normal.Dot(edge->pair->face->normal)) * 180.0 / 3.1415926535897932384626433832795;
|
|
break;
|
|
}
|
|
|
|
edge = edge->pair->next;
|
|
} while (edge != begin_edge);
|
|
|
|
assert(flag == true);
|
|
}
|
|
else
|
|
{
|
|
HE_edge<double>* edge = mesh.get_edge(ungrouped_feature_eid[i]);
|
|
angle = acos(edge->face->normal.Dot(edge->pair->face->normal)) * 180.0 / 3.1415926535897932384626433832795;
|
|
}
|
|
|
|
if (len < len_seg / 2.0)
|
|
{
|
|
sample_pts.push_back((pos0 + pos1) / 2.0);
|
|
pts_angle.push_back(angle);
|
|
}
|
|
else
|
|
{
|
|
int n_split = std::ceil((len - len_seg / 2.0) / len_seg);
|
|
TinyVector<double, 3> vec = (pos1 - pos0)/(pos1 - pos0).Length();
|
|
for (size_t i = 0; i < n_split; i++)
|
|
{
|
|
double tmp_len = len_seg / 2.0 + i * len_seg;
|
|
sample_pts.push_back(pos0 + tmp_len * vec);
|
|
pts_angle.push_back(angle);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
assert(pts_angle.size() == sample_pts.size());
|
|
|
|
//output_xyz
|
|
output_pts_xyz(sample_pts, (output_prefix + ".xyz").c_str());
|
|
|
|
std::ofstream ofs(outputfile);
|
|
for (size_t i = 0; i < pts_angle.size(); i++)
|
|
{
|
|
ofs << sample_pts[i] << " " << pts_angle[i] << std::endl;
|
|
}
|
|
|
|
ofs.close();
|
|
}
|
|
catch (const cxxopts::OptionException& e)
|
|
{
|
|
std::cout << "error parsing options: " << e.what() << std::endl;
|
|
exit(1);
|
|
}
|
|
|
|
return 0;
|
|
}
|