10697 lines
579 KiB
C++
10697 lines
579 KiB
C++
/**
|
|
* Copyright (c) 2021-2022 Floyd M. Chitalu.
|
|
* All rights reserved.
|
|
*
|
|
* NOTE: This file is licensed under GPL-3.0-or-later (default).
|
|
* A commercial license can be purchased from Floyd M. Chitalu.
|
|
*
|
|
* License details:
|
|
*
|
|
* (A) GNU General Public License ("GPL"); a copy of which you should have
|
|
* recieved with this file.
|
|
* - see also: <http://www.gnu.org/licenses/>
|
|
* (B) Commercial license.
|
|
* - email: floyd.m.chitalu@gmail.com
|
|
*
|
|
* The commercial license options is for users that wish to use MCUT in
|
|
* their products for comercial purposes but do not wish to release their
|
|
* software products under the GPL license.
|
|
*
|
|
* Author(s) : Floyd M. Chitalu
|
|
*/
|
|
#include <algorithm>
|
|
#include <functional>
|
|
#include <iterator>
|
|
#include <numeric> // std::iota
|
|
#include <queue>
|
|
#include <set>
|
|
#include <string>
|
|
#include <tuple>
|
|
#include <unordered_map>
|
|
|
|
#include "mcut/internal/bvh.h"
|
|
#include "mcut/internal/hmesh.h"
|
|
#include "mcut/internal/kernel.h"
|
|
#include "mcut/internal/math.h"
|
|
#include "mcut/internal/timer.h"
|
|
#include "mcut/internal/utils.h"
|
|
|
|
#ifndef LICENSE_PURCHASED
|
|
#define lmsg() printf("NOTE: MCUT is copyrighted and may not be sold or included in commercial products without a license.\n")
|
|
#else
|
|
#define lmsg()
|
|
#endif // #ifndef LICENSE_PURCHASED
|
|
|
|
namespace std {
|
|
// need to declare partial and explicit specializations in every translation unit
|
|
// that uses them (before any use that would implicitly instantiate that
|
|
// specialization)
|
|
template <>
|
|
typename edge_array_iterator_t::difference_type distance(
|
|
edge_array_iterator_t first,
|
|
edge_array_iterator_t last);
|
|
}
|
|
|
|
logger_t* logger_ptr = nullptr;
|
|
std::string to_string(const sm_frag_location_t& v)
|
|
{
|
|
std::string s;
|
|
switch (v) {
|
|
case sm_frag_location_t::ABOVE:
|
|
s = "a";
|
|
break;
|
|
case sm_frag_location_t::BELOW:
|
|
s = "b";
|
|
break;
|
|
case sm_frag_location_t::UNDEFINED:
|
|
s = "u";
|
|
break;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
std::string to_string(const cm_patch_location_t& v)
|
|
{
|
|
std::string s;
|
|
switch (v) {
|
|
case cm_patch_location_t::INSIDE:
|
|
s = "i";
|
|
break;
|
|
case cm_patch_location_t::OUTSIDE:
|
|
s = "o";
|
|
break;
|
|
case cm_patch_location_t::UNDEFINED:
|
|
s = "u";
|
|
break;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
std::string to_string(const status_t& v)
|
|
{
|
|
std::string s;
|
|
switch (v) {
|
|
case status_t::SUCCESS:
|
|
s = "SUCCESS";
|
|
break;
|
|
case status_t::INVALID_SRC_MESH:
|
|
s = "INVALID_SRC_MESH";
|
|
break;
|
|
case status_t::INVALID_CUT_MESH:
|
|
s = "INVALID_CUT_MESH";
|
|
break;
|
|
case status_t::INVALID_MESH_INTERSECTION:
|
|
s = "INVALID_MESH_INTERSECTION";
|
|
break;
|
|
case status_t::GENERAL_POSITION_VIOLATION:
|
|
s = "GENERAL_POSITION_VIOLATION";
|
|
break;
|
|
case status_t::DETECTED_FLOATING_POLYGON:
|
|
s = "DETECTED_FLOATING_POLYGON";
|
|
break;
|
|
// case status_t::FACE_VERTEX_INTERSECTION:
|
|
// s = "FACE_VERTEX_INTERSECTION";
|
|
// break;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
std::string to_string(const cm_patch_winding_order_t& v)
|
|
{
|
|
std::string s;
|
|
switch (v) {
|
|
case cm_patch_winding_order_t::DEFAULT:
|
|
s = "def";
|
|
break;
|
|
case cm_patch_winding_order_t::REVERSE:
|
|
s = "rev";
|
|
break;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
// returns whether a polygon-soup vertex is an intersection vertex/point
|
|
inline bool m0_is_intersection_point(const vd_t& ps_vd, const int ps_vtx_cnt)
|
|
{
|
|
return ((int)ps_vd) >= ps_vtx_cnt;
|
|
}
|
|
|
|
// returns whether a polygon-soup vertex belongs to the cut mesh
|
|
bool inline ps_is_cutmesh_vertex(const vd_t& ps_vd, const int sm_vtx_cnt)
|
|
{
|
|
return ((int)ps_vd) >= sm_vtx_cnt;
|
|
}
|
|
|
|
bool inline ps_is_cutmesh_face(const fd_t& ps_fd, const int sm_face_count)
|
|
{
|
|
return ((int)ps_fd) >= sm_face_count;
|
|
}
|
|
|
|
void dump_mesh(const hmesh_t& mesh, const char* fbasename)
|
|
{
|
|
const std::string name = std::string(fbasename) + ".off";
|
|
|
|
for (vertex_array_iterator_t v = mesh.vertices_begin(); v != mesh.vertices_end(); ++v) {
|
|
}
|
|
|
|
for (edge_array_iterator_t e = mesh.edges_begin(); e != mesh.edges_end(); ++e) {
|
|
}
|
|
|
|
for (halfedge_array_iterator_t h = mesh.halfedges_begin(); h != mesh.halfedges_end(); ++h) {
|
|
}
|
|
|
|
for (face_array_iterator_t face_iter = mesh.faces_begin(); face_iter != mesh.faces_end(); ++face_iter) {
|
|
|
|
const std::vector<halfedge_descriptor_t>& halfedges_around_face = mesh.get_halfedges_around_face(*face_iter);
|
|
|
|
// int num_halfedges = ;
|
|
MCUT_ASSERT((int)halfedges_around_face.size());
|
|
|
|
//
|
|
for (std::vector<halfedge_descriptor_t>::const_iterator h = halfedges_around_face.cbegin();
|
|
h != halfedges_around_face.cend();
|
|
++h) {
|
|
}
|
|
}
|
|
|
|
write_off(name.c_str(), mesh);
|
|
}
|
|
|
|
#if 0
|
|
bool point_on_face_plane(const hmesh_t& m, const fd_t& f, const vec3& p, int& fv_count)
|
|
{
|
|
const std::vector<vd_t> vertices = m.get_vertices_around_face(f);
|
|
fv_count = (int)vertices.size();
|
|
{
|
|
for (int i = 0; i < fv_count; ++i) {
|
|
const int j = (i + 1) % fv_count;
|
|
const int k = (i + 2) % fv_count;
|
|
|
|
const vd_t& vi = vertices[i];
|
|
const vd_t& vj = vertices[j];
|
|
const vd_t& vk = vertices[k];
|
|
|
|
const vec3& vi_coords = m.vertex(vi);
|
|
const vec3& vj_coords = m.vertex(vj);
|
|
const vec3& vk_coords = m.vertex(vk);
|
|
|
|
const bool are_coplaner = coplaner(vi_coords, vj_coords, vk_coords, p);
|
|
|
|
if (!are_coplaner) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
dfs(node u)
|
|
for each node v connected to u :
|
|
if v is not visited :
|
|
visited[v] = true
|
|
dfs(v)
|
|
*/
|
|
void dfs_cc(vd_t u, const hmesh_t& mesh, std::vector<int>& visited, int connected_component_id)
|
|
{
|
|
std::vector<vd_t> verts = mesh.get_vertices_around_vertex(u);
|
|
for (std::vector<vd_t>::const_iterator v = verts.cbegin(); v != verts.cend(); ++v) {
|
|
if (SAFE_ACCESS(visited, *v) == -1) {
|
|
visited[*v] = connected_component_id;
|
|
dfs_cc(*v, mesh, visited, connected_component_id);
|
|
}
|
|
}
|
|
}
|
|
|
|
int find_connected_components(
|
|
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
thread_pool& scheduler,
|
|
#endif
|
|
std::vector<int>& fccmap,
|
|
const hmesh_t& mesh,
|
|
std::vector<int>& cc_to_vertex_count,
|
|
std::vector<int>& cc_to_face_count)
|
|
{
|
|
MCUT_ASSERT(mesh.number_of_vertices() >= 3);
|
|
MCUT_ASSERT(mesh.number_of_edges() >= 3);
|
|
MCUT_ASSERT(mesh.number_of_faces() >= 1);
|
|
|
|
/*
|
|
for each node u:
|
|
if u is not visited :
|
|
visited[u] = true
|
|
connected_component += 1
|
|
dfs(u)
|
|
*/
|
|
std::vector<int> visited(mesh.number_of_vertices(), -1); // if vertex does not exist, then its not visited
|
|
int connected_component_id = -1;
|
|
std::vector<bool> queued(mesh.number_of_vertices(), false);
|
|
std::queue<vd_t> queue; // .. to discover all vertices of current connected component
|
|
|
|
std::vector<vd_t> vertices_of_v;
|
|
std::vector<vd_t> vertices_of_u;
|
|
|
|
for (vertex_array_iterator_t u = mesh.vertices_begin(); u != mesh.vertices_end(); ++u) {
|
|
if (visited[*u] == -1) {
|
|
connected_component_id += 1;
|
|
visited[*u] = connected_component_id;
|
|
queued[*u] = true;
|
|
|
|
cc_to_vertex_count.push_back(1); // each discovered cc has at least one vertex
|
|
|
|
vertices_of_u.clear();
|
|
mesh.get_vertices_around_vertex(vertices_of_u, *u);
|
|
|
|
for (int i = 0; i < (int)vertices_of_u.size(); ++i) {
|
|
vd_t vou = vertices_of_u[i];
|
|
queue.push(vou);
|
|
queued[vou] = true;
|
|
}
|
|
|
|
while (!queue.empty()) {
|
|
vd_t v = queue.front(); // current
|
|
queue.pop();
|
|
|
|
if (visited[v] == -1) // v not yet associated with a cc
|
|
{
|
|
visited[v] = connected_component_id;
|
|
cc_to_vertex_count[connected_component_id] += 1;
|
|
|
|
vertices_of_v.clear();
|
|
mesh.get_vertices_around_vertex(vertices_of_v, v);
|
|
|
|
for (int i = 0; i < (int)vertices_of_v.size(); ++i) {
|
|
vd_t vov = vertices_of_v[i];
|
|
if (visited[vov] == -1 || queued[vov] == false) {
|
|
queue.push(vertices_of_v[i]);
|
|
queued[vov] = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// dfs_cc(*u, mesh, visited, connected_component_id);
|
|
}
|
|
}
|
|
|
|
fccmap.clear();
|
|
fccmap.resize(mesh.number_of_faces());
|
|
int num_connected_components = (connected_component_id + 1); // number of CCs
|
|
cc_to_face_count.resize(num_connected_components);
|
|
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
auto fn_set_cc_to_face_count = [](std::vector<int>::iterator block_start_, std::vector<int>::iterator block_end_) {
|
|
for (std::vector<int>::iterator it = block_start_; it != block_end_; ++it) {
|
|
*it = 0;
|
|
}
|
|
};
|
|
|
|
parallel_for(
|
|
scheduler,
|
|
cc_to_face_count.begin(),
|
|
cc_to_face_count.end(),
|
|
fn_set_cc_to_face_count);
|
|
#else
|
|
for (int i = 0; i < (int)cc_to_face_count.size(); ++i) {
|
|
cc_to_face_count[i] = 0;
|
|
}
|
|
#endif
|
|
|
|
fccmap.reserve(mesh.number_of_faces());
|
|
|
|
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
auto fn_map_faces = [&](face_array_iterator_t block_start_, face_array_iterator_t block_end_) {
|
|
for (face_array_iterator_t f = block_start_; f != block_end_; ++f) {
|
|
const std::vector<vertex_descriptor_t> vertices = mesh.get_vertices_around_face(*f);
|
|
|
|
int face_cc_id = SAFE_ACCESS(visited, vertices.front());
|
|
|
|
// all vertices belong to the same conn comp
|
|
fccmap[*f] = face_cc_id;
|
|
|
|
cc_to_face_count[face_cc_id] += 1;
|
|
}
|
|
};
|
|
|
|
parallel_for(
|
|
scheduler,
|
|
mesh.faces_begin(),
|
|
mesh.faces_end(),
|
|
fn_map_faces);
|
|
#else
|
|
// map each face to a connected component
|
|
for (face_array_iterator_t f = mesh.faces_begin(); f != mesh.faces_end(); ++f) {
|
|
const std::vector<vertex_descriptor_t> vertices = mesh.get_vertices_around_face(*f);
|
|
|
|
int face_cc_id = SAFE_ACCESS(visited, vertices.front());
|
|
|
|
// all vertices belong to the same conn comp
|
|
fccmap[*f] = face_cc_id;
|
|
|
|
cc_to_face_count[face_cc_id] += 1;
|
|
}
|
|
#endif
|
|
|
|
return num_connected_components;
|
|
}
|
|
|
|
struct connected_component_info_t {
|
|
sm_frag_location_t location = sm_frag_location_t::UNDEFINED; // above/ below
|
|
// vertices along the cut path seam
|
|
std::vector<vd_t> seam_vertices;
|
|
// mapping from mesh descriptors to input mesh descriptors (vertex and face)
|
|
output_mesh_data_maps_t data_maps;
|
|
};
|
|
|
|
// a seam vertex is simply an intersection point, including a duplicated instance if it exists as determined by the
|
|
// parameters "ps_num_vertices" and "m1_num_vertices_after_srcmesh_partitioning"
|
|
void mark_seam_vertices(
|
|
std::vector<bool>& mesh_seam_vertices,
|
|
hmesh_t& mesh,
|
|
const int ps_num_vertices,
|
|
const int m1_num_vertices_after_srcmesh_partitioning = std::numeric_limits<int>::max())
|
|
{
|
|
mesh_seam_vertices.resize(mesh.number_of_vertices());
|
|
for (vertex_array_iterator_t i = mesh.vertices_begin();
|
|
i != mesh.vertices_end();
|
|
++i) {
|
|
const int idx = static_cast<int>(*i);
|
|
mesh_seam_vertices[*i] = (idx >= ps_num_vertices && idx < m1_num_vertices_after_srcmesh_partitioning);
|
|
}
|
|
}
|
|
|
|
// returns the unseparated/merged connected components
|
|
hmesh_t extract_connected_components(
|
|
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
thread_pool& scheduler,
|
|
#endif
|
|
// key = cc-id; value = list of cc copies each differing by one newly stitched polygon
|
|
std::map<std::size_t, std::vector<std::pair<std::shared_ptr<hmesh_t>, connected_component_info_t>>>& connected_components,
|
|
const hmesh_t& in,
|
|
const int traced_polygons_base_offset,
|
|
const std::vector<std::vector<hd_t>>& mX_traced_polygons, // "m0" or "m1" (dependent on function-call location)
|
|
const std::vector<int>& sm_polygons_below_cs,
|
|
const std::vector<int>& sm_polygons_above_cs,
|
|
const std::vector<bool>& mesh_vertex_to_seam_flag,
|
|
// vertex & face data mapping parameters
|
|
// const std::map<vd_t /*"m1" ovtx in sm*/, vd_t /*"m0" ovtx in sm*/> &m1_to_m0_sm_ovtx_colored,
|
|
const std::vector<vd_t /*"m1" ovtx in sm*/>& m1_to_m0_sm_ovtx_colored,
|
|
const std::unordered_map<vd_t /*"m1" ovtx*/, vd_t /*"m0" ovtx in cm*/>& m1_to_m0_cm_ovtx_colored,
|
|
/*const*/ std::unordered_map<int /*"m0" face idx*/, int /*"m1" face idx*/>& m1_to_m0_face_colored,
|
|
// const std::map<vd_t /*"m0" ovtx*/, vd_t /*"ps" ovtx*/> &m0_to_ps_vtx,
|
|
const std::vector<vd_t>& m0_to_ps_vtx,
|
|
/*const*/ std::unordered_map<int /*"m0" face idx*/, fd_t /*"ps" face*/>& m0_to_ps_face,
|
|
// const std::map<vd_t /*"sm" vtx*/, vd_t /*"ps" vtx*/> &ps_to_sm_vtx,
|
|
const std::vector<vd_t>& ps_to_sm_vtx,
|
|
// const std::map<fd_t /*"sm" face*/, fd_t /*"ps" face*/> &ps_to_sm_face,
|
|
const std::vector<fd_t>& ps_to_sm_face,
|
|
// const std::map<vd_t /*"cm" vtx*/, vd_t /*"ps" vtx*/> &ps_to_cm_vtx,
|
|
const std::vector<vd_t>& ps_to_cm_vtx,
|
|
// const std::map<fd_t /*"cs" face*/, fd_t /*"ps" face*/> &ps_to_cm_face,
|
|
const std::vector<fd_t>& ps_to_cm_face,
|
|
const int sm_vtx_cnt,
|
|
const int sm_face_count,
|
|
bool popuplate_vertex_maps,
|
|
bool popuplate_face_maps,
|
|
bool keep_fragments_below_cutmesh,
|
|
bool keep_fragments_above_cutmesh,
|
|
bool keep_fragments_partially_cut)
|
|
{
|
|
|
|
// the auxilliary halfedge mesh containing vertices and edges referenced by the traced polygons
|
|
hmesh_t mesh = in; // copy
|
|
mesh.reserve_for_additional_elements((std::uint32_t)mX_traced_polygons.size() / 2);
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Insert traced polygons into the auxilliary mesh
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
TIMESTACK_PUSH("Extract CC: Insert polygons");
|
|
|
|
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
{
|
|
typedef std::tuple<
|
|
std::vector<std::vector<vd_t>> // "mesh" faces
|
|
>
|
|
OutputStorageTypesTuple;
|
|
typedef std::vector<std::vector<hd_t>>::const_iterator InputStorageIteratorType;
|
|
|
|
auto fn_compute_inserted_faces = [&mesh](InputStorageIteratorType block_start_, InputStorageIteratorType block_end_) -> OutputStorageTypesTuple {
|
|
OutputStorageTypesTuple local_output;
|
|
std::vector<std::vector<vd_t>>& faces_LOCAL = std::get<0>(local_output);
|
|
faces_LOCAL.reserve(std::distance(block_start_, block_end_));
|
|
for (InputStorageIteratorType mX_traced_polygons_iter = block_start_; mX_traced_polygons_iter != block_end_; ++mX_traced_polygons_iter) {
|
|
const std::vector<hd_t>& mX_traced_polygon = *mX_traced_polygons_iter;
|
|
|
|
faces_LOCAL.push_back(std::vector<vd_t>());
|
|
std::vector<vd_t>& polygon_vertices = faces_LOCAL.back();
|
|
polygon_vertices.reserve(mX_traced_polygon.size());
|
|
|
|
// for each halfedge in polygon
|
|
for (std::vector<hd_t>::const_iterator mX_traced_polygon_halfedge_iter = mX_traced_polygon.cbegin();
|
|
mX_traced_polygon_halfedge_iter != mX_traced_polygon.cend();
|
|
++mX_traced_polygon_halfedge_iter) {
|
|
polygon_vertices.push_back(mesh.target(*mX_traced_polygon_halfedge_iter));
|
|
}
|
|
}
|
|
|
|
return local_output;
|
|
};
|
|
|
|
std::vector<std::future<OutputStorageTypesTuple>> futures;
|
|
OutputStorageTypesTuple partial_res;
|
|
|
|
parallel_for(
|
|
scheduler,
|
|
mX_traced_polygons.cbegin(),
|
|
mX_traced_polygons.cend(),
|
|
fn_compute_inserted_faces,
|
|
partial_res, // output computed by master thread
|
|
futures);
|
|
|
|
const std::vector<std::vector<vd_t>>& faces_MASTER_THREAD_LOCAL = std::get<0>(partial_res);
|
|
|
|
auto merge_local_faces = [&mesh](const std::vector<std::vector<vd_t>>& faces_) {
|
|
for (std::vector<std::vector<vd_t>>::const_iterator face_iter = faces_.cbegin();
|
|
face_iter != faces_.cend();
|
|
++face_iter) {
|
|
const fd_t f = mesh.add_face(*face_iter);
|
|
MCUT_ASSERT(f != hmesh_t::null_face());
|
|
}
|
|
};
|
|
|
|
for (int i = 0; i < (int)futures.size(); ++i) {
|
|
std::future<OutputStorageTypesTuple>& f = futures[i];
|
|
MCUT_ASSERT(f.valid());
|
|
OutputStorageTypesTuple future_result = f.get(); // "get()" is a blocking function
|
|
|
|
const std::vector<std::vector<vd_t>>& faces_FUTURE = std::get<0>(future_result);
|
|
|
|
merge_local_faces(faces_FUTURE);
|
|
}
|
|
|
|
// merge faces computed by master thread at the end to maintain the same order
|
|
// of the traced polygons
|
|
merge_local_faces(faces_MASTER_THREAD_LOCAL);
|
|
} // endif of parallel scope
|
|
|
|
#else
|
|
// for each traced polygon
|
|
for (std::vector<std::vector<hd_t>>::const_iterator mX_traced_polygons_iter = mX_traced_polygons.cbegin();
|
|
mX_traced_polygons_iter != mX_traced_polygons.cend();
|
|
++mX_traced_polygons_iter) {
|
|
|
|
// const int polygon_idx = (int)std::distance(traced_polygons.cbegin(), traced_sm_polygon_iter);
|
|
const std::vector<hd_t>& mX_traced_polygon = *mX_traced_polygons_iter;
|
|
|
|
//
|
|
// gather polygon's vertices
|
|
//
|
|
|
|
std::vector<vd_t> polygon_vertices;
|
|
polygon_vertices.reserve(mX_traced_polygon.size());
|
|
|
|
// for each halfedge in polygon
|
|
for (std::vector<hd_t>::const_iterator mX_traced_polygon_halfedge_iter = mX_traced_polygon.cbegin();
|
|
mX_traced_polygon_halfedge_iter != mX_traced_polygon.cend();
|
|
++mX_traced_polygon_halfedge_iter) {
|
|
polygon_vertices.push_back(mesh.target(*mX_traced_polygon_halfedge_iter));
|
|
}
|
|
|
|
// insert face into halfedge data structure
|
|
const fd_t f = mesh.add_face(polygon_vertices);
|
|
|
|
// we have violated halfedge data structure construction
|
|
// rules probably because we are refering to a halfedge
|
|
// and its opposite in one polygon
|
|
MCUT_ASSERT(f != hmesh_t::null_face());
|
|
}
|
|
#endif
|
|
TIMESTACK_POP();
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// find connected components in "mesh"
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
// connected components
|
|
std::map<std::size_t, std::shared_ptr<hmesh_t>> ccID_to_mesh;
|
|
// location of each connected component w.r.t cut-mesh (above | below | undefined)
|
|
std::map<std::size_t, sm_frag_location_t> ccID_to_cs_descriptor;
|
|
// for each component, we have a map which relates the vertex descriptors (indices) in the
|
|
// auxilliary halfedge data structure "mesh" to the (local) vertex descriptors in
|
|
// the connected-component.
|
|
//
|
|
// the "X" in "...mX_..." stands for "0" or "1" depending on where the current function is called from!
|
|
// Before "m1" is created in "dispatch", X = "0". Afterwards, X == "1" to signify the fact that the
|
|
// input paramater called "in" (in this function) represents "m0" or "m1"
|
|
std::map<std::size_t, std::unordered_map<vd_t, vd_t>> ccID_to_mX_to_cc_vertex;
|
|
// std::map<std::size_t, std::unordered_map<vd_t, vd_t>> ccID_to_cc_to_mX_vertex;
|
|
std::map<std::size_t, std::vector<vd_t>> ccID_to_cc_to_mX_vertex;
|
|
// the vertex descriptors [in the cc] which are seam vertices!
|
|
std::map<std::size_t, std::vector<vd_t>> cc_to_seam_vertices;
|
|
// here we create a map to tag each polygon in "mesh" with the connected component it belongs to.
|
|
std::vector<int> fccmap;
|
|
|
|
TIMESTACK_PUSH("Extract CC: find connected components");
|
|
std::vector<int> cc_to_vertex_count;
|
|
std::vector<int> cc_to_face_count;
|
|
find_connected_components(
|
|
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
scheduler,
|
|
#endif
|
|
fccmap, mesh, cc_to_vertex_count, cc_to_face_count);
|
|
TIMESTACK_POP();
|
|
|
|
//
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Map vertex descriptors to each connected component
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
// NOTE: even if the number of connected components is one, we proceed anyway
|
|
// because each connected connected excludes unused vertices in "mesh"
|
|
|
|
TIMESTACK_PUSH("Extract CC: Map vertices");
|
|
|
|
// for each face in the auxilliary mesh (i.e. traced polygon)
|
|
for (face_array_iterator_t face_iter = mesh.faces_begin(); face_iter != mesh.faces_end(); ++face_iter) {
|
|
|
|
face_descriptor_t fd = *face_iter;
|
|
const int face_cc_id = SAFE_ACCESS(fccmap, fd); // get connected component of face
|
|
|
|
std::map<std::size_t, std::shared_ptr<hmesh_t>>::iterator ccID_to_mesh_fiter = ccID_to_mesh.find(face_cc_id);
|
|
if (ccID_to_mesh_fiter == ccID_to_mesh.end()) {
|
|
// create new mesh to store connected component
|
|
std::pair<std::map<std::size_t, std::shared_ptr<hmesh_t>>::iterator, bool> p = ccID_to_mesh.insert(std::make_pair(face_cc_id, std::shared_ptr<hmesh_t>(new hmesh_t)));
|
|
ccID_to_mesh_fiter = p.first;
|
|
}
|
|
|
|
std::shared_ptr<hmesh_t> cc_mesh = ccID_to_mesh_fiter->second;
|
|
|
|
std::map<std::size_t, std::unordered_map<vd_t, vd_t>>::iterator ccID_to_mX_to_cc_vertex_fiter = ccID_to_mX_to_cc_vertex.find(face_cc_id);
|
|
|
|
if (ccID_to_mX_to_cc_vertex_fiter == ccID_to_mX_to_cc_vertex.end()) {
|
|
// create new component descriptor map
|
|
std::pair<std::map<std::size_t, std::unordered_map<vd_t, vd_t>>::iterator, bool> p = ccID_to_mX_to_cc_vertex.insert(std::make_pair(face_cc_id, std::unordered_map<vd_t, vd_t>()));
|
|
ccID_to_mX_to_cc_vertex_fiter = p.first;
|
|
}
|
|
|
|
std::unordered_map<vd_t, vd_t>& mX_to_cc_vertex = ccID_to_mX_to_cc_vertex_fiter->second;
|
|
|
|
std::map<std::size_t, std::vector<vd_t>>::iterator ccID_to_cc_to_mX_vertex_fiter = ccID_to_cc_to_mX_vertex.find(face_cc_id);
|
|
|
|
if (ccID_to_cc_to_mX_vertex_fiter == ccID_to_cc_to_mX_vertex.end()) {
|
|
std::pair<std::map<std::size_t, std::vector<vd_t>>::iterator, bool> p = ccID_to_cc_to_mX_vertex.insert(std::make_pair(face_cc_id, std::vector<vd_t>()));
|
|
ccID_to_cc_to_mX_vertex_fiter = p.first;
|
|
}
|
|
|
|
std::vector<vd_t>& cc_to_mX_vertex = ccID_to_cc_to_mX_vertex_fiter->second;
|
|
|
|
std::map<std::size_t, std::vector<vd_t>>::iterator cc_to_seam_vertices_fiter = cc_to_seam_vertices.find(face_cc_id);
|
|
|
|
if (cc_to_seam_vertices_fiter == cc_to_seam_vertices.end()) {
|
|
std::pair<std::map<std::size_t, std::vector<vd_t>>::iterator, bool> p = cc_to_seam_vertices.insert(std::make_pair(face_cc_id, std::vector<vd_t>()));
|
|
cc_to_seam_vertices_fiter = p.first;
|
|
}
|
|
|
|
std::vector<vd_t>& cc_seam_vertices = cc_to_seam_vertices_fiter->second;
|
|
|
|
//
|
|
// Determine the location of the connected component w.r.t the cut-mesh (above/below/undefined)
|
|
//
|
|
|
|
// check if the current face is already marked as "below" (w.r.t the cut-mesh).
|
|
const bool cc_is_below_cs = std::binary_search(sm_polygons_below_cs.cbegin(), sm_polygons_below_cs.cend(), static_cast<int>(fd));
|
|
|
|
if (cc_is_below_cs) {
|
|
// try to save the fact that the current connected component is "below"
|
|
std::pair<std::map<std::size_t, sm_frag_location_t>::iterator, bool> p = ccID_to_cs_descriptor.insert(std::make_pair(face_cc_id, sm_frag_location_t::BELOW));
|
|
// if 1) insertion did not take place (connected component already registered), and
|
|
// 2) the existing connected component at that entry is marked as "above":
|
|
// --> partial cut: thus, the notion "above"/"below" is undefined
|
|
if (p.second == false && p.first->second == sm_frag_location_t::ABOVE) {
|
|
// polygon classed as both above and below cs
|
|
// this is because the connected component contains polygons which are both "above"
|
|
// and "below" the cutting surface (we have a partial cut)
|
|
p.first->second = sm_frag_location_t::UNDEFINED;
|
|
}
|
|
}
|
|
|
|
// check if connected component is marked as "above"
|
|
const bool cc_is_above_cs = std::binary_search(sm_polygons_above_cs.cbegin(), sm_polygons_above_cs.cend(), static_cast<int>(fd));
|
|
|
|
if (cc_is_above_cs) {
|
|
// try to save the fact that the current connected component is tagged "above"
|
|
std::pair<std::map<std::size_t, sm_frag_location_t>::iterator, bool> p = ccID_to_cs_descriptor.insert(std::make_pair(face_cc_id, sm_frag_location_t::ABOVE));
|
|
// if 1) insertion did not take place (connected component is already registered), and
|
|
// 2) the existing connected component at that entry is marked as "below":
|
|
//--> partial cut: connected component has polygon whch are both "above" and "below"
|
|
if (p.second == false && p.first->second == sm_frag_location_t::BELOW) {
|
|
p.first->second = sm_frag_location_t::UNDEFINED; // polygon classed as both above and below cs
|
|
}
|
|
}
|
|
|
|
//
|
|
// We now map the vertices of the current face from the auxilliary data
|
|
// structure "mesh" to the (local) connected-component
|
|
//
|
|
|
|
//#define EXTRACT_SEAM_HALFEDGES
|
|
|
|
#ifdef EXTRACT_SEAM_HALFEDGES
|
|
std::vector<halfedge_descriptor_t> cc_seam_halfedges;
|
|
const std::vector<halfedge_descriptor_t>& halfedges_on_face = mesh.get_halfedges_around_face(fd);
|
|
bool prev_vertex_belonged_to_seam = false; // previous in face
|
|
#endif
|
|
// for each vertex around the current face
|
|
const std::vector<vertex_descriptor_t> vertices_around_face = mesh.get_vertices_around_face(fd); // order according to "halfedges_on_face" (targets)
|
|
|
|
for (std::vector<vertex_descriptor_t>::const_iterator face_vertex_iter = vertices_around_face.cbegin();
|
|
face_vertex_iter != vertices_around_face.cend();
|
|
++face_vertex_iter) {
|
|
|
|
MCUT_ASSERT(ccID_to_mX_to_cc_vertex.find(face_cc_id) != ccID_to_mX_to_cc_vertex.cend());
|
|
|
|
// if vertex is not already mapped from "mesh" to connected component
|
|
if (mX_to_cc_vertex.find(*face_vertex_iter) == mX_to_cc_vertex.end()) {
|
|
|
|
// MCUT_ASSERT(ccID_to_mesh.find(face_cc_id) != ccID_to_mesh.cend());
|
|
|
|
// copy vertex from auxilliary data structure "mesh", add it into connected component mesh,
|
|
// and save the vertex's descriptor in the conected component mesh.
|
|
const vd_t cc_descriptor = cc_mesh->add_vertex(mesh.vertex(*face_vertex_iter));
|
|
|
|
// map vertex
|
|
mX_to_cc_vertex.insert(std::make_pair(*face_vertex_iter, cc_descriptor));
|
|
if (popuplate_vertex_maps) {
|
|
// SAFE_ACCESS(ccID_to_cc_to_mX_vertex, face_cc_id).insert(std::make_pair(cc_descriptor, *face_vertex_iter));
|
|
cc_to_mX_vertex.push_back(*face_vertex_iter);
|
|
}
|
|
// check if we need to save vertex as being a seam vertex
|
|
// std::vector<bool>::const_iterator fiter = mesh_vertex_to_seam_flag.find(*face_vertex_iter);
|
|
bool is_seam_vertex = (size_t)(*face_vertex_iter) < mesh_vertex_to_seam_flag.size() && SAFE_ACCESS(mesh_vertex_to_seam_flag, *face_vertex_iter); //(size_t)(*face_vertex_iter) < mesh_vertex_to_seam_flag.size(); //fiter != mesh_vertex_to_seam_flag.cend() && fiter->second == true;
|
|
|
|
if (is_seam_vertex) {
|
|
|
|
cc_seam_vertices.push_back(cc_descriptor);
|
|
#ifdef EXTRACT_SEAM_HALFEDGES
|
|
const uint32_t face_vertex_idx = std::distance(vertices_around_face.cbegin(), face_vertex_iter);
|
|
|
|
const bool is_first_face_vertex = (face_vertex_idx == 0);
|
|
bool have_seam_halfedge = prev_vertex_belonged_to_seam;
|
|
|
|
if (is_first_face_vertex) {
|
|
vd_t last_vtx_descr = (*(vertices_around_face.end() - 1));
|
|
bool last_vertex_is_seam_vertex = (size_t)(last_vtx_descr) < mesh_vertex_to_seam_flag.size() && SAFE_ACCESS(mesh_vertex_to_seam_flag, last_vtx_descr); //(size_t)(*face_vertex_iter) < mesh_vertex_to_seam_flag.size(); //fiter != mesh_vertex_to_seam_flag.cend() && fiter->second == true;
|
|
have_seam_halfedge = (last_vertex_is_seam_vertex);
|
|
}
|
|
|
|
if (have_seam_halfedge) {
|
|
const halfedge_descriptor_t seam_he = SAFE_ACCESS(halfedges_on_face, face_vertex_idx); // number of halfedge == number of vertices in face
|
|
cc_seam_halfedges.push_back(seam_he);
|
|
}
|
|
#endif
|
|
}
|
|
#ifdef EXTRACT_SEAM_HALFEDGES
|
|
prev_vertex_belonged_to_seam = is_seam_vertex;
|
|
#endif
|
|
}
|
|
}
|
|
} // for (face_array_iterator_t face_iter = mesh.faces_begin(); face_iter != mesh.faces_end(); ++face_iter)
|
|
|
|
TIMESTACK_POP();
|
|
|
|
// bool extractingSeams = (m1_num_vertices_after_srcmesh_partitioning == -1);
|
|
|
|
// stores a flag per connected component indicating whether we should
|
|
// keep this CC or throw it away, as per user flags.
|
|
std::map<size_t, bool> ccID_to_keepFlag;
|
|
for (std::map<size_t, std::shared_ptr<hmesh_t>>::const_iterator it = ccID_to_mesh.cbegin(); it != ccID_to_mesh.cend(); ++it) {
|
|
int ccID = (int)it->first;
|
|
std::map<std::size_t, sm_frag_location_t>::iterator fiter = ccID_to_cs_descriptor.find(ccID);
|
|
const bool isSeam = (fiter == ccID_to_cs_descriptor.cend()); // Seams have no notion of "location"
|
|
ccID_to_keepFlag[ccID] = isSeam || ((keep_fragments_above_cutmesh && fiter->second == sm_frag_location_t::ABOVE) || //
|
|
(keep_fragments_below_cutmesh && fiter->second == sm_frag_location_t::BELOW) || //
|
|
(keep_fragments_partially_cut && fiter->second == sm_frag_location_t::UNDEFINED));
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// TODO: shift the logic to add vertices into CC to here (like for face below)
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Insert faces into connected components using mapped vertex descriptors
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
std::map<size_t, std::vector<fd_t>> ccID_to_cc_to_mX_face;
|
|
for (std::map<size_t, std::shared_ptr<hmesh_t>>::const_iterator it = ccID_to_mesh.cbegin(); it != ccID_to_mesh.cend(); ++it) {
|
|
bool userWantsCC = SAFE_ACCESS(ccID_to_keepFlag, it->first);
|
|
|
|
if (!userWantsCC) {
|
|
continue;
|
|
}
|
|
|
|
ccID_to_cc_to_mX_face[it->first] = std::vector<fd_t>();
|
|
}
|
|
|
|
TIMESTACK_PUSH("Extract CC: Map faces");
|
|
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
{
|
|
typedef std::tuple<
|
|
std::vector<std::vector<vd_t>>, // remapped_faces, // using remapped cc descriptors
|
|
std::vector<int>, // the cc id of each remapped
|
|
std::vector<fd_t> // the mX mesh i.e. the halfedge data structure we call "mesh" that is remapped
|
|
>
|
|
OutputStorageTypesTuple;
|
|
typedef face_array_iterator_t InputStorageIteratorType;
|
|
|
|
auto fn_compute_remapped_cc_faces = [&](InputStorageIteratorType block_start_, InputStorageIteratorType block_end_) -> OutputStorageTypesTuple {
|
|
OutputStorageTypesTuple local_output;
|
|
const uint32_t num_elems = (uint32_t)std::distance(block_start_, block_end_);
|
|
std::vector<std::vector<vd_t>>& remapped_faces_LOCAL = std::get<0>(local_output);
|
|
remapped_faces_LOCAL.reserve(num_elems);
|
|
std::vector<int>& local_remapped_face_to_ccID = std::get<1>(local_output);
|
|
local_remapped_face_to_ccID.reserve(num_elems);
|
|
std::vector<fd_t>& local_remapped_face_to_mX_face = std::get<2>(local_output);
|
|
local_remapped_face_to_mX_face.reserve(num_elems);
|
|
for (face_array_iterator_t face_iter = block_start_; face_iter != block_end_; ++face_iter) {
|
|
face_descriptor_t fd = *face_iter;
|
|
const size_t cc_id = fccmap[fd]; // the connected component which contains the current face
|
|
|
|
bool userWantsCC = SAFE_ACCESS(ccID_to_keepFlag, cc_id);
|
|
|
|
if (!userWantsCC) {
|
|
continue;
|
|
}
|
|
|
|
remapped_faces_LOCAL.push_back(std::vector<vd_t>());
|
|
std::vector<vd_t>& remapped_face = remapped_faces_LOCAL.back(); // using remapped cc descriptors
|
|
local_remapped_face_to_ccID.push_back((int)cc_id);
|
|
local_remapped_face_to_mX_face.push_back(fd);
|
|
|
|
// std::map<size_t, std::vector<fd_t>>::iterator ccID_to_cc_to_mX_face_fiter = ccID_to_cc_to_mX_face.find(cc_id);
|
|
|
|
MCUT_ASSERT(ccID_to_cc_to_mX_face.find(cc_id) != ccID_to_cc_to_mX_face.cend());
|
|
// std::vector<fd_t> &cc_to_mX_face = ccID_to_cc_to_mX_face_fiter->second;
|
|
|
|
// std::map<std::size_t, std::unordered_map<vd_t, vd_t>>::iterator ccID_to_mX_to_cc_vertex_fiter = ;
|
|
MCUT_ASSERT(ccID_to_mX_to_cc_vertex.find(cc_id) != ccID_to_mX_to_cc_vertex.end());
|
|
std::unordered_map<vd_t, vd_t>& mX_to_cc_vertex = SAFE_ACCESS(ccID_to_mX_to_cc_vertex, cc_id);
|
|
|
|
// for each vertex around face
|
|
const std::vector<vertex_descriptor_t> vertices_around_face = mesh.get_vertices_around_face(fd);
|
|
|
|
for (std::vector<vertex_descriptor_t>::const_iterator face_vertex_iter = vertices_around_face.cbegin();
|
|
face_vertex_iter != vertices_around_face.cend();
|
|
++face_vertex_iter) {
|
|
MCUT_ASSERT(ccID_to_mX_to_cc_vertex.find(cc_id) != ccID_to_mX_to_cc_vertex.cend());
|
|
|
|
/*const*/ std::unordered_map<vd_t, vd_t>& vertex_map = mX_to_cc_vertex;
|
|
const vd_t m1_sm_descr = *face_vertex_iter;
|
|
|
|
MCUT_ASSERT(vertex_map.find(m1_sm_descr) != vertex_map.cend());
|
|
|
|
const vd_t cc_descr = SAFE_ACCESS(vertex_map, m1_sm_descr);
|
|
remapped_face.push_back(cc_descr);
|
|
}
|
|
}
|
|
|
|
return local_output;
|
|
};
|
|
|
|
std::vector<std::future<OutputStorageTypesTuple>> futures;
|
|
OutputStorageTypesTuple partial_res;
|
|
|
|
parallel_for(
|
|
scheduler,
|
|
mesh.faces_begin(),
|
|
mesh.faces_end(),
|
|
fn_compute_remapped_cc_faces,
|
|
partial_res, // output computed by master thread
|
|
futures);
|
|
|
|
// remapped_faces
|
|
const std::vector<std::vector<vd_t>>& remapped_faces_MASTER_THREAD_LOCAL = std::get<0>(partial_res);
|
|
const std::vector<int>& local_remapped_face_to_ccID_MASTER_THREAD_LOCAL = std::get<1>(partial_res);
|
|
const std::vector<fd_t>& local_remapped_face_to_mX_face_MASTER_THREAD_LOCAL = std::get<2>(partial_res);
|
|
|
|
auto merge_local_remapped_cc_faces = [](
|
|
const std::vector<std::vector<vd_t>>& remapped_faces_,
|
|
const std::vector<int>& local_remapped_face_to_ccID_,
|
|
const std::vector<fd_t>& local_remapped_face_to_mX_face_,
|
|
const bool popuplate_face_maps,
|
|
std::map<size_t, std::shared_ptr<hmesh_t>>& ccID_to_mesh,
|
|
std::map<size_t, std::vector<fd_t>>& ccID_to_cc_to_mX_face) {
|
|
for (std::vector<std::vector<vd_t>>::const_iterator remapped_face_iter = remapped_faces_.cbegin();
|
|
remapped_face_iter != remapped_faces_.cend();
|
|
++remapped_face_iter) {
|
|
uint32_t idx = (uint32_t)std::distance(remapped_faces_.cbegin(), remapped_face_iter);
|
|
uint32_t remapped_face_cc_id = SAFE_ACCESS(local_remapped_face_to_ccID_, idx);
|
|
|
|
MCUT_ASSERT(ccID_to_mesh.find(remapped_face_cc_id) != ccID_to_mesh.end());
|
|
|
|
std::shared_ptr<hmesh_t> cc_mesh = SAFE_ACCESS(ccID_to_mesh, remapped_face_cc_id);
|
|
fd_t f = cc_mesh->add_face(*remapped_face_iter); // insert the face
|
|
|
|
MCUT_ASSERT(f != hmesh_t::null_face());
|
|
|
|
if (popuplate_face_maps) {
|
|
// NOTE: "mX" refers to our halfedge data structure called "mesh" (see single threaded code)
|
|
std::vector<fd_t>& cc_to_mX_face = SAFE_ACCESS(ccID_to_cc_to_mX_face, remapped_face_cc_id);
|
|
MCUT_ASSERT((size_t)f == cc_to_mX_face.size() /*cc_to_mX_face.count(f) == 0*/);
|
|
const fd_t fd = local_remapped_face_to_mX_face_[idx];
|
|
// cc_to_mX_face[f] = fd;
|
|
cc_to_mX_face.push_back(fd);
|
|
}
|
|
}
|
|
};
|
|
|
|
merge_local_remapped_cc_faces(
|
|
remapped_faces_MASTER_THREAD_LOCAL,
|
|
local_remapped_face_to_ccID_MASTER_THREAD_LOCAL,
|
|
local_remapped_face_to_mX_face_MASTER_THREAD_LOCAL,
|
|
popuplate_face_maps,
|
|
ccID_to_mesh,
|
|
ccID_to_cc_to_mX_face);
|
|
|
|
// merge thread-local output into global data structures
|
|
for (int i = 0; i < (int)futures.size(); ++i) {
|
|
std::future<OutputStorageTypesTuple>& f = futures[i];
|
|
MCUT_ASSERT(f.valid());
|
|
OutputStorageTypesTuple future_result = f.get(); // "get()" is a blocking function
|
|
|
|
const std::vector<std::vector<vd_t>>& remapped_faces_FUTURE = std::get<0>(future_result);
|
|
const std::vector<int>& local_remapped_face_to_ccID_FUTURE = std::get<1>(future_result);
|
|
const std::vector<fd_t>& local_remapped_face_to_mX_face_FUTURE = std::get<2>(future_result);
|
|
|
|
merge_local_remapped_cc_faces(
|
|
remapped_faces_FUTURE,
|
|
local_remapped_face_to_ccID_FUTURE,
|
|
local_remapped_face_to_mX_face_FUTURE,
|
|
popuplate_face_maps,
|
|
ccID_to_mesh,
|
|
ccID_to_cc_to_mX_face);
|
|
}
|
|
|
|
} // end of parallel scope
|
|
#else
|
|
// for each face in the auxilliary data structure "mesh" (traced polygon)
|
|
for (face_array_iterator_t face_iter = mesh.faces_begin(); face_iter != mesh.faces_end(); ++face_iter) {
|
|
face_descriptor_t fd = *face_iter;
|
|
const size_t cc_id = SAFE_ACCESS(fccmap, fd); // the connected component which contains the current face
|
|
|
|
bool userWantsCC = SAFE_ACCESS(ccID_to_keepFlag, cc_id);
|
|
|
|
if (!userWantsCC) {
|
|
continue;
|
|
}
|
|
|
|
std::vector<vd_t> remapped_face; // using remapped cc descriptors
|
|
std::map<size_t, std::vector<fd_t>>::iterator ccID_to_cc_to_mX_face_fiter = ccID_to_cc_to_mX_face.find(cc_id);
|
|
MCUT_ASSERT(ccID_to_cc_to_mX_face_fiter != ccID_to_cc_to_mX_face.cend());
|
|
std::vector<fd_t>& cc_to_mX_face = ccID_to_cc_to_mX_face_fiter->second;
|
|
|
|
// std::map<std::size_t, std::unordered_map<vd_t, vd_t>>::iterator ccID_to_mX_to_cc_vertex_fiter = ;
|
|
MCUT_ASSERT(ccID_to_mX_to_cc_vertex.find(cc_id) != ccID_to_mX_to_cc_vertex.end());
|
|
std::unordered_map<vd_t, vd_t>& mX_to_cc_vertex = SAFE_ACCESS(ccID_to_mX_to_cc_vertex, cc_id);
|
|
|
|
// for each vertex around face
|
|
/*const*/ std::vector<vertex_descriptor_t> vertices_around_face = mesh.get_vertices_around_face(fd);
|
|
|
|
for (std::vector<vertex_descriptor_t>::/*const_*/ iterator face_vertex_iter = vertices_around_face.begin();
|
|
face_vertex_iter != vertices_around_face.end();
|
|
++face_vertex_iter) {
|
|
MCUT_ASSERT(ccID_to_mX_to_cc_vertex.find(cc_id) != ccID_to_mX_to_cc_vertex.cend());
|
|
|
|
/*const*/ std::unordered_map<vd_t, vd_t>& vertex_map = mX_to_cc_vertex;
|
|
const vd_t m1_sm_descr = *face_vertex_iter;
|
|
|
|
MCUT_ASSERT(vertex_map.find(m1_sm_descr) != vertex_map.cend());
|
|
|
|
const vd_t cc_descr = SAFE_ACCESS(vertex_map, m1_sm_descr);
|
|
remapped_face.push_back(cc_descr);
|
|
}
|
|
|
|
MCUT_ASSERT(ccID_to_mesh.find(cc_id) != ccID_to_mesh.end());
|
|
|
|
std::shared_ptr<hmesh_t> cc_mesh = SAFE_ACCESS(ccID_to_mesh, cc_id);
|
|
fd_t f = cc_mesh->add_face(remapped_face); // insert the face
|
|
|
|
MCUT_ASSERT(f != hmesh_t::null_face());
|
|
|
|
if (popuplate_face_maps) {
|
|
MCUT_ASSERT((size_t)f == cc_to_mX_face.size() /*cc_to_mX_face.count(f) == 0*/);
|
|
// cc_to_mX_face[f] = fd;
|
|
cc_to_mX_face.push_back(fd);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
TIMESTACK_POP();
|
|
|
|
// Note: at this stage we have our connected components (meshes) with their
|
|
// vertices and faces defined
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Save the output connected components marked with location
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
TIMESTACK_PUSH("Extract CC: save CCs with location properties");
|
|
|
|
// for each connected component
|
|
for (std::map<std::size_t, std::shared_ptr<hmesh_t>>::const_iterator cc_iter = ccID_to_mesh.cbegin();
|
|
cc_iter != ccID_to_mesh.cend();
|
|
++cc_iter) {
|
|
|
|
const std::size_t& cc_id = cc_iter->first;
|
|
bool userWantsCC = SAFE_ACCESS(ccID_to_keepFlag, cc_id);
|
|
|
|
if (!userWantsCC) {
|
|
continue;
|
|
}
|
|
|
|
const std::shared_ptr<hmesh_t> cc = cc_iter->second;
|
|
|
|
// The boolean is needed to prevent saving duplicate connected components into the vector "connected_components[cc_id]".
|
|
// This can happen because the current function is called for each new cut-mesh polygon that is stitched, during the
|
|
// polygon stitching phases. In the other times when the current function is called, we are guarranteed that
|
|
// "connected_components[cc_id]" is empty.
|
|
//
|
|
// The above has the implication that the newly stitched polygon (during the stitching phase) is added to just [one] of the
|
|
// discovered connected components (which are of a particular color tag), thus leaving the other connected components to be
|
|
// discovered as having exactly the same number of polygons as before since no new polygon has been added to them.
|
|
// So to prevent this connected component dupliction issue, a connected component is only added into "connected_components[cc_id]"
|
|
// if the following hold:
|
|
// 1) "connected_components[cc_id]" is empty (making the added connected component new and unique)
|
|
// 2) the most-recent connected component instance at "connected_components[cc_id].back()" has less faces (in which case, always differing by one)
|
|
// than the new connected component we wish to add i.e. "cc"
|
|
auto cc_fiter = connected_components.find(cc_id);
|
|
bool proceed_to_save_mesh = cc_fiter == connected_components.cend() || cc_fiter->second.back().first->number_of_faces() != cc->number_of_faces();
|
|
|
|
if (proceed_to_save_mesh) {
|
|
|
|
sm_frag_location_t location = sm_frag_location_t::UNDEFINED;
|
|
|
|
if (!sm_polygons_below_cs.empty() && !sm_polygons_above_cs.empty()) {
|
|
MCUT_ASSERT(ccID_to_cs_descriptor.find(cc_id) != ccID_to_cs_descriptor.cend());
|
|
location = SAFE_ACCESS(ccID_to_cs_descriptor, cc_id);
|
|
}
|
|
|
|
connected_component_info_t ccinfo;
|
|
ccinfo.location = location;
|
|
ccinfo.seam_vertices = std::move(SAFE_ACCESS(cc_to_seam_vertices, cc_id));
|
|
|
|
//
|
|
// Map vertex and face descriptors to original values in the input source- and cut-mesh
|
|
// For vertices it is only non-intersection points that have defined mapping otherwise
|
|
// the mapped-to value is undefined (hmesh_t::null_vertex())
|
|
//
|
|
|
|
const std::vector<vd_t>& cc_to_mX_vertex = SAFE_ACCESS(ccID_to_cc_to_mX_vertex, cc_id);
|
|
|
|
if (popuplate_vertex_maps) {
|
|
// map cc vertices to original input mesh
|
|
// -----------------------------------
|
|
ccinfo.data_maps.vertex_map.resize(cc->number_of_vertices());
|
|
for (vertex_array_iterator_t i = cc->vertices_begin(); i != cc->vertices_end(); ++i) {
|
|
const vd_t cc_descr = *i;
|
|
MCUT_ASSERT((size_t)cc_descr < cc_to_mX_vertex.size() /*cc_to_mX_vertex.count(cc_descr) == 1*/);
|
|
const vd_t mX_descr = SAFE_ACCESS(cc_to_mX_vertex, cc_descr);
|
|
|
|
// NOTE: "m1_to_m0_sm_ovtx_colored" contains only non-intersection points from the source mesh
|
|
// std::vector<vd_t>::const_iterator m1_to_m0_sm_ovtx_colored_fiter = m1_to_m0_sm_ovtx_colored.find(mX_descr);
|
|
|
|
bool is_m1_sm_overtex = (size_t)mX_descr < m1_to_m0_sm_ovtx_colored.size(); // m1_to_m0_sm_ovtx_colored_fiter != m1_to_m0_sm_ovtx_colored.cend();
|
|
vd_t m0_descr = hmesh_t::null_vertex(); // NOTE: two cut-mesh "m1" original vertices may map to one "m0" vertex (due to winding order duplication)
|
|
|
|
if (is_m1_sm_overtex) {
|
|
m0_descr = SAFE_ACCESS(m1_to_m0_sm_ovtx_colored, mX_descr); // m1_to_m0_sm_ovtx_colored_fiter->second;
|
|
} else if (!m1_to_m0_cm_ovtx_colored.empty()) { // are we in the stitching stage..? (calling with "m1")
|
|
// Lets search through the map "m1_to_m0_cm_ovtx_colored"
|
|
|
|
// NOTE: "m1_to_m0_cm_ovtx_colored" contains only non-intersection points from the cut mesh
|
|
std::unordered_map<vd_t, vd_t>::const_iterator m1_to_m0_cm_ovtx_colored_fiter = m1_to_m0_cm_ovtx_colored.find(mX_descr);
|
|
|
|
bool is_m1_cm_overtex = m1_to_m0_cm_ovtx_colored_fiter != m1_to_m0_cm_ovtx_colored.cend();
|
|
|
|
if (is_m1_cm_overtex) {
|
|
m0_descr = m1_to_m0_cm_ovtx_colored_fiter->second;
|
|
}
|
|
}
|
|
|
|
if (m0_descr == hmesh_t::null_vertex()) { // if still not found, then we are strictly "mX" polygons is "m0" polygons
|
|
m0_descr = mX_descr;
|
|
}
|
|
|
|
const bool vertex_is_in_input_mesh_or_is_intersection_point = (m0_descr != hmesh_t::null_vertex()); // i.e. is it an original vertex (its not an intersection point/along cut-path)
|
|
|
|
if (vertex_is_in_input_mesh_or_is_intersection_point) {
|
|
// std::map<vd_t, vd_t>::const_iterator m0_to_ps_vtx_fiter = m0_to_ps_vtx.find(m0_descr);
|
|
bool vertex_is_in_input_mesh = (int)m0_descr < (int)m0_to_ps_vtx.size(); // m0_to_ps_vtx_fiter != m0_to_ps_vtx.cend();
|
|
vd_t input_mesh_descr = hmesh_t::null_vertex(); // i.e. source-mesh or cut-mesh
|
|
|
|
if (vertex_is_in_input_mesh) {
|
|
// MCUT_ASSERT(m0_to_ps_vtx.count(m0_descr) == 1);
|
|
const vd_t ps_descr = SAFE_ACCESS(m0_to_ps_vtx, m0_descr); // m0_to_ps_vtx_fiter->second; // SAFE_ACCESS(m0_to_ps_vtx, m0_descr);
|
|
// we don't know whether it belongs to cut-mesh patch or source-mesh, so check
|
|
const bool is_cutmesh_vtx = ps_is_cutmesh_vertex(ps_descr, sm_vtx_cnt);
|
|
if (is_cutmesh_vtx) {
|
|
input_mesh_descr = SAFE_ACCESS(ps_to_cm_vtx, ps_descr);
|
|
// add an offset which allows users to deduce which birth/origin mesh (source or cut mesh) a vertex (map value) belongs to.
|
|
input_mesh_descr = static_cast<vd_t>(input_mesh_descr + sm_vtx_cnt);
|
|
} else { // source-mesh vertex
|
|
input_mesh_descr = SAFE_ACCESS(ps_to_sm_vtx, ps_descr);
|
|
}
|
|
}
|
|
|
|
MCUT_ASSERT(SAFE_ACCESS(ccinfo.data_maps.vertex_map, cc_descr) == hmesh_t::null_vertex() /*ccinfo.data_maps.vertex_map.count(cc_descr) == 0*/);
|
|
ccinfo.data_maps.vertex_map[cc_descr] = input_mesh_descr;
|
|
}
|
|
}
|
|
} // if (popuplate_vertex_maps) {
|
|
|
|
if (popuplate_face_maps) {
|
|
// map face to original input mesh
|
|
// -----------------------------------
|
|
MCUT_ASSERT(ccID_to_cc_to_mX_face.count(cc_id) == 1);
|
|
|
|
std::vector<fd_t>& cc_to_mX_face = SAFE_ACCESS(ccID_to_cc_to_mX_face, cc_id);
|
|
ccinfo.data_maps.face_map.resize(cc->number_of_faces());
|
|
for (face_array_iterator_t f = cc->faces_begin(); f != cc->faces_end(); ++f) {
|
|
const fd_t cc_descr = *f;
|
|
// account for the fact that the parameter "mX_traced_polygons" may contain only a subset of traced polygons
|
|
// need this to compute correct polygon index to access std::maps
|
|
// const fd_t cc_descr_offsetted(traced_polygons_base_offset + static_cast<int>(cc_descr));
|
|
MCUT_ASSERT((size_t)cc_descr < cc_to_mX_face.size() /*cc_to_mX_face.count(cc_descr) == 1*/);
|
|
const fd_t mX_descr = SAFE_ACCESS(cc_to_mX_face, cc_descr);
|
|
const fd_t offsetted_mX_descr(traced_polygons_base_offset + static_cast<int>(mX_descr)); // global traced polygon index
|
|
int m0_descr = -1;
|
|
|
|
if (m1_to_m0_face_colored.size() > 0) { // are we calling from during the patch stitching phase..?
|
|
const fd_t m1_descr = offsetted_mX_descr;
|
|
MCUT_ASSERT(m1_to_m0_face_colored.count(mX_descr) == 1);
|
|
m0_descr = SAFE_ACCESS(m1_to_m0_face_colored, m1_descr);
|
|
} else {
|
|
m0_descr = static_cast<int>(offsetted_mX_descr);
|
|
}
|
|
|
|
MCUT_ASSERT(m0_to_ps_face.count(m0_descr) == 1);
|
|
const fd_t ps_descr = SAFE_ACCESS(m0_to_ps_face, m0_descr); // every traced polygon can be mapped back to an input mesh polygon
|
|
fd_t input_mesh_descr = hmesh_t::null_face();
|
|
|
|
const bool from_cutmesh_face = ps_is_cutmesh_face(ps_descr, sm_face_count);
|
|
if (from_cutmesh_face) {
|
|
MCUT_ASSERT((int)ps_descr < (int)ps_to_cm_face.size());
|
|
input_mesh_descr = SAFE_ACCESS(ps_to_cm_face, ps_descr);
|
|
// add an offset which allows users to deduce which birth/origin mesh (source or cut mesh) a face (map value) belongs to.
|
|
input_mesh_descr = static_cast<fd_t>(input_mesh_descr + sm_face_count);
|
|
} else {
|
|
MCUT_ASSERT((int)ps_descr < (int)ps_to_sm_face.size());
|
|
input_mesh_descr = SAFE_ACCESS(ps_to_sm_face, ps_descr);
|
|
}
|
|
|
|
// map to input mesh face
|
|
MCUT_ASSERT(SAFE_ACCESS(ccinfo.data_maps.face_map, cc_descr) == hmesh_t::null_face() /* (ccinfo.data_maps.face_map.count(cc_descr) == 0*/);
|
|
ccinfo.data_maps.face_map[cc_descr] = input_mesh_descr;
|
|
}
|
|
} // if (popuplate_face_maps) {
|
|
|
|
connected_components[cc_id].emplace_back(cc, std::move(ccinfo));
|
|
}
|
|
}
|
|
TIMESTACK_POP();
|
|
|
|
return (mesh);
|
|
}
|
|
|
|
bool is_virtual_face(const fd_t& face)
|
|
{
|
|
return (face == hmesh_t::null_face());
|
|
}
|
|
|
|
/*
|
|
@brief: Given a list of sorted vertices which belong to a histogram bin, check that a
|
|
particular component (x, y, or z) of their coordinates is not the same amongst two or more vertices
|
|
*/
|
|
bool have_same_coordinate(
|
|
const std::vector<std::pair<vd_t, vec3>>& bin_vertices_sorted,
|
|
const int coordinate_index = 0 // 0 = x, 1 = y, 2 = z component
|
|
)
|
|
{
|
|
// for each vertex, compare to all others in vector (compare by given component)
|
|
bool is_duplicate = false;
|
|
for (std::vector<std::pair<vd_t, vec3>>::const_iterator i = bin_vertices_sorted.begin(); i != bin_vertices_sorted.end(); ++i) {
|
|
const vec3& vertex_i_coordinates = i->second;
|
|
const double vertex_i_coordinate = vertex_i_coordinates[coordinate_index];
|
|
bool vertex_i_coordinate_is_duplicate = false;
|
|
|
|
for (std::vector<std::pair<vd_t, vec3>>::const_iterator j = bin_vertices_sorted.begin(); j != bin_vertices_sorted.end(); ++j) {
|
|
if (j == i) {
|
|
continue; // same vertex, skip
|
|
}
|
|
|
|
const vec3& vertex_j_coordinates = j->second;
|
|
const double vertex_j_coordinate = vertex_j_coordinates[coordinate_index];
|
|
vertex_i_coordinate_is_duplicate = (vertex_i_coordinate == vertex_j_coordinate);
|
|
|
|
if (vertex_i_coordinate_is_duplicate) {
|
|
is_duplicate = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (is_duplicate) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return is_duplicate;
|
|
}
|
|
|
|
// TODO: replace code parts that use "m0_ivtx_to_intersection_registry_entry" with calls to this
|
|
// function which is much cheaper
|
|
inline bool m0_is_polygon_boundary_halfedge(const hd_t& h, uint32_t m0_num_cutpath_halfedges)
|
|
{
|
|
return (uint32_t)h >= m0_num_cutpath_halfedges;
|
|
}
|
|
|
|
inline bool m0_is_polygon_boundary_edge(const ed_t& e, uint32_t m0_num_cutpath_edges)
|
|
{
|
|
return (uint32_t)e >= m0_num_cutpath_edges;
|
|
}
|
|
|
|
// point an intersection halfedge to the correct instance of an intersection point
|
|
vd_t resolve_intersection_point_descriptor(
|
|
const hmesh_t& ps,
|
|
const hmesh_t& m0,
|
|
hmesh_t& m1,
|
|
const hd_t& m0_h,
|
|
const vd_t& m0_h_tgt,
|
|
const vd_t& m1_h_tgt,
|
|
const bool m0_h_is_ox,
|
|
/*const*/ std::vector<std::vector<int>>& m0_h_to_ply,
|
|
/*const*/ std::unordered_map<vd_t, std::vector<hd_t>>& ivtx_to_incoming_hlist,
|
|
/*const*/ std::unordered_map<hd_t, bool>& m0_sm_ihe_to_flag,
|
|
const std::vector<std::pair<ed_t, fd_t>>& m0_ivtx_to_intersection_registry_entry,
|
|
/*const*/ std::unordered_map<hd_t, hd_t>& m0_to_m1_ihe,
|
|
// const std::map<vd_t, vd_t> &m0_to_ps_vtx,
|
|
const std::vector<vd_t>& m0_to_ps_vtx,
|
|
const int ps_vtx_cnt,
|
|
const int sm_vtx_cnt,
|
|
const int sm_face_count,
|
|
const int m0_num_cutpath_halfedges)
|
|
{
|
|
// the descriptor instance we want to return
|
|
vd_t resolved_inst = m1_h_tgt;
|
|
|
|
// First, we get list of all other halfedges in (in "m0") whose target-vertex
|
|
// is the same as the target of the current halfedge
|
|
const std::vector<hd_t>& incoming = SAFE_ACCESS(ivtx_to_incoming_hlist, m0_h_tgt);
|
|
|
|
// the minimum number of halfedges whose target is "m0_h_tgt"
|
|
// this "minimum" case come from interior edges
|
|
MCUT_ASSERT(incoming.size() >= 2);
|
|
|
|
// Second, we will now filter "incoming" (those pointing to "m0_h_tgt") by
|
|
// keeping only the halfedges which are:
|
|
// 1) Processed/transformed (so that we can use it to infer what to do with "resolved_inst")
|
|
// 2) are incident to a traced polygon, and
|
|
// 3) used by a traced polygon of the src-mesh
|
|
//
|
|
// The remaining halfedges will be the ones we can use to infer the correct value of "resolved_inst"
|
|
|
|
std::vector<hd_t> halfedges_across_cut_path = incoming;
|
|
|
|
// for each halfedge across the cut-path
|
|
for (std::vector<hd_t>::iterator halfedge_across_cut_path_iter = halfedges_across_cut_path.begin();
|
|
halfedge_across_cut_path_iter != halfedges_across_cut_path.end();) {
|
|
|
|
const vd_t s = m0.source(*halfedge_across_cut_path_iter);
|
|
const vd_t t = m0.target(*halfedge_across_cut_path_iter);
|
|
const bool s_is_ivtx = m0_is_intersection_point(s, ps_vtx_cnt);
|
|
const bool t_is_ivtx = m0_is_intersection_point(t, ps_vtx_cnt);
|
|
|
|
// check if the halfedge is only next to the cut-mesh
|
|
|
|
const bool is_ox_cs_h = (!s_is_ivtx && ps_is_cutmesh_vertex(SAFE_ACCESS(m0_to_ps_vtx, s), sm_vtx_cnt));
|
|
const bool is_xo_cs_h = (!t_is_ivtx && ps_is_cutmesh_vertex(SAFE_ACCESS(m0_to_ps_vtx, t), sm_vtx_cnt));
|
|
bool is_strictly_cs_h = is_ox_cs_h || is_xo_cs_h; // check if halfedge is used only by a cut-surface polygon
|
|
const bool is_xx = s_is_ivtx && t_is_ivtx;
|
|
|
|
if (!is_strictly_cs_h && is_xx) {
|
|
#if 0
|
|
//const hd_t s_ps_h = SAFE_ACCESS(m0_ivtx_to_ps_edge, s);
|
|
//const hd_t t_ps_h = SAFE_ACCESS(m0_ivtx_to_ps_edge, t);
|
|
MCUT_ASSERT((size_t)s - ps_vtx_cnt < m0_ivtx_to_intersection_registry_entry.size() /*m0_ivtx_to_intersection_registry_entry.find(s) != m0_ivtx_to_intersection_registry_entry.cend()*/);
|
|
const ed_t s_ps_e = SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, s - ps_vtx_cnt).first; // SAFE_ACCESS(m0_ivtx_to_ps_edge, s); // ps.edge(s_ps_h);
|
|
MCUT_ASSERT((size_t)t - ps_vtx_cnt < m0_ivtx_to_intersection_registry_entry.size() /*m0_ivtx_to_intersection_registry_entry.find(t) != m0_ivtx_to_intersection_registry_entry.cend()*/);
|
|
const ed_t t_ps_e = SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, t - ps_vtx_cnt).first; //SAFE_ACCESS(m0_ivtx_to_ps_edge, t); // ps.edge(t_ps_h);
|
|
#else
|
|
const bool is_boundary_halfedge = m0_is_polygon_boundary_halfedge(
|
|
*halfedge_across_cut_path_iter,
|
|
m0_num_cutpath_halfedges);
|
|
#endif
|
|
const bool oh_is_exterior = is_boundary_halfedge; //(s_ps_e == t_ps_e); // lays on exterior of ps polygon
|
|
|
|
if (oh_is_exterior) {
|
|
MCUT_ASSERT((size_t)s - ps_vtx_cnt < m0_ivtx_to_intersection_registry_entry.size() /*m0_ivtx_to_intersection_registry_entry.find(s) != m0_ivtx_to_intersection_registry_entry.cend()*/);
|
|
const ed_t s_ps_e = SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, (std::size_t)s - ps_vtx_cnt).first; // SAFE_ACCESS(m0_ivtx_to_ps_edge, s); // ps.edge(s_ps_h);
|
|
|
|
const hd_t s_ps_h0 = ps.halfedge(s_ps_e, 0); // could alternatively use t_ps_e since both he's are part of same edge
|
|
fd_t incident_face = ps.face(s_ps_h0);
|
|
|
|
if (incident_face == hmesh_t::null_face()) {
|
|
const hd_t s_ps_h1 = ps.halfedge(s_ps_e, 1);
|
|
incident_face = ps.face(s_ps_h1);
|
|
MCUT_ASSERT(incident_face != hmesh_t::null_face());
|
|
}
|
|
|
|
// TODO: use "ps_is_cutmesh_vertex" since it will require using much less queries on ps
|
|
is_strictly_cs_h = ps_is_cutmesh_face(incident_face, sm_face_count);
|
|
}
|
|
}
|
|
|
|
// if
|
|
// 1) halfedge strictly belongs to the cut-mesh, OR
|
|
// 2) halfedge is not used for tracing, OR
|
|
// 3) halfedge has not been processed
|
|
if (is_strictly_cs_h || //
|
|
SAFE_ACCESS(m0_h_to_ply, *halfedge_across_cut_path_iter).size() == 0 /*m0_h_to_ply.find(*halfedge_across_cut_path_iter) == m0_h_to_ply.end()*/ || //
|
|
SAFE_ACCESS(m0_sm_ihe_to_flag, *halfedge_across_cut_path_iter) == false) { // is halfedge incident to a traced polygon and is it processed..?
|
|
halfedge_across_cut_path_iter = halfedges_across_cut_path.erase(halfedge_across_cut_path_iter);
|
|
} else {
|
|
++halfedge_across_cut_path_iter; // next
|
|
}
|
|
}
|
|
|
|
// there exists not transformed halfedges connected to the current halfedge
|
|
if (halfedges_across_cut_path.empty()) {
|
|
return resolved_inst; // return the original descriptor
|
|
}
|
|
|
|
// At this, point we have found a number of halfedges which share "m0_h_tgt"
|
|
// with the current halfedge. So we need to decide what value (instance) of
|
|
// "m0_h_tgt" we should assign "resolved_inst"
|
|
|
|
// We classify "halfedges_across_cut_path" into two sets:
|
|
// 1) "halfedges_on_same_side" (... as m0_h )
|
|
// 2) "halfedges_across_cut_path" (other-side)
|
|
//
|
|
|
|
std::vector<hd_t> halfedges_on_same_side;
|
|
|
|
if (m0_h_is_ox) {
|
|
//
|
|
// check if the opposite halfedge has been transformed
|
|
//
|
|
|
|
// get opposite halfedge of the current halfedge (m0_h)
|
|
const hd_t opp = m0.opposite(m0_h);
|
|
|
|
if (SAFE_ACCESS(m0_h_to_ply, opp).size() > 0 /*m0_h_to_ply.find(opp) != m0_h_to_ply.end()*/) { // was the opposite halfedge used to trace a polygon
|
|
// get the previous of the opposite halfedge (because it is one of the "incoming" halfedges)
|
|
const hd_t prv_opp = m0.prev(opp);
|
|
|
|
if (SAFE_ACCESS(m0_sm_ihe_to_flag, prv_opp)) { // is halfedge processed
|
|
halfedges_on_same_side.push_back(prv_opp);
|
|
// prv_opp is guarranteed to be in halfedges_on_same_side becz halfedges_across_cut_path is simply a vec of all incoming hes
|
|
halfedges_across_cut_path.erase(std::find(halfedges_across_cut_path.begin(), halfedges_across_cut_path.end(), prv_opp));
|
|
}
|
|
}
|
|
} else if (SAFE_ACCESS(m0_h_to_ply, m0_h).size() == 0 /*m0_h_to_ply.find(m0_h) == m0_h_to_ply.end()*/) // edge-case when src-mesh is not watertight (e.g. test 21)
|
|
{
|
|
MCUT_ASSERT(halfedges_across_cut_path.size() == 1);
|
|
const hd_t& h = halfedges_across_cut_path.front();
|
|
const hd_t& h_proc = SAFE_ACCESS(m0_to_m1_ihe, h);
|
|
vd_t h_tgt = m1.target(h_proc);
|
|
const vd_t tgt_copy = m1.add_vertex(m1.vertex(h_tgt)); // make a copy
|
|
|
|
resolved_inst = tgt_copy;
|
|
} else { // then halfedge is either xx or xo (see the conditions with which function is called)
|
|
|
|
const hd_t nxt = m0.next(m0_h);
|
|
const hd_t opp_nxt = m0.opposite(nxt);
|
|
|
|
// MCUT_ASSERT(opp_nxt != hmesh_t::null_halfedge());
|
|
|
|
// if halfedge incident to traced polygon and is it processed
|
|
if (SAFE_ACCESS(m0_h_to_ply, opp_nxt).size() > 0 /*m0_h_to_ply.find(opp_nxt) != m0_h_to_ply.end()*/ && SAFE_ACCESS(m0_sm_ihe_to_flag, opp_nxt)) {
|
|
|
|
const vd_t nxt_src = m0_h_tgt; // i.e. m0.source(nxt);
|
|
const vd_t nxt_tgt = m0.target(nxt);
|
|
const bool nxt_src_is_itvx = m0_is_intersection_point(nxt_src, ps_vtx_cnt);
|
|
const bool nxt_tgt_is_itvx = m0_is_intersection_point(nxt_tgt, ps_vtx_cnt);
|
|
const bool nxt_is_xx = nxt_src_is_itvx && nxt_tgt_is_itvx;
|
|
bool on_same_side = true;
|
|
|
|
if (nxt_is_xx) {
|
|
#if 0
|
|
//const hd_t nxt_src_ps_h = SAFE_ACCESS(m0_ivtx_to_ps_edge, nxt_src);
|
|
//const hd_t nxt_tgt_ps_h = SAFE_ACCESS(m0_ivtx_to_ps_edge, nxt_tgt);
|
|
MCUT_ASSERT((size_t)nxt_src - ps_vtx_cnt < m0_ivtx_to_intersection_registry_entry.size() /*m0_ivtx_to_intersection_registry_entry.find(nxt_src) != m0_ivtx_to_intersection_registry_entry.cend()*/);
|
|
const ed_t nxt_src_ps_e = SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, nxt_src - ps_vtx_cnt).first; // SAFE_ACCESS(m0_ivtx_to_ps_edge, nxt_src); // ps.edge(nxt_src_ps_h);
|
|
MCUT_ASSERT((size_t)nxt_tgt - ps_vtx_cnt < m0_ivtx_to_intersection_registry_entry.size() /*m0_ivtx_to_intersection_registry_entry.find(nxt_tgt) != m0_ivtx_to_intersection_registry_entry.cend()*/);
|
|
const ed_t nxt_tgt_ps_e = SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, nxt_tgt - ps_vtx_cnt).first; // SAFE_ACCESS(m0_ivtx_to_ps_edge, nxt_tgt); // ps.edge(nxt_tgt_ps_h);
|
|
#else
|
|
const bool is_boundary_halfedge = m0_is_polygon_boundary_halfedge(
|
|
nxt,
|
|
m0_num_cutpath_halfedges);
|
|
#endif
|
|
const bool nxt_is_exterior = is_boundary_halfedge; //(nxt_src_ps_e == nxt_tgt_ps_e); // lays on exterior of ps polygon
|
|
|
|
on_same_side = nxt_is_exterior;
|
|
}
|
|
|
|
if (on_same_side) {
|
|
halfedges_on_same_side.push_back(opp_nxt);
|
|
halfedges_across_cut_path.erase(std::find(halfedges_across_cut_path.begin(), halfedges_across_cut_path.end(), opp_nxt));
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Decide what to do with target(h) i.e. determine the correct value for "resolved_inst"
|
|
//
|
|
|
|
if (!halfedges_on_same_side.empty()) { // do we already have a halfedge on the [same side] which is tranformed...?
|
|
const hd_t& ss_h = halfedges_on_same_side.front(); // we can retrieve any one
|
|
const hd_t& ss_h_proc = SAFE_ACCESS(m0_to_m1_ihe, ss_h); // m1 version
|
|
resolved_inst = m1.target(ss_h_proc); // update reference
|
|
} else { // do we already have a halfedge on the [other side] which is tranformed...?
|
|
|
|
MCUT_ASSERT(!halfedges_across_cut_path.empty());
|
|
|
|
const hd_t& h = halfedges_across_cut_path.front();
|
|
MCUT_ASSERT(m0_to_m1_ihe.find(h) != m0_to_m1_ihe.cend());
|
|
const hd_t& h_proc = SAFE_ACCESS(m0_to_m1_ihe, h);
|
|
MCUT_ASSERT((uint32_t)h_proc < (uint32_t)m1.number_of_halfedges());
|
|
vd_t h_tgt = m1.target(h_proc);
|
|
MCUT_ASSERT((uint32_t)h_tgt < (uint32_t)m1.number_of_vertices());
|
|
const vec3 vertex = m1.vertex(h_tgt);
|
|
const vd_t tgt_copy = m1.add_vertex(vertex); // make a copy
|
|
|
|
resolved_inst = tgt_copy;
|
|
}
|
|
|
|
return resolved_inst;
|
|
};
|
|
|
|
inline std::vector<fd_t> ps_get_ivtx_registry_entry_faces(const hmesh_t& ps, const std::pair<ed_t, fd_t>& ivtx_registry_entry)
|
|
{
|
|
const hd_t h0 = ps.halfedge(ivtx_registry_entry.first, 0);
|
|
const hd_t h1 = ps.halfedge(ivtx_registry_entry.first, 1);
|
|
const fd_t h0_face = ps.face(h0);
|
|
const fd_t h1_face = ps.face(h1);
|
|
|
|
return { ivtx_registry_entry.second, h0_face, h1_face };
|
|
}
|
|
|
|
//
|
|
// update the m0 edges incident on two given intersecting faces of the polygon soup mesh
|
|
// "incident" just means that the edge will be used to clip the face in question.
|
|
void update_neighouring_ps_iface_m0_edge_list(
|
|
const vd_t& src_vertex,
|
|
const vd_t& tgt_vertex,
|
|
const hmesh_t& ps,
|
|
const fd_t sm_face,
|
|
const fd_t cs_face,
|
|
const std::vector<std::pair<ed_t, fd_t>>& m0_ivtx_to_intersection_registry_entry,
|
|
std::unordered_map<fd_t, std::vector<ed_t>>& ps_iface_to_m0_edge_list,
|
|
const std::vector<ed_t>& m0_cutpath_edges)
|
|
{
|
|
const int ps_vtx_cnt = ps.number_of_vertices();
|
|
// for all neighbours of "sm_face" and "cs_face"
|
|
// if the face is in the registry of src and tgt vertex
|
|
// get edge list of face
|
|
// if list does not already contain new edge
|
|
// add new edge to list
|
|
|
|
std::vector<fd_t> neighbouring_ifaces;
|
|
for (auto neigh_face : { sm_face, cs_face }) {
|
|
const std::vector<face_descriptor_t> faces_around_face = ps.get_faces_around_face(neigh_face);
|
|
neighbouring_ifaces.insert(neighbouring_ifaces.end(), faces_around_face.cbegin(), faces_around_face.cend());
|
|
}
|
|
|
|
MCUT_ASSERT((size_t)src_vertex - ps_vtx_cnt < m0_ivtx_to_intersection_registry_entry.size() /*m0_ivtx_to_intersection_registry_entry.find(src_vertex) != m0_ivtx_to_intersection_registry_entry.cend()*/);
|
|
const std::pair<ed_t, fd_t>& src_vertex_ipair = SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, (std::size_t)src_vertex - ps_vtx_cnt);
|
|
const std::vector<fd_t> src_registry = ps_get_ivtx_registry_entry_faces(ps, src_vertex_ipair); // SAFE_ACCESS(m0_ivtx_to_ps_faces, src_vertex);
|
|
|
|
MCUT_ASSERT((size_t)tgt_vertex - ps_vtx_cnt < m0_ivtx_to_intersection_registry_entry.size() /*m0_ivtx_to_intersection_registry_entry.find(tgt_vertex) != m0_ivtx_to_intersection_registry_entry.cend()*/);
|
|
const std::pair<ed_t, fd_t>& tgt_vertex_ipair = SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, (std::size_t)tgt_vertex - ps_vtx_cnt);
|
|
const std::vector<fd_t> tgt_registry = ps_get_ivtx_registry_entry_faces(ps, tgt_vertex_ipair); // SAFE_ACCESS(m0_ivtx_to_ps_faces, tgt_vertex);
|
|
|
|
// for each face that is a neighbour to either sm-face or cm-face
|
|
for (std::vector<fd_t>::const_iterator neigh_face_it = neighbouring_ifaces.cbegin();
|
|
neigh_face_it != neighbouring_ifaces.cend();
|
|
++neigh_face_it) {
|
|
const fd_t iface = *neigh_face_it;
|
|
|
|
MCUT_ASSERT(iface != sm_face && iface != cs_face);
|
|
|
|
const bool in_src_reg = std::find(src_registry.cbegin(), src_registry.cend(), iface) != src_registry.cend();
|
|
const bool in_tgt_reg = std::find(tgt_registry.cbegin(), tgt_registry.cend(), iface) != tgt_registry.cend();
|
|
|
|
if (in_src_reg && in_tgt_reg) {
|
|
std::unordered_map<fd_t, std::vector<ed_t>>::iterator fiter = ps_iface_to_m0_edge_list.find(iface);
|
|
bool iface_associated_with_some_edges = true;
|
|
if (fiter == ps_iface_to_m0_edge_list.cend()) {
|
|
// insert
|
|
std::pair<std::unordered_map<fd_t, std::vector<ed_t>>::iterator, bool> p = ps_iface_to_m0_edge_list.insert(std::make_pair(iface, std::vector<ed_t>()));
|
|
MCUT_ASSERT(p.second == true);
|
|
fiter = p.first;
|
|
iface_associated_with_some_edges = false;
|
|
}
|
|
|
|
MCUT_ASSERT(fiter != ps_iface_to_m0_edge_list.cend());
|
|
|
|
std::vector<ed_t>& iface_m0_edge_list = fiter->second;
|
|
|
|
bool associate_iface_with_edge = true;
|
|
if (iface_associated_with_some_edges) {
|
|
bool edge_already_associated_with_iface = std::find(iface_m0_edge_list.cbegin(), iface_m0_edge_list.cend(), m0_cutpath_edges.back()) != iface_m0_edge_list.cend();
|
|
associate_iface_with_edge = !(edge_already_associated_with_iface);
|
|
}
|
|
|
|
if (associate_iface_with_edge) {
|
|
iface_m0_edge_list.push_back(m0_cutpath_edges.back());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
typedef std::vector<hd_t> traced_polygon_t;
|
|
|
|
bool mesh_is_closed(
|
|
#if 0 //defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
thread_pool& scheduler,
|
|
#endif
|
|
const hmesh_t& mesh)
|
|
{
|
|
bool all_halfedges_incident_to_face = true;
|
|
#if 0 // defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
{
|
|
printf("mesh=%d\n", (int)mesh.number_of_halfedges());
|
|
all_halfedges_incident_to_face = parallel_find_if(
|
|
scheduler,
|
|
mesh.halfedges_begin(),
|
|
mesh.halfedges_end(),
|
|
[&](hd_t h) {
|
|
const fd_t f = mesh.face(h);
|
|
return (f == hmesh_t::null_face());
|
|
})
|
|
== mesh.halfedges_end();
|
|
}
|
|
#else
|
|
for (halfedge_array_iterator_t iter = mesh.halfedges_begin(); iter != mesh.halfedges_end(); ++iter) {
|
|
const fd_t f = mesh.face(*iter);
|
|
if (f == hmesh_t::null_face()) {
|
|
all_halfedges_incident_to_face = false;
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
return all_halfedges_incident_to_face;
|
|
}
|
|
|
|
// TODO: thsi can be improved by comparing based on the largest component of the difference vector
|
|
// sort points along a straight line
|
|
std::vector<vd_t> linear_projection_sort(const std::vector<std::pair<vd_t, vec3>>& points)
|
|
{
|
|
/*
|
|
1. pick one point as the origin
|
|
2. pick any other point as the vector destination point
|
|
3. compute normalize vector from <1> to <2>
|
|
4. for each point in list of all points
|
|
a) compute unnormalized vector from <1> to <4>
|
|
b) project a) onto 3) using scalar product, and save result in list
|
|
5. sort points according to scalar products values from <4b>
|
|
*/
|
|
MCUT_ASSERT(points.size() >= 2);
|
|
const std::vector<std::pair<vd_t, vec3>>::const_iterator origin = points.cbegin();
|
|
const std::vector<std::pair<vd_t, vec3>>::const_iterator dst = points.cbegin() + 1;
|
|
|
|
vec3 orig_to_dst_vec = normalize(origin->second - dst->second);
|
|
|
|
std::vector<std::pair<vd_t, double>> point_projections;
|
|
|
|
for (std::vector<std::pair<vd_t, vec3>>::const_iterator i = points.cbegin(); i != points.cend(); ++i) {
|
|
vec3 orig_to_point_vec = (origin->second - i->second);
|
|
point_projections.emplace_back(i->first, dot_product(orig_to_point_vec, orig_to_dst_vec));
|
|
}
|
|
|
|
std::sort(point_projections.begin(), point_projections.end(),
|
|
[&](const std::pair<vd_t, double>& a, const std::pair<vd_t, double>& b) {
|
|
return a.second < b.second;
|
|
});
|
|
|
|
std::vector<vd_t> sorted_descriptors;
|
|
for (std::vector<std::pair<vd_t, double>>::const_iterator i = point_projections.cbegin(); i != point_projections.cend(); ++i) {
|
|
sorted_descriptors.push_back(i->first);
|
|
}
|
|
|
|
return sorted_descriptors;
|
|
}
|
|
|
|
//
|
|
// entry point
|
|
//
|
|
void dispatch(output_t& output, const input_t& input)
|
|
{
|
|
lmsg();
|
|
|
|
TIMESTACK_PUSH(__FUNCTION__);
|
|
|
|
logger_t& lg = output.logger;
|
|
logger_ptr = &output.logger;
|
|
lg.reset();
|
|
lg.set_verbose(input.verbose);
|
|
|
|
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
output.status.store(status_t::SUCCESS);
|
|
#endif
|
|
|
|
const hmesh_t& sm = (*input.src_mesh);
|
|
const hmesh_t& cs = (*input.cut_mesh);
|
|
|
|
if (input.verbose) {
|
|
dump_mesh(sm, "src-mesh");
|
|
dump_mesh(cs, "cut-mesh");
|
|
}
|
|
|
|
const int sm_vtx_cnt = sm.number_of_vertices();
|
|
const int sm_face_count = sm.number_of_faces();
|
|
const int cs_face_count = cs.number_of_faces();
|
|
const int cs_vtx_count = cs.number_of_vertices();
|
|
|
|
TIMESTACK_PUSH("Check source mesh is closed");
|
|
const bool sm_is_watertight = mesh_is_closed(
|
|
#if 0 //defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
*input.scheduler,
|
|
#endif
|
|
sm);
|
|
|
|
TIMESTACK_POP();
|
|
|
|
TIMESTACK_PUSH("Check cut mesh is closed");
|
|
const bool cm_is_watertight = mesh_is_closed(
|
|
#if 0// defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
*input.scheduler,
|
|
#endif
|
|
cs);
|
|
|
|
TIMESTACK_POP();
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// create polygon soup
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
TIMESTACK_PUSH("Create ps");
|
|
hmesh_t ps = sm; // copy
|
|
|
|
ps.reserve_for_additional_elements(cs.number_of_vertices()); // hint
|
|
|
|
// std::map<vd_t, vd_t> ps_to_sm_vtx;
|
|
std::vector<vd_t> ps_to_sm_vtx((std::size_t)sm_vtx_cnt + cs.number_of_vertices());
|
|
#if 0
|
|
std::iota(std::begin(ps_to_sm_vtx), std::end(ps_to_sm_vtx), vd_t(0));
|
|
#else
|
|
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
{
|
|
auto fn_iota = [&](vertex_array_iterator_t block_start_, vertex_array_iterator_t block_end_) {
|
|
for (vertex_array_iterator_t v = block_start_; v != block_end_; ++v) {
|
|
ps_to_sm_vtx[*v] = *v;
|
|
}
|
|
};
|
|
|
|
parallel_for(
|
|
*input.scheduler,
|
|
sm.vertices_begin(),
|
|
sm.vertices_end(),
|
|
fn_iota);
|
|
}
|
|
#else
|
|
for (vertex_array_iterator_t v = sm.vertices_begin(); v != sm.vertices_end(); ++v) {
|
|
ps_to_sm_vtx[*v] = *v; // one to one mapping since ps is initially a copy of sm!
|
|
}
|
|
#endif
|
|
#endif
|
|
// std::map<fd_t, fd_t> ps_to_sm_face;
|
|
std::vector<fd_t> ps_to_sm_face((std::size_t)sm.number_of_faces() + cs.number_of_faces());
|
|
#if 0
|
|
std::iota(std::begin(ps_to_sm_face), std::end(ps_to_sm_face), fd_t(0));
|
|
#else
|
|
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
{
|
|
auto fn_iota = [&](face_array_iterator_t block_start_, face_array_iterator_t block_end_) {
|
|
for (face_array_iterator_t f = block_start_; f != block_end_; ++f) {
|
|
ps_to_sm_face[*f] = *f;
|
|
}
|
|
};
|
|
|
|
parallel_for(
|
|
*input.scheduler,
|
|
sm.faces_begin(),
|
|
sm.faces_end(),
|
|
fn_iota);
|
|
}
|
|
#else
|
|
for (face_array_iterator_t f = sm.faces_begin(); f != sm.faces_end(); ++f) {
|
|
ps_to_sm_face[*f] = *f; // one to one mapping since ps is initially a copy of sm!
|
|
}
|
|
#endif
|
|
#endif
|
|
// std::map<vd_t, vd_t> cs_to_ps_vtx;
|
|
// std::map<vd_t, vd_t> ps_to_cm_vtx;
|
|
// std::vector<vd_t> cs_to_ps_vtx(cs.number_of_vertices());
|
|
std::vector<vd_t> ps_to_cm_vtx((std::size_t)sm_vtx_cnt + cs.number_of_vertices());
|
|
|
|
// merge cm vertices
|
|
for (auto i = cs.vertices_begin(); i != cs.vertices_end(); ++i) {
|
|
const vd_t v = ps.add_vertex(cs.vertex(*i));
|
|
|
|
MCUT_ASSERT(v != hmesh_t::null_vertex());
|
|
|
|
// cs_to_ps_vtx.insert(std::make_pair(*i, v));
|
|
// cs_to_ps_vtx[*i] = v;
|
|
ps_to_cm_vtx[v] = *i;
|
|
}
|
|
|
|
// std::map<fd_t, fd_t> ps_to_cm_face;
|
|
std::vector<fd_t> ps_to_cm_face((std::size_t)sm_face_count + cs_face_count);
|
|
|
|
// merge cm faces
|
|
{
|
|
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
{
|
|
auto fn_remap_ps_faces = [&](face_array_iterator_t block_start_, face_array_iterator_t block_end_) {
|
|
std::vector<std::pair<fd_t, std::vector<vd_t>>> result(std::distance(block_start_, block_end_));
|
|
|
|
uint32_t counter = 0;
|
|
for (face_array_iterator_t i = block_start_; i != block_end_; ++i) {
|
|
std::pair<fd_t, std::vector<vd_t>>& p = result[counter++];
|
|
std::vector<vd_t>& remapped_face_vertices = p.second;
|
|
cs.get_vertices_around_face(remapped_face_vertices, *i, sm_vtx_cnt);
|
|
p.first = *i;
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
std::vector<std::future<std::vector<std::pair<fd_t, std::vector<vd_t>>>>> futures;
|
|
std::vector<std::pair<fd_t, std::vector<vd_t>>> master_thread_res;
|
|
|
|
parallel_for(
|
|
*input.scheduler,
|
|
cs.faces_begin(),
|
|
cs.faces_end(),
|
|
fn_remap_ps_faces,
|
|
master_thread_res,
|
|
futures);
|
|
|
|
auto add_faces = [&](const std::vector<std::pair<fd_t, std::vector<vd_t>>>& remapped_faces) {
|
|
for (std::vector<std::pair<fd_t, std::vector<vd_t>>>::const_iterator it = remapped_faces.cbegin(); it != remapped_faces.cend(); ++it) {
|
|
const std::pair<fd_t, std::vector<vd_t>>& p = *it;
|
|
const fd_t f = ps.add_face(p.second);
|
|
|
|
MCUT_ASSERT(f != hmesh_t::null_face());
|
|
|
|
ps_to_cm_face[f] = p.first;
|
|
}
|
|
};
|
|
|
|
for (uint32_t i = 0; i < (uint32_t)futures.size(); ++i) {
|
|
const std::vector<std::pair<fd_t, std::vector<vd_t>>> f_res = futures[i].get();
|
|
add_faces(f_res);
|
|
}
|
|
|
|
add_faces(master_thread_res);
|
|
}
|
|
#else // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
|
|
std::vector<vd_t> remapped_face_vertices_tmp;
|
|
for (face_array_iterator_t i = cs.faces_begin(); i != cs.faces_end(); ++i) {
|
|
// std::vector<vd_t> fv = get_vertices_on_face(cs, *i);
|
|
|
|
cs.get_vertices_around_face(remapped_face_vertices_tmp, *i, sm_vtx_cnt);
|
|
const std::vector<vd_t>& remapped_face_vertices = remapped_face_vertices_tmp;
|
|
|
|
const fd_t f = ps.add_face(remapped_face_vertices);
|
|
|
|
MCUT_ASSERT(f != hmesh_t::null_face());
|
|
|
|
ps_to_cm_face[f] = *i;
|
|
}
|
|
#endif // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
}
|
|
|
|
TIMESTACK_POP();
|
|
|
|
// cs_to_ps_vtx.clear();
|
|
|
|
if (input.verbose) {
|
|
dump_mesh(ps, "polygon-soup");
|
|
}
|
|
|
|
const int ps_vtx_cnt = ps.number_of_vertices();
|
|
// const int ps_face_cnt = ps.number_of_faces();
|
|
|
|
//
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// create the first auxilliary halfedge data structure ("m0")
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
TIMESTACK_PUSH("Create m0");
|
|
// The auxilliary data structure stores:
|
|
// 1) vertices of the polygon-soup, including new intersection points
|
|
// 2) Non-intersecting edges of the polygon-soup
|
|
// 3) New edges created from intersection points
|
|
hmesh_t m0;
|
|
|
|
// copy ps vertices into the auxilliary mesh (map is used to maintain original vertex order)
|
|
// std::map<vd_t, vd_t> m0_to_ps_vtx;
|
|
std::vector<vd_t> m0_to_ps_vtx; // NOTE: only ps vertices are stored here
|
|
// std::map<vd_t, vd_t> ps_to_m0_vtx;
|
|
std::vector<vd_t> ps_to_m0_vtx((std::size_t)sm_vtx_cnt + cs.number_of_vertices());
|
|
for (auto i = ps.vertices_begin(); i != ps.vertices_end(); ++i) {
|
|
const vd_t v = m0.add_vertex(ps.vertex(*i));
|
|
|
|
MCUT_ASSERT(v != hmesh_t::null_vertex());
|
|
|
|
// m0_to_ps_vtx.emplace(v, *i);
|
|
m0_to_ps_vtx.emplace_back(*i);
|
|
// ps_to_m0_vtx.emplace(*i, v);
|
|
ps_to_m0_vtx[*i] = v;
|
|
}
|
|
|
|
TIMESTACK_POP();
|
|
MCUT_ASSERT(m0.number_of_vertices() == ps_vtx_cnt); // ... because we have only copied vertices
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Calculate polygon intersection points
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
std::unordered_map<ed_t, std::vector<fd_t>> ps_edge_face_intersection_pairs;
|
|
|
|
TIMESTACK_PUSH("Prepare edge-to-face pairs");
|
|
|
|
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
{ // NOTE: parallel implementation is different from sequential one
|
|
typedef std::unordered_map<ed_t, std::vector<fd_t>> OutputStorageType;
|
|
typedef std::map<fd_t, std::vector<fd_t>>::const_iterator InputStorageIteratorType;
|
|
|
|
std::vector<std::future<OutputStorageType>> futures;
|
|
|
|
auto fn_compute_ps_edge_to_faces_map = [&](InputStorageIteratorType block_start_, InputStorageIteratorType block_end_) {
|
|
std::unordered_map<ed_t, std::vector<fd_t>> ps_edge_face_intersection_pairs_local;
|
|
|
|
for (InputStorageIteratorType iter = block_start_; iter != block_end_; ++iter) {
|
|
// the face with the intersecting edges (i.e. the edges to be tested against the other face)
|
|
const fd_t& intersecting_edge_face = iter->first; // sm_face != hmesh_t::null_face() ? sm_face : cm_face;
|
|
const std::vector<hd_t>& halfedges = ps.get_halfedges_around_face(intersecting_edge_face);
|
|
|
|
for (std::vector<hd_t>::const_iterator hIter = halfedges.cbegin(); hIter != halfedges.cend(); ++hIter) {
|
|
const ed_t edge = ps.edge(*hIter);
|
|
std::vector<fd_t>& edge_ifaces = ps_edge_face_intersection_pairs_local[edge];
|
|
if (edge_ifaces.empty()) {
|
|
edge_ifaces = iter->second;
|
|
if (edge_ifaces.size() > 1) {
|
|
std::sort(edge_ifaces.begin(), edge_ifaces.end()); // alows us to do binary search (std::lower_bound)
|
|
}
|
|
} else {
|
|
for (std::vector<fd_t>::const_iterator iface_iter = iter->second.cbegin();
|
|
iface_iter != iter->second.cend();
|
|
++iface_iter) {
|
|
std::vector<fd_t>::iterator fiter = std::lower_bound(edge_ifaces.begin(), edge_ifaces.end(), *iface_iter);
|
|
bool exists = fiter != edge_ifaces.end() && (*fiter == *iface_iter);
|
|
if (!exists) {
|
|
edge_ifaces.insert(fiter, *iface_iter); // insert and maintain sorted order
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return ps_edge_face_intersection_pairs_local;
|
|
};
|
|
|
|
parallel_for(
|
|
*input.scheduler,
|
|
input.ps_face_to_potentially_intersecting_others->cbegin(),
|
|
input.ps_face_to_potentially_intersecting_others->cend(),
|
|
fn_compute_ps_edge_to_faces_map,
|
|
ps_edge_face_intersection_pairs, // out
|
|
futures);
|
|
|
|
// merge results from other threads
|
|
|
|
for (int fi = 0; fi < (int)futures.size(); ++fi) {
|
|
std::future<OutputStorageType>& f = futures[fi];
|
|
MCUT_ASSERT(f.valid()); // The behavior is undefined if valid()== false before the call to wait_for
|
|
|
|
OutputStorageType future_res = f.get();
|
|
// merge results for current block
|
|
for (OutputStorageType::const_iterator i = future_res.cbegin(); i != future_res.cend(); ++i) {
|
|
OutputStorageType::iterator fiter = ps_edge_face_intersection_pairs.find(i->first);
|
|
if (fiter == ps_edge_face_intersection_pairs.cend()) {
|
|
ps_edge_face_intersection_pairs[i->first] = i->second;
|
|
} else {
|
|
for (std::vector<fd_t>::const_iterator j = i->second.cbegin(); j != i->second.cend(); ++j) {
|
|
std::vector<fd_t>::iterator lb_iter = std::lower_bound(fiter->second.begin(), fiter->second.end(), *j);
|
|
bool exists = lb_iter != fiter->second.end() && (*lb_iter == *j);
|
|
if (!exists) {
|
|
fiter->second.insert(lb_iter, *j); // insert and maintain sorted order
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} // end of parallel code
|
|
#else
|
|
{
|
|
|
|
std::vector<fd_t> unvisited_ps_ifaces; //= *input.ps_face_to_potentially_intersecting_others;
|
|
unvisited_ps_ifaces.reserve(input.ps_face_to_potentially_intersecting_others->size());
|
|
// NOTE: the elements of "unvisited_ps_ifaces" are already sorted because they come directly from
|
|
// "input.ps_face_to_potentially_intersecting_others", which is an std::map (keys are always sorted)
|
|
std::transform(
|
|
input.ps_face_to_potentially_intersecting_others->cbegin(),
|
|
input.ps_face_to_potentially_intersecting_others->cend(),
|
|
std::back_inserter(unvisited_ps_ifaces),
|
|
[](const std::pair<fd_t, std::vector<fd_t>>& kv) { return kv.first; });
|
|
|
|
std::vector<bool> ps_iface_enqueued(ps.number_of_faces(), false);
|
|
|
|
std::vector<bool> ps_edge_visited(ps.number_of_edges(), false);
|
|
// initially null
|
|
std::map<fd_t, std::vector<fd_t>>::const_iterator cur_ps_cc_face = input.ps_face_to_potentially_intersecting_others->cend();
|
|
// start with any face, but we choose the first
|
|
std::map<fd_t, std::vector<fd_t>>::const_iterator next_ps_cc_face = input.ps_face_to_potentially_intersecting_others->cbegin();
|
|
ps_iface_enqueued[next_ps_cc_face->first] = true;
|
|
|
|
// an element of this queue is an iterator/ptr to an element of "input.ps_face_to_potentially_intersecting_others"
|
|
std::queue<std::map<fd_t, std::vector<fd_t>>::const_iterator> adj_ps_face_queue;
|
|
|
|
do { // each iteration will find a set of edges that belong to a connected-component patch of intersectng faces (of sm or cm) in ps
|
|
cur_ps_cc_face = next_ps_cc_face;
|
|
next_ps_cc_face = input.ps_face_to_potentially_intersecting_others->cend(); // set null
|
|
|
|
// register unique edges of current face, and the add the neighbouring faces to queue
|
|
|
|
adj_ps_face_queue.push(cur_ps_cc_face);
|
|
|
|
do { // each interation will add unregistered edges of current face, and add unvisited faces to queue
|
|
|
|
const std::map<fd_t, std::vector<fd_t>>::const_iterator cc_iface = adj_ps_face_queue.front(); // current face of connected-component patch
|
|
adj_ps_face_queue.pop();
|
|
|
|
{ // face is now visisted so we remove it
|
|
std::vector<fd_t>::iterator fiter = std::lower_bound(
|
|
unvisited_ps_ifaces.begin(),
|
|
unvisited_ps_ifaces.end(),
|
|
cc_iface->first);
|
|
MCUT_ASSERT(fiter != unvisited_ps_ifaces.cend());
|
|
unvisited_ps_ifaces.erase(fiter); // NOTE: list remains sorted
|
|
}
|
|
|
|
std::vector<fd_t> cur_ps_face_ifaces_sorted = cc_iface->second; // copy
|
|
if (cur_ps_face_ifaces_sorted.size() > 1) {
|
|
std::sort(cur_ps_face_ifaces_sorted.begin(), cur_ps_face_ifaces_sorted.end()); // allows quick binary search
|
|
}
|
|
// bool is_sm_face = cc_iface->first < sm_face_count;
|
|
// const fd_t cc_iface_descr = is_sm_face ? cc_iface->first - sm_face_count : sm_face_count;
|
|
|
|
const std::vector<hd_t>& cur_ps_face_halfedges = ps.get_halfedges_around_face(cc_iface->first);
|
|
// all neighbours
|
|
const std::vector<fd_t> cur_ps_face_neigh_faces = ps.get_faces_around_face(cc_iface->first, &cur_ps_face_halfedges);
|
|
// neighbours [which are intersecting faces]
|
|
std::vector<fd_t> cur_ps_face_neigh_ifaces;
|
|
cur_ps_face_neigh_ifaces.reserve(cur_ps_face_neigh_faces.size());
|
|
|
|
for (std::vector<fd_t>::const_iterator face_iter = cur_ps_face_neigh_faces.cbegin();
|
|
face_iter != cur_ps_face_neigh_faces.cend();
|
|
++face_iter) {
|
|
bool is_iface = input.ps_face_to_potentially_intersecting_others->find(*face_iter) != input.ps_face_to_potentially_intersecting_others->cend();
|
|
if (is_iface) {
|
|
cur_ps_face_neigh_ifaces.push_back(*face_iter);
|
|
}
|
|
}
|
|
|
|
if (cur_ps_face_neigh_ifaces.size() > 1) {
|
|
std::sort(cur_ps_face_neigh_ifaces.begin(), cur_ps_face_neigh_ifaces.end());
|
|
}
|
|
|
|
// for each halfedge of current iface
|
|
for (std::vector<hd_t>::const_iterator hiter = cur_ps_face_halfedges.cbegin(); hiter != cur_ps_face_halfedges.cend(); ++hiter) {
|
|
const ed_t halfedge_edge = ps.edge(*hiter);
|
|
const hd_t opp_he = ps.opposite(*hiter);
|
|
// Here we simply access corresponding element in "cur_ps_face_neigh_faces" based on
|
|
// how "ps.get_faces_around_face" populates "cur_ps_face_neigh_faces", given "cur_ps_face_halfedges"
|
|
// as done above
|
|
|
|
if (ps_edge_visited[halfedge_edge] == false) {
|
|
ps_edge_face_intersection_pairs.insert(std::make_pair(halfedge_edge, cur_ps_face_ifaces_sorted)); // add edge
|
|
ps_edge_visited[halfedge_edge] = true;
|
|
|
|
bool is_border_ps_face = cur_ps_face_neigh_faces.size() != cur_ps_face_halfedges.size();
|
|
const size_t idx = std::distance(cur_ps_face_halfedges.cbegin(), hiter);
|
|
fd_t opp_he_face = is_border_ps_face ? ps.face(opp_he) : SAFE_ACCESS(cur_ps_face_neigh_faces, idx);
|
|
|
|
if (!is_virtual_face(opp_he_face) && ps_iface_enqueued[opp_he_face] == false) { // two neighbouring faces might share more that 1 edge (case of non-triangulated mesh)
|
|
bool is_iface = std::binary_search(cur_ps_face_neigh_ifaces.cbegin(), cur_ps_face_neigh_ifaces.cend(), opp_he_face);
|
|
if (is_iface) {
|
|
std::map<fd_t, std::vector<fd_t>>::const_iterator fiter = input.ps_face_to_potentially_intersecting_others->find(opp_he_face);
|
|
adj_ps_face_queue.push(fiter);
|
|
ps_iface_enqueued[opp_he_face] = true;
|
|
}
|
|
}
|
|
} else {
|
|
MCUT_ASSERT(ps_edge_face_intersection_pairs.find(halfedge_edge) != ps_edge_face_intersection_pairs.cend());
|
|
// merge shared "intersected" faces.
|
|
// Two intersecting faces that share an edge will share intersected faces. The shared faces
|
|
// are those intersected by the shared edge.
|
|
std::vector<fd_t>& existing_edge_ifaces = ps_edge_face_intersection_pairs[halfedge_edge]; // sorted list
|
|
|
|
for (std::vector<fd_t>::const_iterator i = cur_ps_face_ifaces_sorted.cbegin(); i != cur_ps_face_ifaces_sorted.cend(); ++i) {
|
|
std::vector<fd_t>::iterator iter = std::lower_bound(existing_edge_ifaces.begin(), existing_edge_ifaces.end(), *i);
|
|
|
|
bool found = iter != existing_edge_ifaces.end() && iter != existing_edge_ifaces.end() && (*iter == *i);
|
|
if (!found) {
|
|
existing_edge_ifaces.insert(iter, *i); // insert into sorted list (i.e. possibly shifts some elements forward)
|
|
}
|
|
}
|
|
}
|
|
} // for each halfedge of current iface
|
|
|
|
} while (adj_ps_face_queue.empty() == false);
|
|
|
|
// find "next_ps_cc_face" as any face in "input.ps_face_to_potentially_intersecting_others" that is not visited
|
|
if (unvisited_ps_ifaces.size() > 0) {
|
|
fd_t next_face = unvisited_ps_ifaces.back(); // pick any unvisited iface (we choose the last for faster elemt removal from std::vector)
|
|
next_ps_cc_face = input.ps_face_to_potentially_intersecting_others->find(next_face);
|
|
MCUT_ASSERT(next_ps_cc_face != input.ps_face_to_potentially_intersecting_others->cend());
|
|
}
|
|
|
|
} while (next_ps_cc_face != input.ps_face_to_potentially_intersecting_others->cend());
|
|
}
|
|
// std::unordered_map<ed_t, std::vector<fd_t>> ps_edge_face_intersection_pairs;
|
|
#endif // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
TIMESTACK_POP();
|
|
|
|
//
|
|
// build bounding boxes for each intersecting edge
|
|
//
|
|
#if 1
|
|
TIMESTACK_PUSH("Build edge bounding boxes");
|
|
|
|
// http://gamma.cs.unc.edu/RTRI/i3d08_RTRI.pdf
|
|
std::unordered_map<ed_t, bounding_box_t<vec3>> ps_edge_to_bbox;
|
|
|
|
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
{
|
|
typedef std::unordered_map<ed_t, bounding_box_t<vec3>> OutputStorageType;
|
|
typedef std::unordered_map<ed_t, std::vector<fd_t>>::const_iterator InputStorageIteratorType;
|
|
|
|
auto fn_compute_ps_edge_bbox = [&](InputStorageIteratorType block_start_, InputStorageIteratorType block_end_) {
|
|
OutputStorageType ps_edge_to_bbox_local;
|
|
|
|
for (std::unordered_map<ed_t, std::vector<fd_t>>::const_iterator iedge_iter = block_start_; iedge_iter != block_end_; iedge_iter++) {
|
|
const ed_t edge = iedge_iter->first;
|
|
const vd_t v0 = ps.vertex(edge, 0);
|
|
const vd_t v1 = ps.vertex(edge, 1);
|
|
bounding_box_t<vec3>& edge_bbox = ps_edge_to_bbox_local[edge];
|
|
edge_bbox.expand(ps.vertex(v0));
|
|
edge_bbox.expand(ps.vertex(v1));
|
|
}
|
|
|
|
return ps_edge_to_bbox_local;
|
|
};
|
|
|
|
std::vector<std::future<OutputStorageType>> futures;
|
|
|
|
parallel_for(
|
|
*input.scheduler,
|
|
ps_edge_face_intersection_pairs.cbegin(),
|
|
ps_edge_face_intersection_pairs.cend(),
|
|
fn_compute_ps_edge_bbox,
|
|
ps_edge_to_bbox, // out
|
|
futures);
|
|
|
|
// merge results from other threads
|
|
|
|
for (int i = 0; i < (int)futures.size(); ++i) {
|
|
std::future<OutputStorageType>& f = futures[i];
|
|
MCUT_ASSERT(f.valid()); // The behavior is undefined if valid()== false before the call to wait_for
|
|
|
|
OutputStorageType future_res = f.get();
|
|
|
|
ps_edge_to_bbox.insert(future_res.cbegin(), future_res.cend());
|
|
}
|
|
}
|
|
#else
|
|
for (std::unordered_map<ed_t, std::vector<fd_t>>::const_iterator iedge_iter = ps_edge_face_intersection_pairs.cbegin();
|
|
iedge_iter != ps_edge_face_intersection_pairs.cend();
|
|
iedge_iter++) {
|
|
MCUT_ASSERT(iedge_iter->second.size() >= 1);
|
|
const ed_t edge = iedge_iter->first;
|
|
const vd_t v0 = ps.vertex(edge, 0);
|
|
const vd_t v1 = ps.vertex(edge, 1);
|
|
bounding_box_t<vec3>& edge_bbox = ps_edge_to_bbox[edge];
|
|
edge_bbox.expand(ps.vertex(v0));
|
|
edge_bbox.expand(ps.vertex(v1));
|
|
}
|
|
#endif // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
|
|
TIMESTACK_POP();
|
|
|
|
//
|
|
// cull redundant edge to face pairs
|
|
//
|
|
TIMESTACK_PUSH("Cull redundant edge-face pairs");
|
|
|
|
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
{
|
|
typedef std::unordered_map<ed_t, std::vector<fd_t>>::iterator InputStorageIteratorType;
|
|
|
|
auto fn_compute_edgefair_pair_culling = [&](InputStorageIteratorType block_start_, InputStorageIteratorType block_end_) {
|
|
for (std::unordered_map<ed_t, std::vector<fd_t>>::iterator iedge_iter = block_start_; iedge_iter != block_end_; iedge_iter++) {
|
|
const ed_t edge = iedge_iter->first;
|
|
const bounding_box_t<vec3>& edge_bbox = ps_edge_to_bbox[edge];
|
|
std::vector<fd_t>& edge_ifaces = iedge_iter->second;
|
|
|
|
for (std::vector<fd_t>::iterator iface_iter = edge_ifaces.begin(); iface_iter != edge_ifaces.end(); /*increment inside loop*/) {
|
|
const bounding_box_t<vec3>* iface_bbox = nullptr;
|
|
bool is_sm_face = (size_t)(*iface_iter) < (size_t)sm_face_count;
|
|
if (is_sm_face) {
|
|
#if defined(USE_OIBVH)
|
|
iface_bbox = &(SAFE_ACCESS((*input.source_hmesh_face_aabb_array_ptr), *iface_iter));
|
|
|
|
#else
|
|
iface_bbox = &input.source_hmesh_BVH->GetPrimitiveBBox(*iface_iter); // SAFE_ACCESS(((*input.source_hmesh_face_aabb_array_ptr), *iface_iter));
|
|
#endif
|
|
} else {
|
|
#if defined(USE_OIBVH)
|
|
iface_bbox = &(SAFE_ACCESS((*input.cut_hmesh_face_aabb_array_ptr), (size_t)(*iface_iter) - sm_face_count));
|
|
#else
|
|
iface_bbox = &input.cut_hmesh_BVH->GetPrimitiveBBox((size_t)(*iface_iter) - sm_face_count); // SAFE_ACCESS(((*input.cut_hmesh_face_aabb_array_ptr), ((size_t)(*iface_iter) - sm_face_count)));
|
|
|
|
#endif
|
|
}
|
|
|
|
bool intersect = intersect_bounding_boxes(edge_bbox, *iface_bbox);
|
|
|
|
if (!intersect) {
|
|
// remove because "iface_iter" was paired with a coincident face (of "edge") based on
|
|
// the mere fact that the coincident face was found to be in close proximity with
|
|
// "iface_iter" (from BVH tree proximity search)
|
|
iface_iter = edge_ifaces.erase(iface_iter); // NOTE: "erase" return iterator to next after
|
|
} else {
|
|
iface_iter++;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
};
|
|
|
|
std::vector<std::future<int>> futures;
|
|
int _1;
|
|
|
|
parallel_for(
|
|
*input.scheduler,
|
|
ps_edge_face_intersection_pairs.begin(),
|
|
ps_edge_face_intersection_pairs.end(),
|
|
fn_compute_edgefair_pair_culling,
|
|
_1, // out
|
|
futures);
|
|
|
|
for (int i = 0; i < (int)futures.size(); ++i) {
|
|
std::future<int>& f = futures[i];
|
|
MCUT_ASSERT(f.valid());
|
|
f.wait(); // simply wait for result to be done
|
|
}
|
|
}
|
|
#else
|
|
for (std::unordered_map<ed_t, std::vector<fd_t>>::iterator iedge_iter = ps_edge_face_intersection_pairs.begin();
|
|
iedge_iter != ps_edge_face_intersection_pairs.end();
|
|
iedge_iter++) {
|
|
const ed_t edge = iedge_iter->first;
|
|
const bounding_box_t<vec3>& edge_bbox = ps_edge_to_bbox[edge];
|
|
std::vector<fd_t>& edge_ifaces = iedge_iter->second;
|
|
|
|
for (std::vector<fd_t>::iterator iface_iter = edge_ifaces.begin(); iface_iter != edge_ifaces.end(); /*increment inside loop*/) {
|
|
const bounding_box_t<vec3>* iface_bbox = nullptr;
|
|
bool is_sm_face = (size_t)(*iface_iter) < (size_t)sm_face_count;
|
|
if (is_sm_face) {
|
|
#if defined(USE_OIBVH)
|
|
iface_bbox = SAFE_ACCESS(&((*input.source_hmesh_face_aabb_array_ptr)), *iface_iter);
|
|
|
|
#else
|
|
iface_bbox = &input.source_hmesh_BVH->GetPrimitiveBBox(*iface_iter); // SAFE_ACCESS(((*input.source_hmesh_face_aabb_array_ptr), *iface_iter));
|
|
#endif
|
|
} else {
|
|
#if defined(USE_OIBVH)
|
|
iface_bbox = SAFE_ACCESS(&((*input.cut_hmesh_face_aabb_array_ptr)), ((size_t)(*iface_iter) - sm_face_count));
|
|
#else
|
|
iface_bbox = &input.cut_hmesh_BVH->GetPrimitiveBBox((size_t)(*iface_iter) - sm_face_count); // SAFE_ACCESS(((*input.cut_hmesh_face_aabb_array_ptr), ((size_t)(*iface_iter) - sm_face_count)));
|
|
|
|
#endif
|
|
}
|
|
|
|
bool intersect = intersect_bounding_boxes(edge_bbox, *iface_bbox);
|
|
|
|
if (!intersect) {
|
|
// remove because "iface_iter" was paired with a coincident face (of "edge") based on
|
|
// the mere fact that the coincident face was found to be in close proximity with
|
|
// "iface_iter" (from BVH tree proximity search)
|
|
iface_iter = edge_ifaces.erase(iface_iter); // NOTE: "erase" return iterator to next after
|
|
} else {
|
|
iface_iter++;
|
|
}
|
|
}
|
|
}
|
|
#endif // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
TIMESTACK_POP();
|
|
|
|
ps_edge_to_bbox.clear();
|
|
|
|
// assuming each edge will produce a new vertex
|
|
m0.reserve_for_additional_elements((std::uint32_t)ps_edge_face_intersection_pairs.size());
|
|
#endif
|
|
TIMESTACK_PUSH("Compute intersecting face properties");
|
|
// compute/extract geometry properties of each tested face
|
|
//--------------------------------------------------------
|
|
|
|
std::unordered_map<fd_t, vec3> ps_tested_face_to_plane_normal;
|
|
std::unordered_map<fd_t, double> ps_tested_face_to_plane_normal_d_param;
|
|
std::unordered_map<fd_t, int> ps_tested_face_to_plane_normal_max_comp;
|
|
std::unordered_map<fd_t, std::vector<vec3>> ps_tested_face_to_vertices;
|
|
|
|
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
{
|
|
typedef std::tuple<
|
|
std::unordered_map<fd_t, vec3>, // ps_tested_face_to_plane_normal;
|
|
std::unordered_map<fd_t, double>, // ps_tested_face_to_plane_normal_d_param;
|
|
std::unordered_map<fd_t, int>, // ps_tested_face_to_plane_normal_max_comp;
|
|
std::unordered_map<fd_t, std::vector<vec3>> // ps_tested_face_to_vertices;
|
|
>
|
|
OutputStorageTypesTuple;
|
|
typedef std::map<fd_t, std::vector<fd_t>>::const_iterator InputStorageIteratorType;
|
|
|
|
std::atomic<int> potentially_intersecting_face_with_zero_area(-1); // did any errors occur (e.g. found a face with zero area)
|
|
|
|
auto fn_compute_intersecting_face_properties = [&](InputStorageIteratorType block_start_, InputStorageIteratorType block_end_) -> OutputStorageTypesTuple {
|
|
OutputStorageTypesTuple output_res;
|
|
std::unordered_map<fd_t, vec3>& ps_tested_face_to_plane_normal_LOCAL = std::get<0>(output_res);
|
|
std::unordered_map<fd_t, double>& ps_tested_face_to_plane_normal_d_param_LOCAL = std::get<1>(output_res);
|
|
std::unordered_map<fd_t, int>& ps_tested_face_to_plane_normal_max_comp_LOCAL = std::get<2>(output_res);
|
|
std::unordered_map<fd_t, std::vector<vec3>>& ps_tested_face_to_vertices_LOCAL = std::get<3>(output_res);
|
|
std::vector<vd_t> tested_face_descriptors_tmp;
|
|
for (std::map<fd_t, std::vector<fd_t>>::const_iterator tested_faces_iter = block_start_;
|
|
tested_faces_iter != block_end_;
|
|
tested_faces_iter++) {
|
|
// get the vertices of tested_face (used to estimate its normal etc.)
|
|
ps.get_vertices_around_face(tested_face_descriptors_tmp, tested_faces_iter->first);
|
|
std::vector<vd_t>& tested_face_descriptors = tested_face_descriptors_tmp;
|
|
std::vector<vec3>& tested_face_vertices = ps_tested_face_to_vertices_LOCAL[tested_faces_iter->first]; // insert and get reference
|
|
|
|
for (std::vector<vd_t>::const_iterator it = tested_face_descriptors.cbegin(); it != tested_face_descriptors.cend(); ++it) {
|
|
const vec3& vertex = ps.vertex(*it);
|
|
tested_face_vertices.push_back(vertex);
|
|
}
|
|
|
|
vec3& tested_face_plane_normal = ps_tested_face_to_plane_normal_LOCAL[tested_faces_iter->first];
|
|
double& tested_face_plane_param_d = ps_tested_face_to_plane_normal_d_param_LOCAL[tested_faces_iter->first];
|
|
int& tested_face_plane_normal_max_comp = ps_tested_face_to_plane_normal_max_comp_LOCAL[tested_faces_iter->first];
|
|
|
|
tested_face_plane_normal_max_comp = compute_polygon_plane_coefficients(
|
|
tested_face_plane_normal,
|
|
tested_face_plane_param_d,
|
|
tested_face_vertices.data(),
|
|
(int)tested_face_vertices.size());
|
|
|
|
if (squared_length(tested_face_plane_normal) == 0) {
|
|
potentially_intersecting_face_with_zero_area.store((int)tested_faces_iter->first, std::memory_order_release);
|
|
}
|
|
}
|
|
return output_res;
|
|
};
|
|
|
|
std::vector<std::future<OutputStorageTypesTuple>> futures;
|
|
OutputStorageTypesTuple partial_res;
|
|
|
|
parallel_for(
|
|
*input.scheduler,
|
|
input.ps_face_to_potentially_intersecting_others->cbegin(),
|
|
input.ps_face_to_potentially_intersecting_others->cend(),
|
|
fn_compute_intersecting_face_properties,
|
|
partial_res, // out
|
|
futures);
|
|
|
|
std::tie(
|
|
ps_tested_face_to_plane_normal,
|
|
ps_tested_face_to_plane_normal_d_param,
|
|
ps_tested_face_to_plane_normal_max_comp,
|
|
ps_tested_face_to_vertices)
|
|
= partial_res;
|
|
// merge results from other threads
|
|
|
|
for (int i = 0; i < (int)futures.size(); ++i) {
|
|
|
|
std::future<OutputStorageTypesTuple>& f = futures[i];
|
|
MCUT_ASSERT(f.valid()); // The behavior is undefined if valid()== false before the call to wait_for
|
|
|
|
OutputStorageTypesTuple future_res = f.get();
|
|
|
|
if (potentially_intersecting_face_with_zero_area.load(std::memory_order_acquire) >= 0) {
|
|
break; // stop there was a runtime error
|
|
}
|
|
|
|
std::unordered_map<fd_t, vec3>& ps_tested_face_to_plane_normal_FUTURE = std::get<0>(future_res);
|
|
std::unordered_map<fd_t, double>& ps_tested_face_to_plane_normal_d_param_FUTURE = std::get<1>(future_res);
|
|
std::unordered_map<fd_t, int>& ps_tested_face_to_plane_normal_max_comp_FUTURE = std::get<2>(future_res);
|
|
std::unordered_map<fd_t, std::vector<vec3>>& ps_tested_face_to_vertices_FUTURE = std::get<3>(future_res);
|
|
|
|
ps_tested_face_to_plane_normal.insert(
|
|
ps_tested_face_to_plane_normal_FUTURE.cbegin(),
|
|
ps_tested_face_to_plane_normal_FUTURE.cend());
|
|
|
|
ps_tested_face_to_plane_normal_d_param.insert(
|
|
ps_tested_face_to_plane_normal_d_param_FUTURE.cbegin(),
|
|
ps_tested_face_to_plane_normal_d_param_FUTURE.cend());
|
|
|
|
ps_tested_face_to_plane_normal_max_comp.insert(
|
|
ps_tested_face_to_plane_normal_max_comp_FUTURE.cbegin(),
|
|
ps_tested_face_to_plane_normal_max_comp_FUTURE.cend());
|
|
|
|
ps_tested_face_to_vertices.insert(
|
|
ps_tested_face_to_vertices_FUTURE.cbegin(),
|
|
ps_tested_face_to_vertices_FUTURE.cend());
|
|
}
|
|
|
|
const int tmp_local = potentially_intersecting_face_with_zero_area.load(std::memory_order_acquire);
|
|
|
|
if (tmp_local >= 0) {
|
|
const bool is_cutmesh_face = (tmp_local > sm_face_count);
|
|
// if "tmp_local" > srcMeshFaceCount then "tmp_local" is a cut-mesh face with id="tmp_local-srcMeshFaceCount"
|
|
const std::string msh_name = is_cutmesh_face ? "cut-mesh" : "source-mesh";
|
|
// index/descriptor in the _kernel_ input mesh (note the stress on kernel since frontend might modify user-provided mesh)
|
|
const fd_t bad_face_desr = fd_t(is_cutmesh_face ? (tmp_local - sm_face_count) : tmp_local);
|
|
lg.set_reason_for_failure("face f" + std::to_string(bad_face_desr) + " of " + msh_name + " is degenerate (has zero area)");
|
|
output.status.store(is_cutmesh_face ? status_t::INVALID_CUT_MESH : status_t::INVALID_SRC_MESH, std::memory_order_release);
|
|
return; // stop there was a runtime error
|
|
}
|
|
|
|
} // end of parallel scope
|
|
#else
|
|
// for each face that is to be tested for intersection
|
|
// NOTE: the keys of input.ps_face_to_potentially_intersecting_others are the potentially colliding polygons
|
|
// that we get after BVH traversal
|
|
{
|
|
std::vector<vd_t> tested_face_descriptors_tmp;
|
|
for (std::map<fd_t, std::vector<fd_t>>::const_iterator tested_faces_iter = input.ps_face_to_potentially_intersecting_others->cbegin();
|
|
tested_faces_iter != input.ps_face_to_potentially_intersecting_others->cend();
|
|
tested_faces_iter++) {
|
|
// get the vertices of tested_face (used to estimate its normal etc.)
|
|
ps.get_vertices_around_face(tested_face_descriptors_tmp, tested_faces_iter->first);
|
|
const std::vector<vd_t>& tested_face_descriptors = tested_face_descriptors_tmp;
|
|
std::vector<vec3>& tested_face_vertices = ps_tested_face_to_vertices[tested_faces_iter->first]; // insert and get reference
|
|
|
|
for (std::vector<vd_t>::const_iterator it = tested_face_descriptors.cbegin(); it != tested_face_descriptors.cend(); ++it) {
|
|
const vec3& vertex = ps.vertex(*it);
|
|
tested_face_vertices.push_back(vertex);
|
|
}
|
|
|
|
vec3& tested_face_plane_normal = ps_tested_face_to_plane_normal[tested_faces_iter->first];
|
|
double& tested_face_plane_param_d = ps_tested_face_to_plane_normal_d_param[tested_faces_iter->first];
|
|
int& tested_face_plane_normal_max_comp = ps_tested_face_to_plane_normal_max_comp[tested_faces_iter->first];
|
|
|
|
tested_face_plane_normal_max_comp = compute_polygon_plane_coefficients(
|
|
tested_face_plane_normal,
|
|
tested_face_plane_param_d,
|
|
tested_face_vertices.data(),
|
|
(int)tested_face_vertices.size());
|
|
|
|
if (squared_length(tested_face_plane_normal) == 0) {
|
|
const int tmp_local = (int)tested_faces_iter->first;
|
|
const bool is_cutmesh_face = (tmp_local > sm_face_count);
|
|
const std::string msh_name = is_cutmesh_face ? "cut-mesh" : "source-mesh";
|
|
const fd_t bad_face_desr = fd_t(is_cutmesh_face ? (tmp_local - sm_face_count) : tmp_local);
|
|
lg.set_reason_for_failure("face f" + std::to_string(bad_face_desr) + " of " + msh_name + " is degenerate (has zero area)");
|
|
output.status = (is_cutmesh_face ? status_t::INVALID_CUT_MESH : status_t::INVALID_SRC_MESH);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
TIMESTACK_POP();
|
|
|
|
// edge-to-face intersection tests (narrow-phase)
|
|
// -----------------------------------------
|
|
|
|
// ivertex to faces that meet at the this ivertex
|
|
// std::map<
|
|
/// vd_t, // intersection point
|
|
// std::vector<fd_t> // list of faces that intersect with another face at the intersection point
|
|
// >
|
|
// m0_ivtx_to_ps_faces;
|
|
|
|
// ivertex to halfedge that was tested again a face in order to produce this ivertex
|
|
// NOTE: the ordering of the intersections point is dependant on the order in which they where actually computed
|
|
// i.e. the data of the first intersection point is at index 0
|
|
// std::map<
|
|
// vd_t, // intersection point
|
|
// ed_t // halfedge
|
|
// >
|
|
// m0_ivtx_to_ps_edge;
|
|
|
|
std::vector<
|
|
std::pair<ed_t, fd_t> // edge and face that where tested to produce our intersection point.
|
|
>
|
|
m0_ivtx_to_intersection_registry_entry;
|
|
|
|
// re-entrant vertices on the border of the cut-mesh
|
|
std::vector<vd_t> cm_border_reentrant_ivtx_list;
|
|
|
|
// std::map<
|
|
/// vd_t, // intersection point
|
|
/// vec3 // the normal vector of intersected face from which intersection point came from
|
|
// >
|
|
// m0_ivtx_to_tested_polygon_normal;
|
|
|
|
// edges of the polygon soup mesh which intersect a face
|
|
std::unordered_map<ed_t, std::vector<vd_t>> ps_intersecting_edges;
|
|
|
|
// A map of used to create edges along the intersection path.
|
|
// Each element is the information such as intersection points that arise from testing two polygons.
|
|
// The size of thos vector is dependent on the number of polygon pairs (in "input.intersecting_sm_cm_face_pairs")
|
|
// which intersect.
|
|
|
|
std::map< // information needed to build edges along the cut-path
|
|
pair<fd_t>, // pair of intersecting polygons (source-mesh polygon, cut-mesh polygon)
|
|
std::vector<vd_t> // resulting intersection points
|
|
>
|
|
cutpath_edge_creation_info;
|
|
|
|
std::unordered_map<
|
|
fd_t, // intersectiong face
|
|
std::vector<vd_t> // intersection point which involve the intersecting face
|
|
>
|
|
ps_iface_to_ivtx_list; // faces which intersect with another
|
|
|
|
// A partial cut intersection exists when there exists at-least one intersection point
|
|
// whose registry has a halfedge from the cut-surface, where this halfedge is a border halfedge.
|
|
bool partial_cut_detected = false;
|
|
|
|
TIMESTACK_PUSH("Calculate intersection points (edge-to-face)");
|
|
|
|
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
{
|
|
// typedef std::unordered_map<ed_t, std::vector<fd_t>>::const_iterator InputStorageIteratorType;
|
|
typedef std::tuple<
|
|
std::vector<std::pair<ed_t, fd_t>>, // m0_ivtx_to_intersection_registry_entry
|
|
std::vector<vd_t>, // cm_border_reentrant_ivtx_list
|
|
std::unordered_map<ed_t, std::vector<vd_t>>, // ps_intersecting_edges
|
|
std::map<pair<fd_t>, std::vector<vd_t>>, // cutpath_edge_creation_info
|
|
std::unordered_map<fd_t, std::vector<vd_t>>, // ps_iface_to_ivtx_list
|
|
bool, // partial_cut_detected
|
|
std::vector<vec3> // list of intersection points computed in a future
|
|
>
|
|
OutputStorageTypesTuple;
|
|
|
|
auto fn_compute_intersection_points = [&](
|
|
std::unordered_map<ed_t, std::vector<fd_t>>::const_iterator block_start_,
|
|
std::unordered_map<ed_t, std::vector<fd_t>>::const_iterator block_end_) -> OutputStorageTypesTuple {
|
|
OutputStorageTypesTuple local_output;
|
|
|
|
std::vector<std::pair<ed_t, fd_t>>& m0_ivtx_to_intersection_registry_entry_LOCAL = std::get<0>(local_output);
|
|
std::vector<vd_t>& cm_border_reentrant_ivtx_list_LOCAL = std::get<1>(local_output);
|
|
std::unordered_map<ed_t, std::vector<vd_t>>& ps_intersecting_edges_LOCAL = std::get<2>(local_output);
|
|
std::map<pair<fd_t>, std::vector<vd_t>>& cutpath_edge_creation_info_LOCAL = std::get<3>(local_output);
|
|
std::unordered_map<fd_t, std::vector<vd_t>>& ps_iface_to_ivtx_list_LOCAL = std::get<4>(local_output);
|
|
bool& partial_cut_detected_LOCAL = std::get<5>(local_output);
|
|
std::vector<vec3>& intersection_points_LOCAL = std::get<6>(local_output);
|
|
|
|
// NOTE: threads do not add vertices into m0 to prevent contention on a shared resource.
|
|
// They instead store a placeholder value that is analogous to an local offset i.e.
|
|
// as if each thread was writing into its own mesh.
|
|
|
|
// did another thread/job encountered a configuration in which we violated general position
|
|
// NOTE: we only check at the beginning to prevent contention on "output.status"
|
|
bool current_status_is_okay = (output.status.load() == status_t::SUCCESS);
|
|
if (!current_status_is_okay) {
|
|
return OutputStorageTypesTuple {}; // immediately
|
|
}
|
|
|
|
// for each edge
|
|
for (std::unordered_map<ed_t, std::vector<fd_t>>::const_iterator ps_edge_face_intersection_pairs_iter = block_start_;
|
|
ps_edge_face_intersection_pairs_iter != block_end_;
|
|
ps_edge_face_intersection_pairs_iter++) {
|
|
|
|
// our edge that we test for intersection with other faces
|
|
const ed_t tested_edge = ps_edge_face_intersection_pairs_iter->first;
|
|
// the faces against which the edge is tested for intersection
|
|
const std::vector<fd_t>& tested_faces = ps_edge_face_intersection_pairs_iter->second;
|
|
|
|
// the halfedges of our edge
|
|
const hd_t tested_edge_h0 = ps.halfedge(tested_edge, 0);
|
|
const hd_t tested_edge_h1 = ps.halfedge(tested_edge, 1);
|
|
|
|
// source vertex
|
|
const vertex_descriptor_t tested_edge_h0_source_descr = ps.source(tested_edge_h0);
|
|
const vec3& tested_edge_h0_source_vertex = ps.vertex(tested_edge_h0_source_descr);
|
|
// target vertex
|
|
const vertex_descriptor_t tested_edge_h0_target_descr = ps.target(tested_edge_h0);
|
|
const vec3& tested_edge_h0_target_vertex = ps.vertex(tested_edge_h0_target_descr);
|
|
|
|
// This boolean var is evaluated based on the fact that sm faces come before cm faces inside the "ps" data structure
|
|
const fd_t tested_edge_h0_face = ps.face(tested_edge_h0);
|
|
const fd_t tested_edge_h1_face = ps.face(tested_edge_h1);
|
|
const fd_t tested_edge_face = tested_edge_h0_face != hmesh_t::null_face() ? tested_edge_h0_face : tested_edge_h1_face;
|
|
const bool tested_edge_belongs_to_cm = ps_is_cutmesh_face(tested_edge_face, sm_face_count);
|
|
|
|
// for each face that is to be intersected with the tested-edge
|
|
for (std::vector<fd_t>::const_iterator tested_faces_iter = tested_faces.cbegin();
|
|
tested_faces_iter != tested_faces.cend();
|
|
++tested_faces_iter) {
|
|
const fd_t tested_face = *tested_faces_iter;
|
|
|
|
// We are now finding the intersection points determined by calculating the location
|
|
// where each halfedge of face A intersects the area defined by face B (if it exists).
|
|
|
|
// get the vertices of tested_face (used to estimate its normal etc.)
|
|
MCUT_ASSERT(ps_tested_face_to_vertices.find(tested_face) != ps_tested_face_to_vertices.end());
|
|
const std::vector<vec3>& tested_face_vertices = SAFE_ACCESS(ps_tested_face_to_vertices, tested_face);
|
|
|
|
// compute plane of tested_face
|
|
// -----------------------
|
|
|
|
MCUT_ASSERT(ps_tested_face_to_plane_normal.find(tested_face) != ps_tested_face_to_plane_normal.end());
|
|
const vec3& tested_face_plane_normal = SAFE_ACCESS(ps_tested_face_to_plane_normal, tested_face);
|
|
MCUT_ASSERT(ps_tested_face_to_plane_normal_d_param.find(tested_face) != ps_tested_face_to_plane_normal_d_param.end());
|
|
const double& tested_face_plane_param_d = SAFE_ACCESS(ps_tested_face_to_plane_normal_d_param, tested_face);
|
|
MCUT_ASSERT(ps_tested_face_to_plane_normal_max_comp.find(tested_face) != ps_tested_face_to_plane_normal_max_comp.end());
|
|
const int& tested_face_plane_normal_max_comp = SAFE_ACCESS(ps_tested_face_to_plane_normal_max_comp, tested_face); // compute_polygon_plane_coefficients(
|
|
|
|
vec3 intersection_point(0., 0., 0.); // the intersection point to be computed
|
|
|
|
char segment_intersection_type = compute_segment_plane_intersection_type( // exact**
|
|
tested_edge_h0_source_vertex,
|
|
tested_edge_h0_target_vertex,
|
|
tested_face_vertices,
|
|
tested_face_plane_normal,
|
|
tested_face_plane_normal_max_comp);
|
|
|
|
bool have_plane_intersection = (segment_intersection_type != '0'); // any intersection !
|
|
|
|
if (have_plane_intersection) {
|
|
if (segment_intersection_type != '1') {
|
|
bool violatedGP = false;
|
|
std::vector<const vec3*> points_touching_plane;
|
|
|
|
if (segment_intersection_type == 'q' || segment_intersection_type == 'r') {
|
|
points_touching_plane.push_back((segment_intersection_type == 'q') ? &tested_edge_h0_source_vertex : &tested_edge_h0_target_vertex);
|
|
} else {
|
|
points_touching_plane.push_back(&tested_edge_h0_source_vertex);
|
|
points_touching_plane.push_back(&tested_edge_h0_target_vertex);
|
|
}
|
|
|
|
for (std::vector<const vec3*>::const_iterator i = points_touching_plane.cbegin(); i != points_touching_plane.cend(); ++i) {
|
|
const vec3& point = (*(*i));
|
|
char result = compute_point_in_polygon_test(
|
|
point,
|
|
tested_face_vertices,
|
|
tested_face_plane_normal,
|
|
tested_face_plane_normal_max_comp);
|
|
if (result == 'i' || (result == 'v' || result == 'e')) {
|
|
violatedGP = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (violatedGP) {
|
|
status_t okay_status = status_t::SUCCESS;
|
|
bool exchanged = output.status.compare_exchange_strong(okay_status, status_t::GENERAL_POSITION_VIOLATION);
|
|
if (!input.enforce_general_position && exchanged) {
|
|
// only one thread can modify "lg". Thus, "lg" does not need to be locked in order to set
|
|
// the reason for failure
|
|
lg.set_reason_for_failure("invalid compute_segment_plane_intersection_type result ('" + std::to_string(segment_intersection_type) + "')");
|
|
}
|
|
return OutputStorageTypesTuple {};
|
|
; // stop immediately and do not complete the current job
|
|
} else {
|
|
// move onto the next edge-face test.
|
|
continue;
|
|
}
|
|
}
|
|
|
|
compute_segment_plane_intersection(
|
|
intersection_point,
|
|
tested_face_plane_normal,
|
|
tested_face_plane_param_d,
|
|
tested_edge_h0_source_vertex,
|
|
tested_edge_h0_target_vertex);
|
|
|
|
char in_poly_test_intersection_type = compute_point_in_polygon_test(
|
|
intersection_point,
|
|
tested_face_vertices,
|
|
// #if 1
|
|
tested_face_plane_normal,
|
|
// #else
|
|
tested_face_plane_normal_max_comp
|
|
// #endif
|
|
);
|
|
|
|
if (in_poly_test_intersection_type == 'v' || in_poly_test_intersection_type == 'e') {
|
|
status_t okay_status = status_t::SUCCESS;
|
|
bool exchanged = output.status.compare_exchange_strong(okay_status, status_t::GENERAL_POSITION_VIOLATION);
|
|
// output.status = status_t::GENERAL_POSITION_VIOLATION;
|
|
if (!input.enforce_general_position && exchanged) {
|
|
lg.set_reason_for_failure("invalid point-in-polygon test result ('" + std::to_string(in_poly_test_intersection_type) + "')");
|
|
}
|
|
return OutputStorageTypesTuple {};
|
|
; // stop immediately and do not complete the current job
|
|
}
|
|
|
|
bool have_point_in_polygon = in_poly_test_intersection_type == 'i';
|
|
|
|
if (have_point_in_polygon) {
|
|
|
|
fd_t face_pqr = tested_edge_face;
|
|
fd_t face_xyz = tested_face;
|
|
fd_t face_pqs = tested_edge_face == tested_edge_h0_face ? tested_edge_h1_face : hmesh_t::null_face();
|
|
|
|
vd_t new_vertex_descr((vd_t::index_type)intersection_points_LOCAL.size());
|
|
intersection_points_LOCAL.push_back(intersection_point); /*m0.add_vertex(intersection_point)*/
|
|
;
|
|
|
|
MCUT_ASSERT((size_t)new_vertex_descr == m0_ivtx_to_intersection_registry_entry_LOCAL.size() /*m0_ivtx_to_intersection_registry_entry.find(new_vertex_descr) == m0_ivtx_to_intersection_registry_entry.cend()*/);
|
|
m0_ivtx_to_intersection_registry_entry_LOCAL.push_back(std::make_pair(tested_edge, tested_face));
|
|
|
|
ps_intersecting_edges_LOCAL[tested_edge].push_back(new_vertex_descr);
|
|
|
|
const fd_t cm_face = tested_edge_belongs_to_cm ? tested_edge_face : tested_face;
|
|
const fd_t sm_face = tested_edge_belongs_to_cm ? tested_face : tested_edge_face;
|
|
|
|
if (tested_edge_belongs_to_cm) {
|
|
// NOTE: std::pair format/order is {source-mesh-face, cut-mesh-face}
|
|
cutpath_edge_creation_info_LOCAL[make_pair(tested_face, face_pqr)].push_back(new_vertex_descr);
|
|
if (face_pqs != hmesh_t::null_face()) {
|
|
cutpath_edge_creation_info_LOCAL[make_pair(tested_face, face_pqs)].push_back(new_vertex_descr);
|
|
}
|
|
} else {
|
|
cutpath_edge_creation_info_LOCAL[make_pair(tested_edge_face, tested_face)].push_back(new_vertex_descr);
|
|
const fd_t tested_edge_face_other = (tested_edge_face == tested_edge_h0_face) ? tested_edge_h1_face : tested_edge_h0_face;
|
|
|
|
if (tested_edge_face_other != hmesh_t::null_face()) {
|
|
cutpath_edge_creation_info_LOCAL[make_pair(tested_edge_face_other, tested_face)].push_back(new_vertex_descr);
|
|
}
|
|
}
|
|
|
|
if (tested_edge_belongs_to_cm) {
|
|
const bool is_border_reentrant_ivertex = ps.is_border(tested_edge); // ps.is_border(ps.edge(halfedge_pq));
|
|
|
|
if (is_border_reentrant_ivertex) {
|
|
cm_border_reentrant_ivtx_list_LOCAL.push_back(new_vertex_descr);
|
|
}
|
|
}
|
|
|
|
ps_iface_to_ivtx_list_LOCAL[tested_face].push_back(new_vertex_descr);
|
|
if (tested_edge_h0_face != hmesh_t::null_face()) {
|
|
ps_iface_to_ivtx_list_LOCAL[tested_edge_h0_face].push_back(new_vertex_descr);
|
|
}
|
|
if (tested_edge_h1_face != hmesh_t::null_face()) {
|
|
ps_iface_to_ivtx_list_LOCAL[tested_edge_h1_face].push_back(new_vertex_descr);
|
|
}
|
|
|
|
if (partial_cut_detected_LOCAL == false) { // keep checking until (locally) true
|
|
const bool is_cs_edge = ps_is_cutmesh_vertex(tested_edge_h0_source_descr, sm_vtx_cnt);
|
|
bool is_border = (tested_edge_h0_face == hmesh_t::null_face() || tested_edge_h1_face == hmesh_t::null_face());
|
|
partial_cut_detected_LOCAL = (is_cs_edge && is_border);
|
|
}
|
|
} // if (have_point_in_polygon)
|
|
} // if (have_plane_intersection) {
|
|
} // for (std::vector<fd_t>::const_iterator intersected_faces_iter = intersected_faces.cbegin(); intersected_faces_iter != intersected_faces.cend(); ++intersected_faces_iter) {
|
|
} // for (std::map<ed_t, std::vector<fd_t>>::const_iterator ps_edge_face_intersection_pairs_iter = ps_edge_face_intersection_pairs.cbegin(); ps_edge_face_intersection_pairs_iter != ps_edge_face_intersection_pairs.cend(); ps_edge_face_intersection_pairs_iter++) {
|
|
|
|
return local_output;
|
|
};
|
|
|
|
std::vector<std::future<OutputStorageTypesTuple>> futures;
|
|
OutputStorageTypesTuple partial_res;
|
|
parallel_for(
|
|
*input.scheduler,
|
|
ps_edge_face_intersection_pairs.cbegin(),
|
|
ps_edge_face_intersection_pairs.cend(),
|
|
fn_compute_intersection_points,
|
|
partial_res, // output computed by master thread
|
|
futures);
|
|
|
|
std::vector<vec3> intersection_points;
|
|
std::tie(
|
|
m0_ivtx_to_intersection_registry_entry,
|
|
cm_border_reentrant_ivtx_list,
|
|
ps_intersecting_edges,
|
|
cutpath_edge_creation_info,
|
|
ps_iface_to_ivtx_list,
|
|
partial_cut_detected,
|
|
intersection_points)
|
|
= partial_res;
|
|
|
|
// Add intersection points computed by master thread in to "m0" and
|
|
// account for intersection point offsets
|
|
for (std::vector<vec3>::const_iterator i = intersection_points.cbegin(); i != intersection_points.cend(); ++i) {
|
|
const vd_t stored_descr = m0.add_vertex(*i);
|
|
MCUT_ASSERT(stored_descr != hmesh_t::null_vertex());
|
|
}
|
|
|
|
for (int i = 0; i < (int)cm_border_reentrant_ivtx_list.size(); ++i) {
|
|
cm_border_reentrant_ivtx_list[i] += ps_vtx_cnt;
|
|
}
|
|
|
|
for (std::unordered_map<ed_t, std::vector<vd_t>>::iterator i = ps_intersecting_edges.begin();
|
|
i != ps_intersecting_edges.end(); ++i) {
|
|
for (std::vector<vd_t>::iterator j = i->second.begin(); j != i->second.end(); j++) {
|
|
*j += ps_vtx_cnt;
|
|
}
|
|
}
|
|
|
|
for (std::unordered_map<fd_t, std::vector<vd_t>>::iterator i = ps_iface_to_ivtx_list.begin();
|
|
i != ps_iface_to_ivtx_list.end(); ++i) {
|
|
for (std::vector<vd_t>::iterator j = i->second.begin(); j != i->second.end(); j++) {
|
|
*j += ps_vtx_cnt;
|
|
}
|
|
}
|
|
|
|
for (std::map<pair<fd_t>, std::vector<vd_t>>::iterator i = cutpath_edge_creation_info.begin();
|
|
i != cutpath_edge_creation_info.end(); ++i) {
|
|
for (std::vector<vd_t>::iterator j = i->second.begin(); j != i->second.end(); j++) {
|
|
*j += ps_vtx_cnt;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now we merge the results from futures
|
|
|
|
bool status_is_okay = true;
|
|
|
|
if (futures.empty()) { // only master thred
|
|
status_is_okay = (output.status.load() == status_t::SUCCESS);
|
|
if (!status_is_okay) {
|
|
return;
|
|
}
|
|
} else { // one or more worker threads
|
|
|
|
//
|
|
// This scope is executed by the master thread, which will merge results from other threads via std::futures
|
|
//
|
|
vd_t intersection_point_descr_baseoffset(m0.number_of_vertices());
|
|
|
|
// iterate through all available future so that we can 1) get their results
|
|
// and 2) ensure that all scheduled jobs are completed before exiting (or returning from)
|
|
// the kernel
|
|
for (int i_ = 0; i_ < (int)futures.size(); ++i_) {
|
|
|
|
std::future<OutputStorageTypesTuple>& f = futures[i_];
|
|
MCUT_ASSERT(f.valid()); // The behavior is undefined if valid()== false before the call to wait_for
|
|
|
|
OutputStorageTypesTuple future_result = f.get(); // "get()" is a blocking function
|
|
|
|
// As the master thread works to merge the partial results, it is possible that one of the
|
|
// worker threads detected a violation of general position. In this case, the current dispatch call
|
|
// needs to be stopped so that the front-end will be able to perturb the cut-mesh.
|
|
//
|
|
// Thus, we ask "did any worker-thread encounter a GP violation?" If so, we must stop all merging
|
|
// of partial results [and] wait for the currently running jobs (futures) to finish. Waiting is
|
|
// done automatically by calling f.get()
|
|
status_is_okay = (output.status.load() == status_t::SUCCESS);
|
|
|
|
if (!status_is_okay) {
|
|
continue; // skip future result
|
|
}
|
|
|
|
const std::vector<std::pair<ed_t, fd_t>>& m0_ivtx_to_intersection_registry_entry_FUTURE = std::get<0>(future_result);
|
|
const std::vector<vd_t>& cm_border_reentrant_ivtx_list_FUTURE = std::get<1>(future_result);
|
|
const std::unordered_map<ed_t, std::vector<vd_t>>& ps_intersecting_edges_FUTURE = std::get<2>(future_result);
|
|
const std::map<pair<fd_t>, std::vector<vd_t>>& cutpath_edge_creation_info_FUTURE = std::get<3>(future_result);
|
|
const std::unordered_map<fd_t, std::vector<vd_t>>& ps_iface_to_ivtx_list_FUTURE = std::get<4>(future_result);
|
|
const bool& partial_cut_detected_FUTURE = std::get<5>(future_result);
|
|
const std::vector<vec3>& intersection_points_FUTURE = std::get<6>(future_result);
|
|
const uint32_t intersection_points_in_future = (uint32_t)intersection_points_FUTURE.size();
|
|
|
|
MCUT_ASSERT(intersection_points_FUTURE.size() == m0_ivtx_to_intersection_registry_entry_FUTURE.size());
|
|
|
|
// add intersection point corresponding to the current future
|
|
for (std::vector<vec3>::const_iterator it = intersection_points_FUTURE.cbegin();
|
|
it != intersection_points_FUTURE.cend();
|
|
++it) {
|
|
const vd_t stored_descr = m0.add_vertex(*it);
|
|
MCUT_ASSERT(stored_descr != hmesh_t::null_vertex());
|
|
}
|
|
|
|
// merge m0_ivtx_to_intersection_registry_entry_FUTURE
|
|
m0_ivtx_to_intersection_registry_entry.insert(
|
|
m0_ivtx_to_intersection_registry_entry.end(),
|
|
m0_ivtx_to_intersection_registry_entry_FUTURE.cbegin(),
|
|
m0_ivtx_to_intersection_registry_entry_FUTURE.cend());
|
|
|
|
// merge cm_border_reentrant_ivtx_list_FUTURE
|
|
for (std::vector<vd_t>::const_iterator it = cm_border_reentrant_ivtx_list_FUTURE.cbegin();
|
|
it != cm_border_reentrant_ivtx_list_FUTURE.cend();
|
|
++it) {
|
|
const vd_t rel_descr = (*it);
|
|
const vd_t actual_descr = vd_t(intersection_point_descr_baseoffset + rel_descr);
|
|
cm_border_reentrant_ivtx_list.push_back(actual_descr);
|
|
}
|
|
|
|
// merge ps_intersecting_edges_FUTURE
|
|
for (std::unordered_map<ed_t, std::vector<vd_t>>::const_iterator i = ps_intersecting_edges_FUTURE.cbegin();
|
|
i != ps_intersecting_edges_FUTURE.cend();
|
|
++i) {
|
|
std::unordered_map<ed_t, std::vector<vd_t>>::iterator fiter = ps_intersecting_edges.find(i->first);
|
|
bool collision = (fiter != ps_intersecting_edges.end());
|
|
if (collision) { // another thread also encounter the same edge
|
|
for (std::vector<vd_t>::const_iterator j = i->second.cbegin(); j != i->second.cend(); ++j) {
|
|
const vd_t rel_descr = (*j);
|
|
const vd_t actual_descr = vd_t(intersection_point_descr_baseoffset + rel_descr);
|
|
if (std::find(fiter->second.cbegin(), fiter->second.cend(), *j) == fiter->second.cend()) {
|
|
fiter->second.push_back(actual_descr);
|
|
}
|
|
}
|
|
} else {
|
|
// just insert and account for offset
|
|
for (std::vector<vd_t>::const_iterator j = i->second.cbegin(); j != i->second.cend(); ++j) {
|
|
const vd_t rel_descr = (*j);
|
|
const vd_t actual_descr = vd_t(intersection_point_descr_baseoffset + rel_descr);
|
|
ps_intersecting_edges[i->first].push_back(actual_descr);
|
|
}
|
|
}
|
|
}
|
|
|
|
// merge cutpath_edge_creation_info_FUTURE
|
|
for (std::map<pair<fd_t>, std::vector<vd_t>>::const_iterator i = cutpath_edge_creation_info_FUTURE.cbegin();
|
|
i != cutpath_edge_creation_info_FUTURE.cend();
|
|
++i) {
|
|
std::map<pair<fd_t>, std::vector<vd_t>>::iterator fiter = cutpath_edge_creation_info.find(i->first);
|
|
bool collision = (fiter != cutpath_edge_creation_info.end());
|
|
if (collision) { // another thread also encounter the same key
|
|
for (std::vector<vd_t>::const_iterator j = i->second.cbegin(); j != i->second.cend(); ++j) {
|
|
const vd_t rel_descr = (*j);
|
|
const vd_t actual_descr = vd_t(intersection_point_descr_baseoffset + rel_descr);
|
|
if (std::find(fiter->second.cbegin(), fiter->second.cend(), actual_descr) == fiter->second.cend()) {
|
|
fiter->second.push_back(actual_descr);
|
|
}
|
|
}
|
|
} else {
|
|
// just insert and account for offset
|
|
for (std::vector<vd_t>::const_iterator j = i->second.cbegin(); j != i->second.cend(); ++j) {
|
|
const vd_t rel_descr = (*j);
|
|
const vd_t actual_descr = vd_t(intersection_point_descr_baseoffset + rel_descr);
|
|
cutpath_edge_creation_info[i->first].push_back(actual_descr);
|
|
}
|
|
}
|
|
}
|
|
|
|
// merge ps_iface_to_ivtx_list_FUTURE
|
|
for (std::unordered_map<fd_t, std::vector<vd_t>>::const_iterator i = ps_iface_to_ivtx_list_FUTURE.cbegin();
|
|
i != ps_iface_to_ivtx_list_FUTURE.cend();
|
|
++i) {
|
|
std::unordered_map<fd_t, std::vector<vd_t>>::iterator fiter = ps_iface_to_ivtx_list.find(i->first);
|
|
bool collision = (fiter != ps_iface_to_ivtx_list.end());
|
|
if (collision) { // another thread also encounter the same face
|
|
for (std::vector<vd_t>::const_iterator j = i->second.cbegin(); j != i->second.cend(); ++j) {
|
|
const vd_t rel_descr = (*j);
|
|
const vd_t actual_descr = vd_t(intersection_point_descr_baseoffset + rel_descr);
|
|
if (std::find(fiter->second.cbegin(), fiter->second.cend(), actual_descr) == fiter->second.cend()) {
|
|
fiter->second.push_back(actual_descr);
|
|
}
|
|
}
|
|
} else {
|
|
// just insert and account for offset
|
|
for (std::vector<vd_t>::const_iterator j = i->second.cbegin(); j != i->second.cend(); ++j) {
|
|
const vd_t rel_descr = (*j);
|
|
const vd_t actual_descr = vd_t(intersection_point_descr_baseoffset + rel_descr);
|
|
ps_iface_to_ivtx_list[i->first].push_back(actual_descr);
|
|
}
|
|
}
|
|
}
|
|
|
|
// merge (i.e. boolean-wise OR) partial_cut_detected_FUTURE
|
|
partial_cut_detected = (partial_cut_detected || partial_cut_detected_FUTURE) ? true : false;
|
|
|
|
// shift to account for the (number of) intersection points computed in the current future
|
|
intersection_point_descr_baseoffset += intersection_points_in_future;
|
|
} // while(!futures.empty()) {
|
|
} // if(num_blocks > 1)
|
|
|
|
if (!status_is_okay) {
|
|
// Safely return to the front-end since all jobs are now successively finished/cancelled.
|
|
//
|
|
// CAUTION: if the master thread returns before all current jobs are done (e.g.
|
|
// successively cancelled) the worker threads would end up accessing dangling
|
|
// references to e.g. variables like "ps", "m0", etc.
|
|
return;
|
|
}
|
|
} // end of parallel execution scope
|
|
#else
|
|
for (std::unordered_map<ed_t, std::vector<fd_t>>::const_iterator ps_edge_face_intersection_pairs_iter = ps_edge_face_intersection_pairs.cbegin();
|
|
ps_edge_face_intersection_pairs_iter != ps_edge_face_intersection_pairs.cend();
|
|
ps_edge_face_intersection_pairs_iter++) {
|
|
|
|
// our edge that we test for intersection with other faces
|
|
const ed_t tested_edge = ps_edge_face_intersection_pairs_iter->first;
|
|
// the faces against which the edge is tested for intersection
|
|
const std::vector<fd_t>& tested_faces = ps_edge_face_intersection_pairs_iter->second;
|
|
|
|
// the halfedges of our edge
|
|
const hd_t tested_edge_h0 = ps.halfedge(tested_edge, 0);
|
|
const hd_t tested_edge_h1 = ps.halfedge(tested_edge, 1);
|
|
|
|
// source vertex
|
|
const vertex_descriptor_t tested_edge_h0_source_descr = ps.source(tested_edge_h0);
|
|
const vec3& tested_edge_h0_source_vertex = ps.vertex(tested_edge_h0_source_descr);
|
|
// target vertex
|
|
const vertex_descriptor_t tested_edge_h0_target_descr = ps.target(tested_edge_h0);
|
|
const vec3& tested_edge_h0_target_vertex = ps.vertex(tested_edge_h0_target_descr);
|
|
|
|
// This boolean var is evaluated based on the fact that sm faces come before cm faces inside the "ps" data structure
|
|
const fd_t tested_edge_h0_face = ps.face(tested_edge_h0);
|
|
const fd_t tested_edge_h1_face = ps.face(tested_edge_h1);
|
|
const fd_t tested_edge_face = tested_edge_h0_face != hmesh_t::null_face() ? tested_edge_h0_face : tested_edge_h1_face;
|
|
const bool tested_edge_belongs_to_cm = ps_is_cutmesh_face(tested_edge_face, sm_face_count);
|
|
|
|
// for each face that is to be intersected with the tested-edge
|
|
for (std::vector<fd_t>::const_iterator tested_faces_iter = tested_faces.cbegin();
|
|
tested_faces_iter != tested_faces.cend();
|
|
++tested_faces_iter) {
|
|
const fd_t tested_face = *tested_faces_iter;
|
|
|
|
// We are now finding the intersection points determined by calculating the location
|
|
// where each halfedge of face A intersects the area defined by face B (if it exists).
|
|
|
|
// get the vertices of tested_face (used to estimate its normal etc.)
|
|
// std::vector<vd_t> tested_face_descriptors = ps.get_vertices_around_face(tested_face);
|
|
MCUT_ASSERT(ps_tested_face_to_vertices.find(tested_face) != ps_tested_face_to_vertices.end());
|
|
const std::vector<vec3>& tested_face_vertices = SAFE_ACCESS(ps_tested_face_to_vertices, tested_face);
|
|
|
|
// compute plane of tested_face
|
|
// -----------------------
|
|
|
|
MCUT_ASSERT(ps_tested_face_to_plane_normal.find(tested_face) != ps_tested_face_to_plane_normal.end());
|
|
const vec3& tested_face_plane_normal = SAFE_ACCESS(ps_tested_face_to_plane_normal, tested_face);
|
|
MCUT_ASSERT(ps_tested_face_to_plane_normal_d_param.find(tested_face) != ps_tested_face_to_plane_normal_d_param.end());
|
|
const double& tested_face_plane_param_d = SAFE_ACCESS(ps_tested_face_to_plane_normal_d_param, tested_face);
|
|
MCUT_ASSERT(ps_tested_face_to_plane_normal_max_comp.find(tested_face) != ps_tested_face_to_plane_normal_max_comp.end());
|
|
const int& tested_face_plane_normal_max_comp = SAFE_ACCESS(ps_tested_face_to_plane_normal_max_comp, tested_face);
|
|
|
|
vec3 intersection_point(0., 0., 0.); // the intersection po int to be computed
|
|
|
|
// TODO: replace this with shewchuck predicate (nasty failure on test 42)
|
|
// at least orient3d will be able to give use the corrent result!
|
|
#if 0
|
|
char lp_intersection_result = compute_line_plane_intersection(
|
|
intersection_point,
|
|
tested_edge_h0_source_vertex,
|
|
tested_edge_h0_target_vertex,
|
|
tested_face_vertices.data(),
|
|
tested_face_vertices.size(),
|
|
tested_face_plane_normal_max_comp,
|
|
tested_face_plane_normal,
|
|
tested_face_plane_param_d);
|
|
#else
|
|
char segment_intersection_type = compute_segment_plane_intersection_type( // exact**
|
|
tested_edge_h0_source_vertex,
|
|
tested_edge_h0_target_vertex,
|
|
tested_face_vertices,
|
|
tested_face_plane_normal,
|
|
tested_face_plane_normal_max_comp);
|
|
#endif
|
|
bool have_plane_intersection = (segment_intersection_type != '0'); // any intersection !
|
|
|
|
if (have_plane_intersection) { // does the segment intersect the plane?
|
|
|
|
if (segment_intersection_type != '1') { // the segment only touches the the plane (the line reprsented by segment still intersects the plane)
|
|
|
|
// before jumping-the-gun and assuming that we have indeed violated GP,
|
|
// we should check whether the point found to be on the plane (touching point) is
|
|
// actually [inside] the tested_face. That would imply cutting through a vertex or edge (which is undefined).
|
|
// If this is true then we have indeed violated GP. Otherwise, we just treat this as a non-intersection because
|
|
// the what-would-have-been intersection point actually lies outside the tested_face.
|
|
bool violatedGP = false;
|
|
std::vector<const vec3*> points_touching_plane;
|
|
|
|
if (segment_intersection_type == 'q' /*segment start*/ || segment_intersection_type == 'r' /*segment end*/) { // only one segment end is touching plane
|
|
points_touching_plane.push_back((segment_intersection_type == 'q') ? &tested_edge_h0_source_vertex : &tested_edge_h0_target_vertex);
|
|
} else { // both point are in the plane, so we will need to confirm whether BOTH are outside our tested face.
|
|
points_touching_plane.push_back(&tested_edge_h0_source_vertex);
|
|
points_touching_plane.push_back(&tested_edge_h0_target_vertex);
|
|
}
|
|
|
|
// if any point in "points_touching_plane" is inside the tested_face then we have violated GP
|
|
for (std::vector<const vec3*>::const_iterator i = points_touching_plane.cbegin(); i != points_touching_plane.cend(); ++i) {
|
|
const vec3& point = (*(*i));
|
|
char result = compute_point_in_polygon_test(
|
|
point,
|
|
tested_face_vertices,
|
|
tested_face_plane_normal,
|
|
tested_face_plane_normal_max_comp);
|
|
if (
|
|
// the touching point is inside, which implies cutting through a vertex (of "tested_edge")
|
|
result == 'i' ||
|
|
// The following condition means that we will have an edge-edge intersection anyway!
|
|
// i.e. 'v' means that two edges (from sm and cm) touch at their tips/points since point is an end point of "tested_edge"
|
|
// ... and 'e' means that an end point of "tested_edge" touches an edge of "tested_face"
|
|
(result == 'v' || result == 'e')) {
|
|
violatedGP = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (violatedGP) {
|
|
output.status = status_t::GENERAL_POSITION_VIOLATION;
|
|
if (!input.enforce_general_position) {
|
|
// Our assumption of having inputs in general position has been violated, we need to terminate
|
|
// with an error since perturbation (enforcement of general positions) is disabled by the user.
|
|
// Note: our intersection registry formulation requires that edges completely penetrate/intersect through polygon's area.
|
|
lg.set_reason_for_failure("invalid compute_segment_plane_intersection_type result ('" + std::to_string(segment_intersection_type) + "')");
|
|
}
|
|
return; // bail and return to the front-end
|
|
} else {
|
|
// same as the case where "have_plane_intersection" is false.
|
|
// so we just move onto the next edge-face test.
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// at this point, we have established that the segment actually intersects the plane [properly]
|
|
|
|
// ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
// Now we compute the [actual] intersection point (coordinates)
|
|
// and check whether it lies inside our polygon, or that GP has been violated,
|
|
// which happens if e.g. the intersection point lies on an edge/vertex of "tested_face")
|
|
|
|
// NOTE: if using fixed precision floats (i.e. double), then here we just care about getting the intersection point
|
|
// irrespective of whether "segment_intersection_result" is consistent with "segment_intersection_type" from above.
|
|
// The inconsistency can happen during edge cases. see e.g. test 42.
|
|
compute_segment_plane_intersection(
|
|
intersection_point,
|
|
tested_face_plane_normal,
|
|
tested_face_plane_param_d,
|
|
tested_edge_h0_source_vertex,
|
|
tested_edge_h0_target_vertex);
|
|
|
|
// is our intersection point in the polygon?
|
|
char in_poly_test_intersection_type = compute_point_in_polygon_test(
|
|
intersection_point,
|
|
tested_face_vertices,
|
|
tested_face_plane_normal,
|
|
tested_face_plane_normal_max_comp);
|
|
|
|
if (
|
|
// illegal on-edge and on-vertex intersections
|
|
(in_poly_test_intersection_type == 'v' || in_poly_test_intersection_type == 'e')) {
|
|
output.status = status_t::GENERAL_POSITION_VIOLATION;
|
|
if (!input.enforce_general_position) {
|
|
// Our assuption of having inputs in general position has been violated, we need to terminate
|
|
// with an error since perturbation (enforment of general positions) is disabled.
|
|
lg.set_reason_for_failure("invalid point-in-polygon test result ('" + std::to_string(in_poly_test_intersection_type) + "')");
|
|
}
|
|
return;
|
|
}
|
|
|
|
bool have_point_in_polygon = in_poly_test_intersection_type == 'i';
|
|
|
|
if (have_point_in_polygon) { // NOTE: point must be [inside] the polygon for us to consider it further
|
|
#if 0
|
|
// Intersection point is now determined to be in side face-B (our polygon), now we must use the information
|
|
// we computed from the segment-plane intersection test Check source-mesh for defectsto find out if general position has been violated (i.e.
|
|
// invalid case of cutting through a vertex)
|
|
if (segment_intersection_type == 'p' || segment_intersection_type == 'q' || segment_intersection_type == 'r') {
|
|
|
|
output.status = status_t::GENERAL_POSITION_VIOLATION;
|
|
|
|
if (!input.enforce_general_position) {
|
|
// Our assumption of having inputs in general position has been violated, we need to terminate
|
|
// with an error since perturbation (i.e. enforcement of general positions) is disabled.
|
|
// If any one of a segment's vertices only touch (i.e. lie on) the plane
|
|
// then that implies a situation of cutting through a vertex which is undefined.
|
|
lg.set_reason_for_failure("segment-plane intersection ('" + std::to_string(segment_intersection_type) + "')");
|
|
}
|
|
return;
|
|
}
|
|
vd_t pre_existing_copy = hmesh_t::null_vertex(); // set to correct value if intersection has already been computed
|
|
#endif
|
|
// The naming convention of these variables is based on Sifakis et al. 2007
|
|
// hd_t halfedge_pq = tested_edge_h0; // the halfedge which is intersected with polygon
|
|
// hd_t halfedge_pq_opp = tested_edge_h1; // ps.opposite(halfedge_pq);
|
|
fd_t face_pqr = tested_edge_face; // the face which is incident to halfedge-pq
|
|
fd_t face_xyz = tested_face; // the face which is intersected with halfedge-pq
|
|
fd_t face_pqs = tested_edge_face == tested_edge_h0_face ? tested_edge_h1_face : hmesh_t::null_face(); // ps.face(halfedge_pq_opp); // the face which is incident to the halfedge opposite to halfedge-pq
|
|
// fd_t face_pqX = hmesh_t::null_face(); // a virtual face pqX (where X denotes an unspecified auxiliary point)
|
|
|
|
#if 0
|
|
// add vertex if it does not exist.
|
|
// --------------------------------
|
|
|
|
|
|
const bool pq_is_indicent_on_pqr_and_pqs = (face_pqs != hmesh_t::null_face()); // pq is common to faces pqr and pqs
|
|
std::vector<fd_t> new_vertex_incident_ps_faces; // the list of faces which are incident to our intersection point
|
|
// NOTE: Two intersection vertices are same if they are incident on the same faces AND their registry halfedges are opposites
|
|
//bool computed_intersection_point_exists = false;
|
|
|
|
if (pq_is_indicent_on_pqr_and_pqs) {
|
|
|
|
// all three faces are defined and meet at the intersection point
|
|
new_vertex_incident_ps_faces.push_back(face_pqr);
|
|
new_vertex_incident_ps_faces.push_back(face_pqs);
|
|
new_vertex_incident_ps_faces.push_back(face_xyz);
|
|
|
|
} // if (pq_is_indicent_on_pqr_and_pqs) {
|
|
else {
|
|
// pqr is the only face incident to pq
|
|
// -----------------------------------
|
|
new_vertex_incident_ps_faces.push_back(face_pqr);
|
|
new_vertex_incident_ps_faces.push_back(face_pqX); // virtual face
|
|
new_vertex_incident_ps_faces.push_back(face_xyz);
|
|
}
|
|
#endif
|
|
#if 0
|
|
int fv_count = 0;
|
|
const bool on_face = point_on_face_plane(ps, tested_face, intersection_point, fv_count);
|
|
|
|
if (!on_face)
|
|
{
|
|
const vec3 normal = normalize(tested_face_plane_normal);
|
|
const double length = length(normal) ;
|
|
|
|
//MCUT_ASSERT(length == double(1.0));
|
|
const vec3& point_on_plane = tested_face_vertices.back(); // any vertex will do (assuming all vertices of face are coplanar)
|
|
const vec3 vec = (intersection_point - point_on_plane);
|
|
const double dot = dot_product(normal, vec);
|
|
intersection_point = intersection_point - (normal * dot);
|
|
point_on_face_plane(ps, tested_face, intersection_point, fv_count);
|
|
}
|
|
#endif
|
|
vd_t new_vertex_descr = m0.add_vertex(intersection_point);
|
|
|
|
#if 0
|
|
|
|
//
|
|
//
|
|
//
|
|
//
|
|
//
|
|
//
|
|
|
|
//
|
|
|
|
#endif
|
|
// m0_ivtx_to_ps_faces.insert(std::make_pair(new_vertex_descr, new_vertex_incident_ps_faces));
|
|
// m0_ivtx_to_ps_edge.insert(std::make_pair(new_vertex_descr, tested_edge));
|
|
MCUT_ASSERT((size_t)new_vertex_descr - ps_vtx_cnt == m0_ivtx_to_intersection_registry_entry.size() /*m0_ivtx_to_intersection_registry_entry.find(new_vertex_descr) == m0_ivtx_to_intersection_registry_entry.cend()*/);
|
|
m0_ivtx_to_intersection_registry_entry.push_back(std::make_pair(tested_edge, tested_face));
|
|
|
|
// ed_t e = ps.edge(halfedge_pq);
|
|
// bool edge_registered_as_intersecting = ps_intersecting_edges.find(tested_edge) != ps_intersecting_edges.cend();
|
|
|
|
ps_intersecting_edges[tested_edge].push_back(new_vertex_descr);
|
|
|
|
// intersection_test_ivtx_list.push_back(new_vertex_descr);
|
|
|
|
const fd_t cm_face = tested_edge_belongs_to_cm ? tested_edge_face : tested_face;
|
|
const fd_t sm_face = tested_edge_belongs_to_cm ? tested_face : tested_edge_face;
|
|
|
|
if (tested_edge_belongs_to_cm) {
|
|
// "tested_face" is from the source mesh
|
|
|
|
// NOTE: std::pair format/order is {source-mesh-face, cut-mesh-face}
|
|
cutpath_edge_creation_info[make_pair(tested_face, face_pqr)].push_back(new_vertex_descr);
|
|
|
|
if (face_pqs != hmesh_t::null_face()) {
|
|
|
|
cutpath_edge_creation_info[make_pair(tested_face, face_pqs)].push_back(new_vertex_descr);
|
|
}
|
|
} else {
|
|
|
|
cutpath_edge_creation_info[make_pair(tested_edge_face, tested_face)].push_back(new_vertex_descr);
|
|
|
|
const fd_t tested_edge_face_other = (tested_edge_face == tested_edge_h0_face) ? tested_edge_h1_face : tested_edge_h0_face;
|
|
|
|
if (tested_edge_face_other != hmesh_t::null_face()) {
|
|
|
|
cutpath_edge_creation_info[make_pair(tested_edge_face_other, tested_face)].push_back(new_vertex_descr);
|
|
}
|
|
}
|
|
|
|
// MCUT_ASSERT(m0_ivtx_to_tested_polygon_normal.count(new_vertex_descr) == 0);
|
|
// m0_ivtx_to_tested_polygon_normal[new_vertex_descr] = tested_face_plane_normal;
|
|
// MCUT_ASSERT(m0_ivtx_to_tested_polygon_normal.count(new_vertex_descr) == 1);
|
|
|
|
if (tested_edge_belongs_to_cm) { // halfedge_pq belongs to cut mesh
|
|
|
|
const bool is_border_reentrant_ivertex = ps.is_border(tested_edge); // ps.is_border(ps.edge(halfedge_pq));
|
|
|
|
if (is_border_reentrant_ivertex) {
|
|
|
|
cm_border_reentrant_ivtx_list.push_back(new_vertex_descr);
|
|
} // else // is regular
|
|
}
|
|
|
|
// map face to intersections points (reverse mapping of the intPoint-to-registryEntry)
|
|
ps_iface_to_ivtx_list[tested_face].push_back(new_vertex_descr);
|
|
if (tested_edge_h0_face != hmesh_t::null_face()) {
|
|
ps_iface_to_ivtx_list[tested_edge_h0_face].push_back(new_vertex_descr);
|
|
}
|
|
if (tested_edge_h1_face != hmesh_t::null_face()) {
|
|
ps_iface_to_ivtx_list[tested_edge_h1_face].push_back(new_vertex_descr);
|
|
}
|
|
|
|
if (partial_cut_detected == false) { // keep checking until true
|
|
// const vd_t v0 = ps.vertex(tested_edge, 0);
|
|
const bool is_cs_edge = ps_is_cutmesh_vertex(tested_edge_h0_source_descr, sm_vtx_cnt);
|
|
// partial_cut_detected = (is_cs_edge && ps.is_border(tested_edge));
|
|
bool is_border = (tested_edge_h0_face == hmesh_t::null_face() || tested_edge_h1_face == hmesh_t::null_face());
|
|
partial_cut_detected = (is_cs_edge && is_border);
|
|
}
|
|
|
|
} // if (have_point_in_polygon)
|
|
|
|
} // if (have_plane_intersection) {
|
|
} // for (std::vector<fd_t>::const_iterator intersected_faces_iter = intersected_faces.cbegin(); intersected_faces_iter != intersected_faces.cend(); ++intersected_faces_iter) {
|
|
|
|
} // for (std::map<ed_t, std::vector<fd_t>>::const_iterator ps_edge_face_intersection_pairs_iter = ps_edge_face_intersection_pairs.cbegin(); ps_edge_face_intersection_pairs_iter != ps_edge_face_intersection_pairs.cend(); ps_edge_face_intersection_pairs_iter++) {
|
|
#endif
|
|
|
|
// Create edges from the new intersection points
|
|
// ---------------------------------------------
|
|
|
|
if (m0_ivtx_to_intersection_registry_entry.empty()) {
|
|
lg.set_reason_for_failure("no face intersection found");
|
|
if (input.enforce_general_position && input.general_position_enforcement_count > 0) {
|
|
// This is not the first time we have invoked the kernel, which means that
|
|
// perturbation pushed the cut-mesh into a state/position/configuration that
|
|
// that does not actually intersect with the src-mesh. Thus, we will need to
|
|
// perturb again!
|
|
// By contruction, general position can only have been violated on inputs that
|
|
// where intersecting (in some way) to begin with i.e. with the input meshes
|
|
// as provided by the user. Thus, it would be incorrect to perturb the cut-mesh
|
|
// into an intersection-free configuration and then claim that we have
|
|
// successfully enforced general position. We want to make sure that after we
|
|
// perturb we can at least produce *some* cut.
|
|
output.status = status_t::GENERAL_POSITION_VIOLATION;
|
|
} else {
|
|
output.status = status_t::SUCCESS;
|
|
}
|
|
return;
|
|
}
|
|
|
|
TIMESTACK_POP();
|
|
|
|
#if 0
|
|
for (std::map<vd_t, std::pair<ed_t, fd_t>>::const_iterator entry_it = m0_ivtx_to_intersection_registry_entry.cbegin(); entry_it != m0_ivtx_to_intersection_registry_entry.cend(); ++entry_it)
|
|
{
|
|
//const vd_t& ipoint_descr = entry_it->first;
|
|
const ed_t &ipoint_iedge = entry_it->second.first;
|
|
const vd_t v0 = ps.vertex(ipoint_iedge, 0);
|
|
const bool is_cs_edge = ps_is_cutmesh_vertex(v0, sm_vtx_cnt);
|
|
|
|
//if () {
|
|
|
|
partial_cut_detected = (is_cs_edge && ps.is_border(ipoint_iedge));
|
|
if (partial_cut_detected)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// if (is_border) {
|
|
// partial_cut_detected = true;
|
|
// }
|
|
//}
|
|
}
|
|
#endif
|
|
|
|
if (partial_cut_detected && cm_border_reentrant_ivtx_list.size() == 0) {
|
|
// can happen with case when both the input mesh and cut surface are not watertight
|
|
}
|
|
|
|
if (input.verbose) {
|
|
dump_mesh(m0, "m0.v"); // containing only vertices (polygon soup vertices and newly computed intersection points)
|
|
}
|
|
|
|
if (partial_cut_detected) {
|
|
|
|
MCUT_ASSERT(!cm_is_watertight);
|
|
MCUT_ASSERT(!m0_ivtx_to_intersection_registry_entry.empty());
|
|
// MCUT_ASSERT(!m0_ivtx_to_ps_edge.empty());
|
|
|
|
if (input.require_looped_cutpaths) {
|
|
output.status = status_t::SUCCESS;
|
|
return;
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Check for degenerate mesh intersections
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
// TODO: this is redundnat (remove)
|
|
vertex_array_iterator_t m0_ivtx_iter_begin = m0.vertices_begin();
|
|
std::advance(m0_ivtx_iter_begin, ps_vtx_cnt); // offset to start of intersection vertices in hmesh_t (note: internal mesh data stored consecutively)
|
|
#if 0
|
|
//
|
|
// check if at-least one source mesh edge intersects any face of the cut mesh.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/*for each intersection cs face
|
|
for each halfedge of face
|
|
if halfedge intersects an im face and halfedge is a border halfedge
|
|
1) find all other border halfedges of faces which also intersect im face
|
|
if (1) > 0
|
|
check to ensure that at least one halfedge of the im face intersects the cs face
|
|
*/
|
|
|
|
// This check prevents malformed configurations where a cut-mesh face might stab/pierce
|
|
// a face of the source-mesh face while correctly intersecting another source-mesh face
|
|
// on the "other side". (Think of a wedge-like triangle stabbing a tet face while
|
|
// intersecting [two] other faces by cutting an edge in the tet).
|
|
bool atleast_one_sm_edge_intersects_an_cs_face = false;
|
|
|
|
// TODO: just loop over m0_ivtx_to_intersection_registry_entry
|
|
for (vertex_array_iterator_t i = m0_ivtx_iter_begin; i != m0.vertices_end(); ++i)
|
|
{
|
|
|
|
const ed_t &ps_edge = SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, (*i) - ps_vtx_cnt).first;
|
|
const vd_t ps_edge_v0 = ps.vertex(ps_edge, 0);
|
|
const bool is_sm_edge = !ps_is_cutmesh_vertex(ps_edge_v0, sm_vtx_cnt);
|
|
|
|
if (is_sm_edge)
|
|
{
|
|
atleast_one_sm_edge_intersects_an_cs_face = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!atleast_one_sm_edge_intersects_an_cs_face) {
|
|
// NOTE: the sm must intersect at least one face of the cs to allow for an opening on the sm boundary.
|
|
lg.set_reason_for_failure("found no edge in source mesh which intersects a cut mesh face.");
|
|
output.status = status_t::INVALID_MESH_INTERSECTION;
|
|
return;
|
|
}
|
|
#endif
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Create new edges along the intersection
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
TIMESTACK_PUSH("Create edges with intersection points "); // &&&&&
|
|
|
|
// A mapping from an intersecting ps-face to the new edges. These edges are those whose
|
|
// src and tgt vertices contain the respective face in their registry entry
|
|
// (note: all or some may be used for used to clip the face).
|
|
std::unordered_map<
|
|
fd_t, // A face intersecting another
|
|
std::vector<ed_t> // edges touching/incident on the intersecting face
|
|
>
|
|
ps_iface_to_m0_edge_list;
|
|
|
|
// Edges defining the cut path/line of intersecton/intersection contour
|
|
std::vector<ed_t> m0_cutpath_edges;
|
|
|
|
// A mapping from and ivertex to the incoming halfedges
|
|
std::unordered_map<
|
|
vd_t, // intersection point
|
|
std::vector<hd_t> // list of halfedges whose target is the intersection point
|
|
>
|
|
ivtx_to_incoming_hlist;
|
|
#if 0 // used for debugging colinearity bug, which occur when we have poly with eg. > 3
|
|
// vertices where at least 3 more-or-less are colinear but exact predicate says no.
|
|
for (std::map<pair<fd_t>, std::vector<vd_t>>::const_iterator cutpath_edge_creation_info_iter = cutpath_edge_creation_info.cbegin();
|
|
cutpath_edge_creation_info_iter != cutpath_edge_creation_info.cend();
|
|
++cutpath_edge_creation_info_iter) {
|
|
|
|
const fd_t sm_face = cutpath_edge_creation_info_iter->first.first;
|
|
const fd_t cm_face = cutpath_edge_creation_info_iter->first.second;
|
|
MCUT_ASSERT(!ps_is_cutmesh_face(sm_face, sm_face_count));
|
|
const std::vector<vd_t>& intersection_test_ivtx_list = cutpath_edge_creation_info_iter->second;
|
|
if ((int)intersection_test_ivtx_list.size() < 2) {
|
|
const vd_t ivtx = intersection_test_ivtx_list.back();
|
|
const ed_t& ps_edge = SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, ivtx - ps_vtx_cnt).first;
|
|
const fd_t ps_edge_f0 = ps.face(ps.halfedge(ps_edge, 0));
|
|
const fd_t ps_edge_f1 = ps.face(ps.halfedge(ps_edge, 1));
|
|
const fd_t ps_f = SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, ivtx - ps_vtx_cnt).second;
|
|
|
|
#if 0
|
|
auto dump_faces = [&](std::vector<fd_t> fv, std::string fpath)
|
|
{
|
|
std::ofstream file(fpath);
|
|
for(vertex_array_iterator_t v = ps.vertices_begin(); v != ps.vertices_end(); ++v)
|
|
{
|
|
file << "v " << ps.vertex(*v).x() << " " << ps.vertex(*v).y() << " " << ps.vertex(*v).z() << std::endl;
|
|
}
|
|
|
|
for(auto f : fv)
|
|
{
|
|
file << "f ";
|
|
std::vector<vd_t> verts = ps.get_vertices_around_face(f);
|
|
for(auto v : verts)
|
|
{
|
|
file << v+1 << " ";
|
|
}
|
|
file << "\n";
|
|
}
|
|
};
|
|
|
|
dump_faces({ ps_edge_f0, ps_edge_f1 }, "ps_edge.obj");
|
|
dump_faces({ ps_f }, "ps_f.obj");
|
|
|
|
dump_mesh(sm, "sm-.off");
|
|
dump_mesh(cs, "cm-.off");
|
|
#endif
|
|
|
|
output.status = status_t::GENERAL_POSITION_VIOLATION;
|
|
|
|
if (!input.enforce_general_position) {
|
|
|
|
// Our assumption of having inputs in general position has been violated, we need to terminate
|
|
// with an error since perturbation (i.e. enforcement of general positions) is disabled.
|
|
// If any one of a segment's vertices only touch (i.e. lie on) the plane
|
|
// then that implies a situation of cutting through a vertex which is undefined.
|
|
|
|
auto sm_or_cm = [&](fd_t f) {
|
|
return ps_is_cutmesh_face(f, sm_face_count) ? "cm" : "sm";
|
|
};
|
|
|
|
auto descr_v = [&](fd_t f) {
|
|
return ps_is_cutmesh_face(f, sm_face_count) ? f - sm_face_count : f;
|
|
};
|
|
|
|
char buff[128];
|
|
sprintf(buff, "edge(%s.f%d, %s.f%d) lies exactly on face %s.f%d\n",
|
|
sm_or_cm(ps_edge_f0), (int)descr_v(ps_edge_f0),
|
|
sm_or_cm(ps_edge_f1), (int)descr_v(ps_edge_f1),
|
|
sm_or_cm(ps_f), (int)descr_v(ps_f));
|
|
|
|
lg.set_reason_for_failure(buff);
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
for (std::map<pair<fd_t>, std::vector<vd_t>>::const_iterator cutpath_edge_creation_info_iter = cutpath_edge_creation_info.cbegin();
|
|
cutpath_edge_creation_info_iter != cutpath_edge_creation_info.cend();
|
|
++cutpath_edge_creation_info_iter) {
|
|
|
|
const fd_t sm_face = cutpath_edge_creation_info_iter->first.first;
|
|
const fd_t cm_face = cutpath_edge_creation_info_iter->first.second;
|
|
MCUT_ASSERT(!ps_is_cutmesh_face(sm_face, sm_face_count));
|
|
const std::vector<vd_t>& intersection_test_ivtx_list = cutpath_edge_creation_info_iter->second;
|
|
MCUT_ASSERT((int)intersection_test_ivtx_list.size() >= 2); // edge-case scenario: an edge intersects with another edge exactly
|
|
const uint32_t new_ivertices_count = (uint32_t)intersection_test_ivtx_list.size();
|
|
|
|
if (new_ivertices_count == 2) { // one edge
|
|
vd_t first_new_ivertex = intersection_test_ivtx_list.front();
|
|
vd_t second_new_ivertex = intersection_test_ivtx_list.back();
|
|
|
|
MCUT_ASSERT(m0_is_intersection_point(first_new_ivertex, ps_vtx_cnt));
|
|
MCUT_ASSERT(m0_is_intersection_point(second_new_ivertex, ps_vtx_cnt));
|
|
|
|
bool cutpath_edge_exists = m0.halfedge(first_new_ivertex, second_new_ivertex, true) != hmesh_t::null_halfedge();
|
|
if (
|
|
cutpath_edge_exists == false
|
|
//! interior_edge_exists(m0, first_new_ivertex, second_new_ivertex /*, m0_cutpath_edges*/)
|
|
) {
|
|
|
|
hd_t h = m0.add_edge(first_new_ivertex, second_new_ivertex);
|
|
MCUT_ASSERT(h != hmesh_t::null_halfedge());
|
|
|
|
m0_cutpath_edges.emplace_back(m0.edge(h));
|
|
|
|
// all newly created edges will lie on both face A and face B since intersection
|
|
// points lie on a line which is the intersection of the two planes of face A and B
|
|
ps_iface_to_m0_edge_list[sm_face].emplace_back(m0_cutpath_edges.back());
|
|
ps_iface_to_m0_edge_list[cm_face].emplace_back(m0_cutpath_edges.back());
|
|
|
|
update_neighouring_ps_iface_m0_edge_list(
|
|
first_new_ivertex,
|
|
second_new_ivertex,
|
|
ps,
|
|
sm_face,
|
|
cm_face,
|
|
m0_ivtx_to_intersection_registry_entry,
|
|
ps_iface_to_m0_edge_list,
|
|
m0_cutpath_edges);
|
|
|
|
MCUT_ASSERT(m0.target(h) == second_new_ivertex);
|
|
ivtx_to_incoming_hlist[second_new_ivertex].push_back(h);
|
|
|
|
MCUT_ASSERT(m0.target(m0.opposite(h)) == first_new_ivertex);
|
|
ivtx_to_incoming_hlist[first_new_ivertex].push_back(m0.opposite(h));
|
|
}
|
|
} else if (new_ivertices_count > 2) { // create N edges (N >= 1)
|
|
|
|
// our produced intersection points
|
|
std::vector<
|
|
std::pair<
|
|
vd_t, // descriptor
|
|
vec3 // coordinates
|
|
>>
|
|
ivertex_coords;
|
|
|
|
for (uint32_t v = 0; v < new_ivertices_count; ++v) {
|
|
vd_t new_ivertex_descr = intersection_test_ivtx_list[v];
|
|
MCUT_ASSERT(m0_is_intersection_point(new_ivertex_descr, ps_vtx_cnt));
|
|
ivertex_coords.emplace_back(new_ivertex_descr, m0.vertex(new_ivertex_descr));
|
|
}
|
|
|
|
std::vector<vd_t> sorted_descriptors = linear_projection_sort(ivertex_coords);
|
|
|
|
// for (std::vector<std::pair<vd_t, vec3>>::const_iterator iter = ivertex_coords.cbegin() + 1; iter != ivertex_coords.cend(); ++iter) {
|
|
for (std::vector<vd_t>::const_iterator iter = sorted_descriptors.cbegin() + 1; iter != sorted_descriptors.cend(); ++iter) {
|
|
// const vd_t src_vertex = (iter - 1)->first;
|
|
// const vd_t tgt_vertex = (iter)->first;
|
|
const vd_t src_vertex = *(iter - 1);
|
|
const vd_t tgt_vertex = *(iter);
|
|
|
|
bool cutpath_edge_exists = m0.halfedge(src_vertex, tgt_vertex, true) != hmesh_t::null_halfedge();
|
|
|
|
if (cutpath_edge_exists == false) {
|
|
|
|
// Here we also check whether the edge actually lies on the area of two [shared polygons]
|
|
// in the registry entries of its vertices. This operation is fundamentally geometric
|
|
// and cannot be resolved using topology (we have insufficient information to identify
|
|
// intersection edges). See benchmark test 34
|
|
// Thus, we will not add the edge if its mid-point does not lie in the area of [two]
|
|
// of the shared faces in the resgistry entries of its vertices (intersection points)..
|
|
// std::vector<std::pair<ed_t, fd_t>>::const_iterator find_iter = m0_ivtx_to_intersection_registry_entry.cend();
|
|
|
|
// get intersection-registry faces of src vertex
|
|
// find_iter = m0_ivtx_to_intersection_registry_entry.find(src_vertex);
|
|
MCUT_ASSERT((size_t)src_vertex - ps_vtx_cnt < m0_ivtx_to_intersection_registry_entry.size() /*find_iter != m0_ivtx_to_intersection_registry_entry.cend()*/);
|
|
const std::vector<fd_t> src_vertex_faces = ps_get_ivtx_registry_entry_faces(ps, SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, (std::size_t)src_vertex - ps_vtx_cnt) /*find_iter->second*/);
|
|
|
|
// get intersection-registry faces of tgt vertex
|
|
// find_iter = m0_ivtx_to_intersection_registry_entry.find(tgt_vertex);
|
|
MCUT_ASSERT((size_t)tgt_vertex - ps_vtx_cnt < m0_ivtx_to_intersection_registry_entry.size() /*find_iter != m0_ivtx_to_intersection_registry_entry.cend()*/);
|
|
const std::vector<fd_t> tgt_vertex_faces = ps_get_ivtx_registry_entry_faces(ps, SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, (std::size_t)tgt_vertex - ps_vtx_cnt) /*find_iter->second*/);
|
|
|
|
std::vector<fd_t> shared_faces;
|
|
std::copy_if(src_vertex_faces.begin(), src_vertex_faces.end(), std::back_inserter(shared_faces),
|
|
[&](fd_t f) {
|
|
return !is_virtual_face(f) && std::find(tgt_vertex_faces.cbegin(), tgt_vertex_faces.cend(), f) != tgt_vertex_faces.cend();
|
|
});
|
|
|
|
MCUT_ASSERT(shared_faces.size() >= 2); // connectable intersection points must match by 2 or more faces
|
|
|
|
// compute edge mid-point (could be any point along the edge that is not one of the vertices)
|
|
const vec3& src_vertex_coords = m0.vertex(src_vertex);
|
|
const vec3& tgt_vertex_coords = m0.vertex(tgt_vertex);
|
|
const vec3 midpoint = (tgt_vertex_coords + src_vertex_coords) * double(0.5);
|
|
|
|
std::vector<int> shared_faces_containing_edge;
|
|
// for each shared face
|
|
for (std::vector<fd_t>::const_iterator sf_iter = shared_faces.cbegin(); sf_iter != shared_faces.cend(); ++sf_iter) {
|
|
const fd_t shared_face = *sf_iter;
|
|
|
|
MCUT_ASSERT(ps_tested_face_to_plane_normal.find(shared_face) != ps_tested_face_to_plane_normal.cend());
|
|
const vec3& shared_face_plane_normal = SAFE_ACCESS(ps_tested_face_to_plane_normal, shared_face);
|
|
|
|
MCUT_ASSERT(ps_tested_face_to_plane_normal_max_comp.find(shared_face) != ps_tested_face_to_plane_normal_max_comp.cend());
|
|
int shared_face_normal_max_comp = SAFE_ACCESS(ps_tested_face_to_plane_normal_max_comp, shared_face);
|
|
|
|
MCUT_ASSERT(ps_tested_face_to_vertices.find(shared_face) != ps_tested_face_to_vertices.cend());
|
|
const std::vector<vec3>& shared_face_vertices = SAFE_ACCESS(ps_tested_face_to_vertices, shared_face);
|
|
|
|
char in_poly_test_intersection_type = compute_point_in_polygon_test(
|
|
midpoint,
|
|
shared_face_vertices,
|
|
shared_face_plane_normal,
|
|
shared_face_normal_max_comp);
|
|
|
|
if (in_poly_test_intersection_type == 'i') {
|
|
const int idx = (int)std::distance(shared_faces.cbegin(), sf_iter);
|
|
shared_faces_containing_edge.push_back(idx);
|
|
}
|
|
}
|
|
|
|
MCUT_ASSERT((int)shared_faces_containing_edge.size() <= 2);
|
|
|
|
if ((int)shared_faces_containing_edge.size() == 2) {
|
|
|
|
const hd_t h = m0.add_edge(src_vertex, tgt_vertex); // insert segment!
|
|
|
|
MCUT_ASSERT(h != hmesh_t::null_halfedge());
|
|
m0_cutpath_edges.emplace_back(m0.edge(h));
|
|
|
|
// NOTE: here we add all edge without assuming anything about which of the will be used to clip either polygon
|
|
// ps_iface_to_m0_edge_list[sm_face].emplace_back(m0_cutpath_edges.back());
|
|
// ps_iface_to_m0_edge_list[cm_face].emplace_back(m0_cutpath_edges.back());
|
|
for (std::vector<int>::const_iterator i = shared_faces_containing_edge.cbegin(); i != shared_faces_containing_edge.cend(); ++i) {
|
|
const fd_t shared_face = SAFE_ACCESS(shared_faces, *i);
|
|
ps_iface_to_m0_edge_list[shared_face].emplace_back(m0_cutpath_edges.back());
|
|
}
|
|
|
|
// update_neighouring_ps_iface_m0_edge_list(src_vertex, tgt_vertex, ps,
|
|
// sm_face,
|
|
// cm_face,
|
|
// m0_ivtx_to_intersection_registry_entry,
|
|
// ps_iface_to_m0_edge_list,
|
|
// m0_cutpath_edges);
|
|
|
|
MCUT_ASSERT(m0.target(h) == tgt_vertex);
|
|
ivtx_to_incoming_hlist[tgt_vertex].push_back(h);
|
|
|
|
MCUT_ASSERT(m0.target(m0.opposite(h)) == src_vertex);
|
|
ivtx_to_incoming_hlist[src_vertex].push_back(m0.opposite(h));
|
|
}
|
|
}
|
|
}
|
|
} // else if (new_ivertices_count > 2) {
|
|
}
|
|
|
|
TIMESTACK_POP();
|
|
|
|
// NOTE: at this stage we have all vertices and edges which are needed to clip
|
|
// intersecting faces in the polygon-soup ("ps").
|
|
|
|
if (input.verbose) {
|
|
dump_mesh(m0, "m0.v.e"); // containing only vertices & edges
|
|
}
|
|
|
|
const uint32_t m0_num_cutpath_edges = (uint32_t)m0_cutpath_edges.size();
|
|
const uint32_t m0_num_cutpath_halfedges = m0_num_cutpath_edges * 2;
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Find cut-paths (the boundaries of the openings/holes in the source mesh)
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
TIMESTACK_PUSH("Find cut-paths ");
|
|
|
|
// We are now going to search for all the cut-paths created from the intersection
|
|
// between source- and cut-mesh faces. Some of these cut-paths identify holes to be
|
|
// filled while others indentify separation/slitting of the src-mesh.
|
|
|
|
// We start off by creating "bins" : each bin corresonds to an intersection point
|
|
// and the values/elements in that bin are the [cut-path edges] connected to it.
|
|
|
|
std::unordered_map<vd_t, std::vector<ed_t>> m0_ivtx_to_cutpath_edges;
|
|
|
|
for (std::vector<ed_t>::const_iterator cutpath_edge_iter = m0_cutpath_edges.cbegin();
|
|
cutpath_edge_iter != m0_cutpath_edges.cend();
|
|
++cutpath_edge_iter) {
|
|
const ed_t& edge = *cutpath_edge_iter;
|
|
const vd_t vertex0 = m0.vertex(edge, 0);
|
|
const vd_t vertex1 = m0.vertex(edge, 1);
|
|
m0_ivtx_to_cutpath_edges[vertex0].push_back(edge);
|
|
m0_ivtx_to_cutpath_edges[vertex1].push_back(edge);
|
|
}
|
|
|
|
//... every intersection point is connected to at least onecut-path edge
|
|
MCUT_ASSERT(m0_ivtx_to_cutpath_edges.empty() == false);
|
|
|
|
// build implicit cut-path sequences (a sorted set of connected edges)
|
|
// -----------------------------------------------------------------------
|
|
|
|
// An "implicit" cut-path sequence is a list of cut-path edges that are sorted (i.e.
|
|
// this means that in memory, edges are placed next to others they connect to).
|
|
|
|
std::vector<std::vector<ed_t>> m0_cutpath_sequences;
|
|
std::unordered_map<vd_t, int> m0_ivtx_to_cutpath_sequence;
|
|
std::unordered_map<ed_t, int> m0_edge_to_cutpath_sequence;
|
|
|
|
do { // an iteration will build a cut-path sequence
|
|
|
|
// const int diff = (int)m0_ivtx_to_cutpath_edges.size() - (int)m0_ivtx_to_cutpath_sequence.size();
|
|
MCUT_ASSERT((int)m0_ivtx_to_cutpath_edges.size() - (int)m0_ivtx_to_cutpath_sequence.size() >= 2); // need a minimum of 2 intersection points (one edge) to form a sequence
|
|
|
|
int cur_cutpath_sequence_index = (int)m0_cutpath_sequences.size();
|
|
|
|
// start from an intersection point that is not yet mapped-to/associated-with a
|
|
// sequence in "cur_cutpath_sequence"
|
|
// pick the vertex which is a terminal vertex (to start search from beginning of sequence)
|
|
// or any vertex (if there are not terminal vertices)
|
|
|
|
// find any intersection point which is not associated with a cut-path and is connected to one edge (terminal vertex)
|
|
std::unordered_map<vd_t, std::vector<ed_t>>::const_iterator m0_ivtx_to_cutpath_edges_iter = std::find_if(
|
|
m0_ivtx_to_cutpath_edges.cbegin(), m0_ivtx_to_cutpath_edges.cend(),
|
|
[&](const std::pair<vd_t, std::vector<ed_t>>& elem) {
|
|
bool is_mapped = m0_ivtx_to_cutpath_sequence.find(elem.first) != m0_ivtx_to_cutpath_sequence.cend();
|
|
bool is_connected_to_one_edge = elem.second.size() == 1;
|
|
return (!is_mapped && is_connected_to_one_edge);
|
|
});
|
|
|
|
if (m0_ivtx_to_cutpath_edges_iter == m0_ivtx_to_cutpath_edges.cend()) { // we could not find any intersection point from above
|
|
// find any intersection point which is not mapped to a cut-path (less strict condition that above)
|
|
m0_ivtx_to_cutpath_edges_iter = std::find_if(
|
|
m0_ivtx_to_cutpath_edges.cbegin(), m0_ivtx_to_cutpath_edges.cend(),
|
|
[&](const std::pair<vd_t, std::vector<ed_t>>& elem) {
|
|
bool is_mapped = m0_ivtx_to_cutpath_sequence.find(elem.first) != m0_ivtx_to_cutpath_sequence.cend();
|
|
return !is_mapped;
|
|
});
|
|
}
|
|
|
|
if (m0_ivtx_to_cutpath_edges_iter == m0_ivtx_to_cutpath_edges.cend()) { // still could not find any unmapped intersection point
|
|
break; // done (found all implicit cut paths)
|
|
}
|
|
|
|
// start new sequence of edges
|
|
// ---------------------------
|
|
|
|
m0_cutpath_sequences.emplace_back(std::vector<ed_t>());
|
|
std::vector<ed_t>& cur_cutpath_sequence = m0_cutpath_sequences.back();
|
|
|
|
// vertex at the beginning of the sequence
|
|
const vd_t& first_vertex_of_sequence = m0_ivtx_to_cutpath_edges_iter->first;
|
|
|
|
// the edges connected to our first intersection point
|
|
const std::vector<ed_t>& cutpath_edges_connected_to_first_vertex = m0_ivtx_to_cutpath_edges_iter->second;
|
|
|
|
// pick the edge that is not yet mapped-to/associated-with a cut-path
|
|
// sequence in "cur_cutpath_sequence". Note: if the current sequence
|
|
// is linear, then there is no possibility that one of the edges
|
|
// in "cutpath_edges_connected_to_first_vertex" has already been mapped-to/associated-with
|
|
// a disjoint cut-path sequence in "cur_cutpath_sequence".
|
|
// This is because sequence discovery starts by first searching from terminal vertices/edges
|
|
// (see above conditions).
|
|
std::vector<ed_t>::const_iterator incident_edge_find_iter = std::find_if(
|
|
cutpath_edges_connected_to_first_vertex.cbegin(),
|
|
cutpath_edges_connected_to_first_vertex.cend(),
|
|
[&](const ed_t& incident_edge) {
|
|
return m0_edge_to_cutpath_sequence.find(incident_edge) == m0_edge_to_cutpath_sequence.cend();
|
|
});
|
|
|
|
MCUT_ASSERT(incident_edge_find_iter != cutpath_edges_connected_to_first_vertex.cend());
|
|
|
|
const ed_t& first_edge = *incident_edge_find_iter;
|
|
|
|
// now we will iteratively add edges into the current sequence, starting from "first_edge".
|
|
// the next added edge is alway one which share's the "next_vertex" with the current.
|
|
// ----------------------------------------------------------------------------------------
|
|
|
|
vd_t current_vertex = hmesh_t::null_vertex();
|
|
ed_t current_edge = hmesh_t::null_edge();
|
|
vd_t next_vertex = first_vertex_of_sequence; // ... initial intersection point
|
|
ed_t next_edge = first_edge;
|
|
|
|
do { // an iteration will add an edge to the current cut-path sequence
|
|
|
|
// update state
|
|
current_vertex = next_vertex;
|
|
current_edge = next_edge;
|
|
|
|
// add edge
|
|
cur_cutpath_sequence.emplace_back(current_edge);
|
|
|
|
// map vertex to current disjoint implicit cut-path sequence
|
|
MCUT_ASSERT(m0_ivtx_to_cutpath_sequence.count(current_vertex) == 0);
|
|
m0_ivtx_to_cutpath_sequence[current_vertex] = cur_cutpath_sequence_index;
|
|
MCUT_ASSERT(m0_ivtx_to_cutpath_sequence.count(current_vertex) == 1);
|
|
|
|
// map edge to current disjoint implicit cut-path sequence
|
|
MCUT_ASSERT(m0_edge_to_cutpath_sequence.count(current_edge) == 0);
|
|
m0_edge_to_cutpath_sequence[current_edge] = cur_cutpath_sequence_index;
|
|
MCUT_ASSERT(m0_edge_to_cutpath_sequence.count(current_edge) == 1);
|
|
|
|
// reset state
|
|
next_vertex = hmesh_t::null_vertex();
|
|
next_edge = hmesh_t::null_edge();
|
|
|
|
// resolve next vertex (..since we don't know whether vertex0 or vertex1 is "current_vertex")
|
|
const vd_t current_edge_vertex0 = m0.vertex(current_edge, 0);
|
|
const vd_t current_edge_vertex1 = m0.vertex(current_edge, 1);
|
|
|
|
// "next_vertex" is whichever vertex of the current edge that is not
|
|
// equal to the "current_vertex"
|
|
if (current_vertex == current_edge_vertex0) {
|
|
next_vertex = current_edge_vertex1;
|
|
} else {
|
|
next_vertex = current_edge_vertex0;
|
|
}
|
|
|
|
// now that we have the next vertex, we can determine the next edge
|
|
// ----------------------------------------------------------------
|
|
|
|
// check if next vertex has already been associated with the cut-path sequence.
|
|
bool reached_end_of_sequence = m0_ivtx_to_cutpath_sequence.find(next_vertex) != m0_ivtx_to_cutpath_sequence.cend();
|
|
|
|
if (!reached_end_of_sequence) {
|
|
// get the other edge connected to "next_vertex" i.e. the edge which is not the "current_edge"
|
|
m0_ivtx_to_cutpath_edges_iter = m0_ivtx_to_cutpath_edges.find(next_vertex);
|
|
MCUT_ASSERT(m0_ivtx_to_cutpath_edges_iter != m0_ivtx_to_cutpath_edges.cend());
|
|
|
|
const std::vector<ed_t>& cutpath_edges_connected_to_next_vertex = m0_ivtx_to_cutpath_edges_iter->second;
|
|
MCUT_ASSERT(cutpath_edges_connected_to_next_vertex.size() <= 2);
|
|
|
|
bool current_edge_is_terminal = (cutpath_edges_connected_to_next_vertex.size() == 1);
|
|
|
|
if (current_edge_is_terminal == false) {
|
|
const ed_t& edge0 = cutpath_edges_connected_to_next_vertex.front();
|
|
const ed_t& edge1 = cutpath_edges_connected_to_next_vertex.back();
|
|
const ed_t& other_edge = (current_edge == edge0) ? edge1 : edge0;
|
|
|
|
// check that "other_edge" has not already been mapped to a disjoint implicit cutpath sequence
|
|
std::unordered_map<ed_t, int>::const_iterator find_iter = m0_edge_to_cutpath_sequence.find(other_edge);
|
|
bool other_edge_is_already_mapped = (find_iter != m0_edge_to_cutpath_sequence.cend());
|
|
|
|
if (other_edge_is_already_mapped == false) {
|
|
next_edge = other_edge; // set sext edge
|
|
} else {
|
|
// reached end of sequence
|
|
MCUT_ASSERT(m0_ivtx_to_cutpath_sequence.count(next_vertex) == 0);
|
|
// need to update this state here because we wont jump back up to the top of the loop as in the normal case.
|
|
// This is because "next_edge" is null, and the do-while loop continues iff "next_edge != hmesh_t::null_edge()"
|
|
m0_ivtx_to_cutpath_sequence[next_vertex] = cur_cutpath_sequence_index;
|
|
|
|
MCUT_ASSERT(m0_ivtx_to_cutpath_sequence.count(next_vertex) == 1);
|
|
}
|
|
} // if (current_edge_is_terminal == false) {
|
|
else {
|
|
m0_ivtx_to_cutpath_sequence[next_vertex] = cur_cutpath_sequence_index;
|
|
}
|
|
} // if (!reached_end_of_sequence) {
|
|
|
|
// while there is another edge to added to the current disjoint implicit cutpath sequence
|
|
} while (next_edge != hmesh_t::null_edge());
|
|
|
|
// while not all intersection-points have been mapped to a disjoint implicit cutpath sequence
|
|
} while (m0_edge_to_cutpath_sequence.size() != m0_cutpath_edges.size());
|
|
|
|
MCUT_ASSERT(m0_cutpath_sequences.empty() == false);
|
|
|
|
// delink the implicit cut-path sequences to create the final explicit cut-path sequences
|
|
// --------------------------------------------------------------------------------------
|
|
|
|
m0_cutpath_edges.clear(); // free
|
|
|
|
m0_edge_to_cutpath_sequence.clear(); // free
|
|
// m0_ivtx_to_cutpath_edges.clear(); // free
|
|
// m0_cutpath_sequences.clear(); // free
|
|
|
|
TIMESTACK_POP();
|
|
|
|
MCUT_ASSERT(m0_cutpath_sequences.empty() == false);
|
|
|
|
const int num_explicit_cutpath_sequences = (int)m0_cutpath_sequences.size();
|
|
|
|
// save cut-path sequence properties (linear/circular;is_hole)
|
|
// -----------------------------------------------------------------------
|
|
|
|
// first we need to find all intersection points which have a border source-mesh
|
|
// halfedge in their intersection registry. We need this data structure to allow
|
|
// us to determine the properties of the cut-paths
|
|
//
|
|
|
|
TIMESTACK_PUSH("Infer cutpath info");
|
|
|
|
//
|
|
// MapKey=intersection point on a border halfedge of either the source-mesh or cut-mesh
|
|
// MapValue=pointer entry in "m0_ivtx_to_ps_edge"
|
|
std::map<vd_t, std::vector<std::pair<ed_t, fd_t>>::const_iterator> m0_cutpath_terminal_vertices;
|
|
|
|
for (std::vector<std::pair<ed_t, fd_t>>::const_iterator iter = m0_ivtx_to_intersection_registry_entry.cbegin();
|
|
iter != m0_ivtx_to_intersection_registry_entry.cend();
|
|
++iter) {
|
|
|
|
const vd_t& ivtx = vd_t((std::uint32_t)std::distance(m0_ivtx_to_intersection_registry_entry.cbegin(), iter) + ps_vtx_cnt); // iter->first;
|
|
|
|
const ed_t edge_of_ivtx_ps_he = iter->first; // iter->second.first; // ps.edge(ivtx_ps_he);
|
|
// check that "ivtx_ps_he" is a border halfedge
|
|
if (ps.is_border(edge_of_ivtx_ps_he)) {
|
|
// we have found a terminal vertex
|
|
MCUT_ASSERT(m0_cutpath_terminal_vertices.count(ivtx) == 0);
|
|
m0_cutpath_terminal_vertices[ivtx] = iter;
|
|
MCUT_ASSERT(m0_cutpath_terminal_vertices.count(ivtx) == 1);
|
|
}
|
|
}
|
|
|
|
// MapKey=index of an explicit cutpath in m0_cutpath_sequences
|
|
// MapValue=a tuple of boolean properties (is_linear, is_hole, is_srcmesh_severing).
|
|
//
|
|
// if is_linear is false, then the cut path is "circular" and "is_hole" will
|
|
// always be true in this specific case.
|
|
// if is_linear is true, then the cutpath may or may not be a source-mesh severing cutpath (depends on
|
|
// whether we have a partial cut or not)
|
|
// if is_circular is true, then the cutpath is always severing.
|
|
std::map<int, std::tuple<bool, bool, bool>> m0_cutpath_sequence_to_properties;
|
|
|
|
for (std::vector<std::vector<ed_t>>::const_iterator iter = m0_cutpath_sequences.cbegin();
|
|
iter != m0_cutpath_sequences.cend();
|
|
++iter) {
|
|
|
|
const int cutpath_index = (int)std::distance(m0_cutpath_sequences.cbegin(), iter);
|
|
|
|
const std::vector<ed_t>& cutpath = *iter;
|
|
|
|
MCUT_ASSERT(m0_cutpath_sequence_to_properties.count(cutpath_index) == 0);
|
|
m0_cutpath_sequence_to_properties[cutpath_index] = std::tuple<bool, bool, bool>();
|
|
|
|
MCUT_ASSERT(m0_cutpath_sequence_to_properties.count(cutpath_index) == 1);
|
|
|
|
std::tuple<bool, bool, bool>& properties = m0_cutpath_sequence_to_properties[cutpath_index];
|
|
bool& cutpath_is_linear = std::get<0>(properties);
|
|
bool& cutpath_is_hole = std::get<1>(properties);
|
|
bool& cutpath_is_srcmesh_severing = std::get<2>(properties); // i.e. the cutpath severs/partitions the src-mesh into two parts
|
|
|
|
cutpath_is_linear = false;
|
|
cutpath_is_hole = false;
|
|
cutpath_is_srcmesh_severing = true;
|
|
|
|
// check if it is a linear cut path
|
|
|
|
const ed_t& first_edge = cutpath.front();
|
|
const vd_t first_edge_vertex0 = m0.vertex(first_edge, 0);
|
|
const vd_t first_edge_vertex1 = m0.vertex(first_edge, 1);
|
|
bool first_edge_vertex0_is_terminal = m0_cutpath_terminal_vertices.find(first_edge_vertex0) != m0_cutpath_terminal_vertices.cend();
|
|
|
|
bool first_edge_is_terminal = first_edge_vertex0_is_terminal;
|
|
|
|
if (first_edge_vertex0_is_terminal == false) {
|
|
// check if vertex1 is terminal
|
|
bool first_edge_vertex1_is_terminal = m0_cutpath_terminal_vertices.find(first_edge_vertex1) != m0_cutpath_terminal_vertices.cend();
|
|
first_edge_is_terminal = first_edge_vertex1_is_terminal;
|
|
}
|
|
|
|
// note: by construction, if the first edge is terminal then the
|
|
// last edge will also be terminal (thus we could have used the
|
|
// last edge for the above tests too!)
|
|
cutpath_is_linear = first_edge_is_terminal;
|
|
|
|
bool cutpath_is_circular = !cutpath_is_linear;
|
|
// check if a hole is created by the cutpath (which will need sealing later)
|
|
if (cutpath_is_circular) {
|
|
cutpath_is_hole = true;
|
|
} else {
|
|
// current cut path is [linear]. it creates a hole (in the source mesh) if both terminal vertices
|
|
// have a cut-mesh halfedge in their registry
|
|
|
|
const vd_t& first_edge_terminal_vertex = (first_edge_vertex0_is_terminal ? first_edge_vertex0 : first_edge_vertex1);
|
|
|
|
// get the halfedge and check where is comes from (cut-mesh/source-mesh)
|
|
|
|
std::map<vd_t, std::vector<std::pair<ed_t, fd_t>>::const_iterator>::const_iterator find_iter = m0_cutpath_terminal_vertices.cend();
|
|
find_iter = m0_cutpath_terminal_vertices.find(first_edge_terminal_vertex);
|
|
|
|
MCUT_ASSERT(find_iter != m0_cutpath_terminal_vertices.cend());
|
|
|
|
// TODO: These variable names are outdated
|
|
const ed_t& first_edge_terminal_vertex_edge = find_iter->second->first;
|
|
const hd_t first_edge_terminal_vertex_edge_h0 = ps.halfedge(first_edge_terminal_vertex_edge, 0);
|
|
fd_t ps_face_of_first_edge_terminal_vertex_he = ps.face(first_edge_terminal_vertex_edge_h0);
|
|
if (ps_face_of_first_edge_terminal_vertex_he == hmesh_t::null_face()) {
|
|
hd_t first_edge_terminal_vertex_edge_h1 = ps.opposite(first_edge_terminal_vertex_edge_h0);
|
|
ps_face_of_first_edge_terminal_vertex_he = ps.face(first_edge_terminal_vertex_edge_h1);
|
|
}
|
|
|
|
MCUT_ASSERT(ps_face_of_first_edge_terminal_vertex_he != hmesh_t::null_face());
|
|
|
|
bool is_from_cut_mesh = ps_is_cutmesh_face(ps_face_of_first_edge_terminal_vertex_he, sm_face_count);
|
|
bool is_from_src_mesh = !is_from_cut_mesh;
|
|
|
|
const bool first_vtx_is_from_src_mesh = is_from_src_mesh;
|
|
/*
|
|
if (is_from_src_mesh) {
|
|
cutpath_is_hole = false;
|
|
}
|
|
else
|
|
{*/
|
|
// ... so the halfedge in the registry of "first_edge_terminal_vertex"
|
|
// belongs to the cut-mesh. Now let us repeat the same test but this
|
|
// time for the "last_edge_terminal_vertex"
|
|
|
|
const ed_t& last_edge = cutpath.back();
|
|
const vd_t last_edge_vertex0 = m0.vertex(last_edge, 0);
|
|
const vd_t last_edge_vertex1 = m0.vertex(last_edge, 1);
|
|
bool last_edge_vertex0_is_terminal = m0_cutpath_terminal_vertices.find(last_edge_vertex0) != m0_cutpath_terminal_vertices.cend();
|
|
|
|
// bool last_edge_is_terminal = last_edge_vertex0_is_terminal;
|
|
|
|
// if (last_edge_vertex0_is_terminal == false)
|
|
//{
|
|
// check if vertex1 is terminal
|
|
// bool last_edge_vertex1_is_terminal = m0_cutpath_terminal_vertices.find(last_edge_vertex1) != m0_cutpath_terminal_vertices.cend();
|
|
// last_edge_is_terminal = last_edge_vertex1_is_terminal;
|
|
//}
|
|
|
|
bool last_vtx_is_from_src_mesh = first_vtx_is_from_src_mesh; // ... we will use this to determine whether we have a severing cutpath or not (the current one)
|
|
|
|
// MCUT_ASSERT(last_edge_is_terminal); // i.e. we have a linear cut path
|
|
vd_t last_edge_terminal_vertex = hmesh_t::null_vertex();
|
|
|
|
if (last_edge == first_edge) // sequence has one edge
|
|
{
|
|
last_edge_terminal_vertex = (first_edge_terminal_vertex == last_edge_vertex0) ? last_edge_vertex1 : last_edge_vertex0;
|
|
} else {
|
|
last_edge_terminal_vertex = (last_edge_vertex0_is_terminal ? last_edge_vertex0 : last_edge_vertex1);
|
|
}
|
|
|
|
// get the halfedge and check where is comes from (cut-mesh/src-mesh)
|
|
|
|
// std::map<vd_t, std::map<vd_t, hd_t>::const_iterator>::const_iterator find_iter = m0_cutpath_terminal_vertices.cend();
|
|
find_iter = m0_cutpath_terminal_vertices.find(last_edge_terminal_vertex);
|
|
MCUT_ASSERT(find_iter != m0_cutpath_terminal_vertices.cend());
|
|
const ed_t& last_edge_terminal_vertex_e = find_iter->second->first;
|
|
const hd_t last_edge_terminal_vertex_e_h0 = ps.halfedge(last_edge_terminal_vertex_e, 0);
|
|
fd_t ps_face_of_last_edge_terminal_vertex_he = ps.face(last_edge_terminal_vertex_e_h0);
|
|
|
|
if (ps_face_of_last_edge_terminal_vertex_he == hmesh_t::null_face()) {
|
|
const hd_t last_edge_terminal_vertex_e_h1 = ps.opposite(last_edge_terminal_vertex_e_h0);
|
|
ps_face_of_last_edge_terminal_vertex_he = ps.face(last_edge_terminal_vertex_e_h1);
|
|
}
|
|
|
|
// must exist because "ivtx_ps_he" came from an intersecting face in the
|
|
// polygon soup
|
|
MCUT_ASSERT(ps_face_of_last_edge_terminal_vertex_he != hmesh_t::null_face());
|
|
|
|
is_from_cut_mesh = ps_is_cutmesh_face(ps_face_of_last_edge_terminal_vertex_he, sm_face_count);
|
|
is_from_src_mesh = !is_from_cut_mesh;
|
|
|
|
last_vtx_is_from_src_mesh = is_from_src_mesh;
|
|
|
|
// if (is_from_cut_mesh) {
|
|
// cutpath_is_hole = true;
|
|
// }
|
|
// }
|
|
cutpath_is_hole = (!last_vtx_is_from_src_mesh && !first_vtx_is_from_src_mesh);
|
|
cutpath_is_srcmesh_severing = (first_vtx_is_from_src_mesh && (last_vtx_is_from_src_mesh /* == first_vtx_is_from_src_mesh*/));
|
|
//}
|
|
}
|
|
}
|
|
|
|
m0_cutpath_terminal_vertices.clear(); // free
|
|
|
|
int num_explicit_linear_cutpaths = 0;
|
|
int num_explicit_circular_cutpaths = 0;
|
|
std::vector<int> explicit_cutpaths_making_holes;
|
|
std::vector<int> explicit_cutpaths_severing_srcmesh;
|
|
|
|
for (std::map<int, std::tuple<bool, bool, bool>>::const_iterator iter = m0_cutpath_sequence_to_properties.cbegin();
|
|
iter != m0_cutpath_sequence_to_properties.cend(); ++iter) {
|
|
const int& explicit_cutpath_index = iter->first;
|
|
const std::tuple<bool, bool, bool>& properties = iter->second;
|
|
|
|
const bool& is_linear = std::get<0>(properties);
|
|
const bool& is_hole = std::get<1>(properties);
|
|
const bool& is_srcmesh_severing = std::get<2>(properties);
|
|
|
|
if (is_linear) {
|
|
num_explicit_linear_cutpaths += 1;
|
|
} else {
|
|
num_explicit_circular_cutpaths += 1;
|
|
}
|
|
|
|
if (is_hole) {
|
|
explicit_cutpaths_making_holes.push_back(explicit_cutpath_index);
|
|
}
|
|
|
|
if (is_srcmesh_severing) {
|
|
explicit_cutpaths_severing_srcmesh.push_back(explicit_cutpath_index);
|
|
}
|
|
}
|
|
|
|
TIMESTACK_POP();
|
|
|
|
// NOTE: at this point we have all vertices, edges, and the lists of
|
|
// edge sequences identifying the cutpaths
|
|
// =====================================================================
|
|
|
|
#if 0
|
|
// Detect degeneracy (see note "limitations")
|
|
//--------------------------------------------
|
|
|
|
bool have_more_than_one_cutpath = (m0_cutpath_sequences.size() > 0);
|
|
if (have_more_than_one_cutpath)
|
|
{
|
|
bool atleast_one_cutpath_makes_a_hole = !explicit_cutpaths_making_holes.empty();
|
|
//bool atleast_one_explicit_cutpath_is_linear = num_explicit_linear_cutpaths;
|
|
if (atleast_one_cutpath_makes_a_hole)
|
|
{
|
|
// TODO
|
|
}
|
|
}
|
|
#endif
|
|
|
|
TIMESTACK_PUSH("Detect floating polygons");
|
|
|
|
// Detect floating polygons
|
|
// ::::::::::::::::::::::::
|
|
// NOTE: The following code is what we used to determine when to do polygon partitioning in the front end
|
|
|
|
// for each circular cut-path (i.e. those making a hole)
|
|
for (std::vector<int>::const_iterator it = explicit_cutpaths_making_holes.cbegin(); it != explicit_cutpaths_making_holes.cend(); ++it) {
|
|
const int cutpath_idx = *it;
|
|
MCUT_ASSERT(m0_cutpath_sequence_to_properties.find(cutpath_idx) != m0_cutpath_sequence_to_properties.cend());
|
|
const std::tuple<bool, bool, bool>& properties = SAFE_ACCESS(m0_cutpath_sequence_to_properties, cutpath_idx);
|
|
const bool& is_linear = std::get<0>(properties);
|
|
bool is_circular = !is_linear;
|
|
|
|
if (!is_circular) {
|
|
continue;
|
|
}
|
|
|
|
MCUT_ASSERT(cutpath_idx < (int)m0_cutpath_sequences.size());
|
|
const std::vector<ed_t>& cutpath_sequence = SAFE_ACCESS(m0_cutpath_sequences, cutpath_idx);
|
|
|
|
MCUT_ASSERT(cutpath_sequence.size() >= 3); // triangle
|
|
|
|
// :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
// Detect a floating polygon as "one which is not connectable to other
|
|
// [traced] polygons by an edge". In practice, these polygons are represented
|
|
// by circular cutpaths whose vertices are intersection points that have a
|
|
// tested/intersected face in their registry entry from the same input mesh
|
|
bool is_floating_polygon = true;
|
|
fd_t shared_registry_entry_intersected_face = hmesh_t::null_face();
|
|
vd_t vertex_prev = hmesh_t::null_vertex();
|
|
for (std::vector<ed_t>::const_iterator cp_edge_iter = cutpath_sequence.cbegin(); cp_edge_iter != cutpath_sequence.cend(); cp_edge_iter++) {
|
|
vd_t v = m0.vertex(*cp_edge_iter, 0);
|
|
if (vertex_prev != hmesh_t::null_vertex() && v == vertex_prev) {
|
|
v = m0.vertex(*cp_edge_iter, 1);
|
|
MCUT_ASSERT(v != vertex_prev);
|
|
}
|
|
|
|
MCUT_ASSERT(m0_is_intersection_point(v, ps_vtx_cnt));
|
|
|
|
// std::map<vd_t, std::pair<ed_t, fd_t>>::const_iterator v_to_intersection_registry_entry = m0_ivtx_to_intersection_registry_entry.find(v);
|
|
if (shared_registry_entry_intersected_face == hmesh_t::null_face()) {
|
|
shared_registry_entry_intersected_face = SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, (std::size_t)v - ps_vtx_cnt).second; // v_to_intersection_registry_entry->second.second; // set to initial value
|
|
} else {
|
|
// i.e. "is same face" which is being pierced by multiple edges [of the same input mesh]
|
|
is_floating_polygon = (shared_registry_entry_intersected_face == SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, (std::size_t)v - ps_vtx_cnt).second /*v_to_intersection_registry_entry->second.second*/);
|
|
}
|
|
|
|
if (!is_floating_polygon) {
|
|
break;
|
|
}
|
|
|
|
vertex_prev = v;
|
|
}
|
|
|
|
vertex_prev = hmesh_t::null_vertex();
|
|
if (is_floating_polygon) {
|
|
|
|
// bool ps_face_is_from_cutmesh = ps_is_cutmesh_face(shared_registry_entry_intersected_face, sm_face_count);
|
|
|
|
output.detected_floating_polygons[shared_registry_entry_intersected_face].emplace_back(floating_polygon_info_t());
|
|
floating_polygon_info_t& fpi = output.detected_floating_polygons[shared_registry_entry_intersected_face].back();
|
|
|
|
// fpi.origin_mesh = ps_face_is_from_cutmesh ? input.cut_mesh : input.src_mesh;
|
|
// const uint32_t cm_faces_start_offset = sm_face_count; // i.e. start offset in "ps"
|
|
// fpi.origin_face = (ps_face_is_from_cutmesh ? fd_t(shared_registry_entry_intersected_face - cm_faces_start_offset) : shared_registry_entry_intersected_face);
|
|
fpi.polygon_vertices.clear();
|
|
|
|
std::unordered_map<vd_t, std::vector<ed_t>> ivtx_to_cp_edges;
|
|
|
|
for (std::vector<ed_t>::const_iterator cp_edge_iter = cutpath_sequence.cbegin(); cp_edge_iter != cutpath_sequence.cend(); cp_edge_iter++) {
|
|
const vd_t v0 = m0.vertex(*cp_edge_iter, 0);
|
|
const vd_t v1 = m0.vertex(*cp_edge_iter, 1);
|
|
ivtx_to_cp_edges[v0].push_back(*cp_edge_iter);
|
|
ivtx_to_cp_edges[v1].push_back(*cp_edge_iter);
|
|
}
|
|
|
|
std::unordered_map<vd_t, std::vector<ed_t>>::const_iterator cur = ivtx_to_cp_edges.cend();
|
|
std::unordered_map<vd_t, std::vector<ed_t>>::const_iterator next = ivtx_to_cp_edges.cbegin();
|
|
|
|
ed_t prev_edge = hmesh_t::null_edge();
|
|
do {
|
|
cur = next;
|
|
next = ivtx_to_cp_edges.cend();
|
|
|
|
vd_t cur_vertex = cur->first;
|
|
|
|
fpi.polygon_vertices.emplace_back(m0.vertex(cur_vertex)); // save coords
|
|
|
|
if (prev_edge == hmesh_t::null_edge() || fpi.polygon_vertices.size() < cutpath_sequence.size()) {
|
|
const std::vector<ed_t>& evec = SAFE_ACCESS(ivtx_to_cp_edges, cur->first);
|
|
std::vector<ed_t>::const_iterator fiter = std::find_if(evec.cbegin(), evec.cend(), [&](const ed_t& e) { return e != prev_edge; });
|
|
|
|
ed_t edge = *fiter;
|
|
vd_t next_vertex = m0.vertex(edge, 0);
|
|
|
|
if (next_vertex == cur->first) {
|
|
next_vertex = m0.vertex(edge, 1);
|
|
}
|
|
|
|
prev_edge = edge;
|
|
|
|
next = ivtx_to_cp_edges.find(next_vertex);
|
|
|
|
MCUT_ASSERT(next != ivtx_to_cp_edges.cend()); // because we have a loop (floating polygon)
|
|
}
|
|
|
|
} while (next != ivtx_to_cp_edges.cend());
|
|
|
|
MCUT_ASSERT(ps_tested_face_to_plane_normal.find(shared_registry_entry_intersected_face) != ps_tested_face_to_plane_normal.end());
|
|
fpi.polygon_normal = SAFE_ACCESS(ps_tested_face_to_plane_normal, shared_registry_entry_intersected_face); // used for 2d project
|
|
|
|
MCUT_ASSERT(ps_tested_face_to_plane_normal_max_comp.find(shared_registry_entry_intersected_face) != ps_tested_face_to_plane_normal_max_comp.end());
|
|
fpi.polygon_normal_largest_component = SAFE_ACCESS(ps_tested_face_to_plane_normal_max_comp, shared_registry_entry_intersected_face);
|
|
}
|
|
}
|
|
|
|
if (!output.detected_floating_polygons.empty()) {
|
|
output.status = status_t::DETECTED_FLOATING_POLYGON;
|
|
return; // abort, so that the front-end can partition ps-faces containing floating polygon
|
|
}
|
|
|
|
m0_cutpath_sequence_to_properties.clear();
|
|
m0_ivtx_to_cutpath_edges.clear(); // free
|
|
|
|
// The following sections of code are about clipping intersecting polygons,
|
|
// and the information we need in order to do that.
|
|
// -----------------------------------------------------------------------
|
|
|
|
#if 0
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Gather/map intersection points on each intersecting faces
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
// TODO: build this data structure during polygon intersection tests!
|
|
std::map<
|
|
fd_t, // intersectiong face
|
|
std::vector<vd_t> // intersection point which involve the intersecting face
|
|
>
|
|
ps_iface_to_ivtx_list; // faces which intersect with another
|
|
|
|
for (std::map<vd_t, std::pair<ed_t, fd_t>>::const_iterator ireg_entry_iter = m0_ivtx_to_intersection_registry_entry.cbegin();
|
|
ireg_entry_iter != m0_ivtx_to_intersection_registry_entry.cend();
|
|
++ireg_entry_iter)
|
|
{ // for each intersection point ...
|
|
|
|
const vd_t &intersection_point = ireg_entry_iter->first;
|
|
const std::vector<fd_t> entry_faces = ps_get_ivtx_registry_entry_faces(ps, ireg_entry_iter->second); // faces in registry entry
|
|
|
|
// update face vertex-registry
|
|
|
|
for (std::vector<fd_t>::const_iterator entry_face_iter = entry_faces.cbegin();
|
|
entry_face_iter != entry_faces.cend();
|
|
++entry_face_iter)
|
|
{ // for each face in the intersection point's registry entry
|
|
|
|
if (is_virtual_face(*entry_face_iter))
|
|
{
|
|
continue; // virtual faces are simply placeholders - not useful
|
|
}
|
|
|
|
// get entry of current face
|
|
std::map<fd_t, std::vector<vd_t>>::iterator find_iter = ps_iface_to_ivtx_list.find(*entry_face_iter);
|
|
const bool face_vertex_registery_exists = find_iter != ps_iface_to_ivtx_list.cend();
|
|
|
|
if (face_vertex_registery_exists)
|
|
{ // do we have a map entry already...?
|
|
// get the intersection points incident to the face
|
|
std::vector<vd_t> &face_vertex_registry = find_iter->second;
|
|
// has the current intersection point been associated with the current intersecting face
|
|
const bool vertex_registered = std::find(face_vertex_registry.cbegin(), face_vertex_registry.cend(), intersection_point) != face_vertex_registry.cend();
|
|
|
|
if (!vertex_registered)
|
|
{
|
|
face_vertex_registry.push_back(intersection_point); // associate intersection point with intersecting face
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// add face-registry and register vertex
|
|
std::pair<std::map<fd_t, std::vector<vd_t>>::iterator, bool> pair = ps_iface_to_ivtx_list.insert(std::make_pair(*entry_face_iter, std::vector<vd_t>()));
|
|
MCUT_ASSERT(pair.second == true);
|
|
|
|
find_iter = pair.first;
|
|
std::vector<vd_t> &face_vertex_registry = find_iter->second;
|
|
face_vertex_registry.push_back(intersection_point); // associate intersection point with intersecting face
|
|
}
|
|
}
|
|
}
|
|
|
|
for (std::map<fd_t, std::vector<vd_t>>::const_iterator i = ps_iface_to_ivtx_list.cbegin(); i != ps_iface_to_ivtx_list.cend(); ++i)
|
|
{
|
|
|
|
|
|
|
|
// log
|
|
|
|
for (std::vector<vd_t>::const_iterator j = i->second.cbegin(); j != i->second.cend(); ++j)
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
#endif
|
|
|
|
TIMESTACK_POP();
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Create new edges partitioning the intersecting ps edges (2-part process)
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
TIMESTACK_PUSH("Create polygon-exterior edges (w/ > 3 vertices)");
|
|
|
|
// Part 1
|
|
//
|
|
// Here, we identify ps-edges with more than 3 coincident m0-vertices (ps-
|
|
// and intersection points)
|
|
//
|
|
// Task: 1) find ps-edges with more than 3 coincident m0-vertices, 2)
|
|
// sort these vertices along the ps-edge 3) connect sorted point by
|
|
// creating edges in "m0"
|
|
//
|
|
// Brief: ps-edges with more than 3 coincident vertices arise during a
|
|
// partial-cut (3d polyhedron) and/or concave cut-mesh-to-source-mesh
|
|
// face intersection.
|
|
// For every such edge, there will be 2 ps-vertices and the rest are
|
|
// intersection points. (Sorting requires numerical calculation).
|
|
//
|
|
// We also create a mapping between each polygon-boundary interior-edge
|
|
// vertex and its multiple copies which will be used for connected component
|
|
// separation and sealing (hole filling).
|
|
// NOTE: a polygon-boundary edge is one which is lies on the boundary of a
|
|
// ps-polygon. Conversely, an interior edge lies within the polygon (path
|
|
// along which polygon is clipped - the cut path).
|
|
|
|
// std::map<vd_t, std::vector<vd_t>> m0_to_m1_poly_ext_int_edge_vertex;
|
|
|
|
std::unordered_map<ed_t, std::vector<std::pair<vd_t, vec3>>> ps_edge_to_vertices; // stores ps-edges with more-than 3 coincident vertices
|
|
|
|
for (std::unordered_map<ed_t, std::vector<vd_t>>::const_iterator iter_ps_edge = ps_intersecting_edges.cbegin(); iter_ps_edge != ps_intersecting_edges.cend(); ++iter_ps_edge) {
|
|
|
|
// TODO: get_vertices_on_ps_edge() is not needed we can probably infer this information using previously/pre-computed std::maps
|
|
// vertices that lie on current ps edge
|
|
|
|
// get_vertices_on_ps_edge(iter_ps_edge, m0_ivtx_to_intersection_registry_entry, ps, m0_to_ps_vtx);
|
|
|
|
if (iter_ps_edge->second.size() > 1) { // intersection points on edge is more than 1 i.e. edge has more than three vertices
|
|
|
|
const vd_t ps_v0 = ps.vertex(iter_ps_edge->first, 0);
|
|
const vd_t ps_v1 = ps.vertex(iter_ps_edge->first, 1);
|
|
|
|
MCUT_ASSERT((int)(ps_v0) < (int)ps_to_m0_vtx.size() /*ps_to_m0_vtx.find(ps_v0) != ps_to_m0_vtx.cend())*/);
|
|
const vd_t m0_v0 = ps_to_m0_vtx[ps_v0];
|
|
MCUT_ASSERT((int)(ps_v1) < (int)ps_to_m0_vtx.size() /*ps_to_m0_vtx.find(ps_v1) != ps_to_m0_vtx.cend()*/);
|
|
const vd_t m0_v1 = ps_to_m0_vtx[ps_v1];
|
|
std::vector<vd_t> vertices_on_ps_edge = { m0_v0, m0_v1 };
|
|
|
|
// and rest of points (intersection points)
|
|
vertices_on_ps_edge.insert(vertices_on_ps_edge.end(), iter_ps_edge->second.cbegin(), iter_ps_edge->second.cend());
|
|
|
|
MCUT_ASSERT(ps_edge_to_vertices.find(iter_ps_edge->first) == ps_edge_to_vertices.end()); // edge cannot have been traversed before!
|
|
|
|
ps_edge_to_vertices.insert(std::make_pair(iter_ps_edge->first, std::vector<std::pair<vd_t, vec3>>()));
|
|
|
|
for (std::vector<vd_t>::const_iterator it = vertices_on_ps_edge.cbegin(); it != vertices_on_ps_edge.cend(); ++it) {
|
|
|
|
const vec3& vertex_coordinates = m0.vertex(*it); // get the coordinates (for sorting)
|
|
SAFE_ACCESS(ps_edge_to_vertices, iter_ps_edge->first).push_back(std::make_pair(*it, vertex_coordinates));
|
|
|
|
// if (m0_is_intersection_point(*it, ps_vtx_cnt)) { // is intersection point
|
|
// m0_to_m1_poly_ext_int_edge_vertex.insert(std::make_pair(*it, std::vector<vd_t>()));
|
|
// }
|
|
}
|
|
}
|
|
}
|
|
|
|
// In the next for-loop, we sort each list of vertices on each ps-edge
|
|
// which more than 3 coincident vertices
|
|
|
|
std::unordered_map<ed_t, std::vector<vd_t>> ps_edge_to_sorted_descriptors; // sorted vertex that lie on each edge with > 3 vertices
|
|
|
|
for (std::unordered_map<ed_t, std::vector<std::pair<vd_t, vec3>>>::iterator edge_vertices_iter = ps_edge_to_vertices.begin(); edge_vertices_iter != ps_edge_to_vertices.end(); ++edge_vertices_iter) {
|
|
std::vector<std::pair<vd_t, vec3>>& incident_vertices = edge_vertices_iter->second;
|
|
|
|
ps_edge_to_sorted_descriptors[edge_vertices_iter->first] = linear_projection_sort(incident_vertices);
|
|
|
|
#if 0
|
|
// since all points are on straight line, we sort them by x-coord and by y-coord if x-coord is the same for all vertices
|
|
std::sort(incident_vertices.begin(), incident_vertices.end(),
|
|
[&](const std::pair<vd_t, vec3>& a, const std::pair<vd_t, vec3>& b) {
|
|
return (a.second.x() < b.second.x());
|
|
});
|
|
|
|
const bool x_coordinate_is_same = have_same_coordinate(incident_vertices, 0);
|
|
|
|
if (x_coordinate_is_same) {
|
|
// ... then sort on y-coord
|
|
std::sort(incident_vertices.begin(), incident_vertices.end(),
|
|
[&](const std::pair<vd_t, vec3>& a, const std::pair<vd_t, vec3>& b) {
|
|
return (a.second.y() < b.second.y());
|
|
});
|
|
|
|
const bool y_coordinate_is_same = have_same_coordinate(incident_vertices, 1);
|
|
|
|
if (y_coordinate_is_same) {
|
|
// ... then sort on z-coord
|
|
std::sort(incident_vertices.begin(), incident_vertices.end(),
|
|
[&](const std::pair<vd_t, vec3>& a, const std::pair<vd_t, vec3>& b) {
|
|
return (a.second.z() < b.second.z());
|
|
});
|
|
}
|
|
}
|
|
|
|
#endif
|
|
}
|
|
|
|
//
|
|
// Now we, create edges between the sorted vertices that are coincident
|
|
// on the same ps-edge that has more-than 3 incident vertices.
|
|
//
|
|
// This step will create class-1 (o==>x), class-2 (o==>x),
|
|
// and class-3 (x==>x) which are the so called "polygon-boundary
|
|
// interior-iedges".
|
|
|
|
std::map<
|
|
ed_t, // polygon-soup edge
|
|
std::vector<ed_t> // list of m0-edges which lay on polygon-soup edge
|
|
>
|
|
ps_to_m0_edges;
|
|
|
|
// for each ps-edge with more than 3 coincindent vertices
|
|
for (std::unordered_map<ed_t, std::vector<vd_t>>::const_iterator ps_edge_coincident_vertices_iter = ps_edge_to_sorted_descriptors.begin();
|
|
ps_edge_coincident_vertices_iter != ps_edge_to_sorted_descriptors.end();
|
|
++ps_edge_coincident_vertices_iter) {
|
|
|
|
// get sorted list of vertices on edge
|
|
const std::vector<vd_t>& coincident_sorted_vertices = ps_edge_coincident_vertices_iter->second;
|
|
|
|
MCUT_ASSERT(coincident_sorted_vertices.size() > 3); // we are only dealing with ps-edges with more than 3 coicindent vertices
|
|
|
|
// first vertex must not be an intersection point, because all vertices lie on a
|
|
// ps-edge to be partitioned into new edges, thus the first vertex must not
|
|
// be an intersection point: [*]===========[*] --> [*]===*==*======[*]
|
|
MCUT_ASSERT(!m0_is_intersection_point(coincident_sorted_vertices.front(), ps_vtx_cnt));
|
|
MCUT_ASSERT(m0_is_intersection_point((*(coincident_sorted_vertices.cbegin() + 1)), ps_vtx_cnt));
|
|
|
|
MCUT_ASSERT(m0_is_intersection_point((*(coincident_sorted_vertices.cend() - 2)), ps_vtx_cnt));
|
|
MCUT_ASSERT(!m0_is_intersection_point(coincident_sorted_vertices.back(), ps_vtx_cnt)); // likewise, last vertex must not be an intersection point
|
|
|
|
// for each sorted vertex on ps-edge (starting from the second in the list)
|
|
for (std::vector<vd_t>::const_iterator iter = coincident_sorted_vertices.cbegin() + 1; iter != coincident_sorted_vertices.cend(); ++iter) {
|
|
|
|
const vd_t src_vertex = *(iter - 1);
|
|
const vd_t tgt_vertex = *(iter);
|
|
const hd_t h = m0.add_edge(src_vertex, tgt_vertex); // create edge!
|
|
|
|
MCUT_ASSERT(h != hmesh_t::null_halfedge());
|
|
|
|
const ed_t new_edge = m0.edge(h);
|
|
MCUT_ASSERT(new_edge != hmesh_t::null_edge());
|
|
|
|
// map original ps-edge to list of "child" edges which lie on it
|
|
ps_to_m0_edges[ps_edge_coincident_vertices_iter->first].push_back(new_edge);
|
|
|
|
// Here we save the "incoming" halfedge for each vertex of the created edge,
|
|
// if the vertex is an intersection point. An incoming halfedge is a one
|
|
// whose target is the vertex.
|
|
// We will using this information when splitting the source mesh along the
|
|
// cut path (when duplicating intersection points to create holes).
|
|
|
|
if ((iter - 1) == coincident_sorted_vertices.cbegin()) // first iteration
|
|
{
|
|
MCUT_ASSERT(m0.target(h) == tgt_vertex);
|
|
ivtx_to_incoming_hlist[tgt_vertex].push_back(h);
|
|
} else if ((std::size_t)std::distance(coincident_sorted_vertices.cbegin(), iter) == coincident_sorted_vertices.size() - 1) // last iterator
|
|
{
|
|
MCUT_ASSERT(m0.target(m0.opposite(h)) == src_vertex);
|
|
ivtx_to_incoming_hlist[src_vertex].push_back(m0.opposite(h));
|
|
} else {
|
|
MCUT_ASSERT(m0.target(h) == tgt_vertex);
|
|
ivtx_to_incoming_hlist[tgt_vertex].push_back(h);
|
|
|
|
MCUT_ASSERT(m0.target(m0.opposite(h)) == src_vertex);
|
|
ivtx_to_incoming_hlist[src_vertex].push_back(m0.opposite(h));
|
|
}
|
|
|
|
// Here, we also associate the new edge with an intersecting ps-face.
|
|
// Note: since the new edge here will lie on the face boundary, its associated intersecting ps-face(s) will
|
|
// be those which are incident to the parent ps-edge
|
|
const ed_t ps_edge = ps_edge_coincident_vertices_iter->first;
|
|
|
|
for (int i = 0; i < 2; ++i) { // for each halfedge of edge
|
|
const hd_t ps_edge_h = ps.halfedge(ps_edge, i);
|
|
|
|
if (ps_edge_h != hmesh_t::null_halfedge()) {
|
|
const fd_t f = ps.face(ps_edge_h);
|
|
if (f != hmesh_t::null_face()) // ps_edge could be on the border!
|
|
{
|
|
ps_iface_to_m0_edge_list[f].emplace_back(new_edge);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TIMESTACK_POP();
|
|
|
|
TIMESTACK_PUSH("Create polygon-exterior edges (2 or 3 vertices)");
|
|
|
|
// Part 2
|
|
//
|
|
// We will now create edges between vertices that lie on the same ps-edge
|
|
// which has 2 or 3 coincident vertices. Note that in the case of 2 coincident
|
|
// vertices, the created edge is the same as the original ps-edge.
|
|
//
|
|
// Brief: the order of m0-vertices along the ps-edge is deduced since the number
|
|
// of vertices is small enough (unlike Part 1).
|
|
// So we have two simple cases:
|
|
// a) ps-edge is coincident on two m0-vertices which are not intersection points
|
|
// b) ps-edge is coincident on three m0-vertices such that one is an intersection point
|
|
//
|
|
|
|
// a map between edge ids in "ps" and in "m0", which is the data structure we are progressively
|
|
// defining to hold data for the new mesh containing clipped polygons
|
|
std::unordered_map<ed_t, ed_t> ps_to_m0_non_intersecting_edge;
|
|
|
|
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
{
|
|
typedef edge_array_iterator_t InputStorageIteratorType;
|
|
typedef std::tuple<
|
|
std::unordered_map<ed_t, ed_t>, // ps_to_m0_non_intersecting_edge
|
|
std::unordered_map<fd_t, std::vector<ed_t>>, // ps_iface_to_m0_edge_list
|
|
std::unordered_map<vd_t, std::vector<hd_t>>, // ivtx_to_incoming_hlist
|
|
// locally computed edge
|
|
// local-edge-id to the m0-vertex-descriptors that are used to create edge
|
|
std::vector<std::pair<vd_t, vd_t>>>
|
|
OutputStorageTypesTuple;
|
|
|
|
auto fn_compute_polygon_boundary_edges = [&](
|
|
InputStorageIteratorType block_start_,
|
|
InputStorageIteratorType block_end_) -> OutputStorageTypesTuple {
|
|
OutputStorageTypesTuple local_output;
|
|
|
|
std::unordered_map<ed_t, ed_t>& ps_to_m0_non_intersecting_edge_LOCAL = std::get<0>(local_output);
|
|
std::unordered_map<fd_t, std::vector<ed_t>>& ps_iface_to_m0_edge_list_LOCAL = std::get<1>(local_output);
|
|
std::unordered_map<vd_t, std::vector<hd_t>>& ivtx_to_incoming_hlist_LOCAL = std::get<2>(local_output);
|
|
std::vector<std::pair<vd_t, vd_t>>& edges_LOCAL = std::get<3>(local_output);
|
|
|
|
const uint32_t rough_number_of_edges = (uint32_t)std::distance(block_start_, block_end_);
|
|
edges_LOCAL.reserve((uint32_t)(rough_number_of_edges * 1.2)); // most edges are original
|
|
|
|
for (edge_array_iterator_t iter_ps_edge = block_start_; iter_ps_edge != block_end_; ++iter_ps_edge) {
|
|
// std::cout << (uint32_t)(*iter_ps_edge) << std::endl;
|
|
if (ps_edge_to_vertices.find(*iter_ps_edge) != ps_edge_to_vertices.end()) {
|
|
continue; // the case of more than 3 vertices (handled above)
|
|
}
|
|
|
|
const ed_t ps_edge = *iter_ps_edge; // edge handle
|
|
const vd_t ps_v0 = ps.vertex(ps_edge, 0);
|
|
const vd_t ps_v1 = ps.vertex(ps_edge, 1);
|
|
|
|
MCUT_ASSERT((int)(ps_v0) < (int)ps_to_m0_vtx.size());
|
|
const vd_t m0_v0 = ps_to_m0_vtx[ps_v0];
|
|
MCUT_ASSERT((int)(ps_v1) < (int)ps_to_m0_vtx.size());
|
|
const vd_t m0_v1 = ps_to_m0_vtx[ps_v1];
|
|
|
|
std::vector<vd_t> vertices_on_ps_edge = { ps_v0, ps_v1 };
|
|
std::unordered_map<ed_t, std::vector<vd_t>>::const_iterator ps_intersecting_edges_iter = ps_intersecting_edges.find(ps_edge);
|
|
if (ps_intersecting_edges_iter != ps_intersecting_edges.cend()) {
|
|
vertices_on_ps_edge.insert(
|
|
vertices_on_ps_edge.end(),
|
|
ps_intersecting_edges_iter->second.cbegin(),
|
|
ps_intersecting_edges_iter->second.cend());
|
|
}
|
|
|
|
if (vertices_on_ps_edge.size() == 2) {
|
|
// const hd_t h = m0.add_edge(vertices_on_ps_edge.back(), vertices_on_ps_edge.front());
|
|
// MCUT_ASSERT(h != hmesh_t::null_halfedge());
|
|
edges_LOCAL.push_back(std::make_pair(vertices_on_ps_edge.back(), vertices_on_ps_edge.front()));
|
|
const ed_t edge = ed_t((ed_t::index_type)(edges_LOCAL.size() - 1));
|
|
const hd_t h(edge * 2);
|
|
|
|
ps_to_m0_non_intersecting_edge_LOCAL[ps_edge] = edge;
|
|
|
|
for (int i = 0; i < 2; ++i) {
|
|
// const hd_t ps_edge_h = ps.halfedge(ps_edge, i);
|
|
const hd_t ps_edge_h = ps.halfedge(ps_edge, i);
|
|
if (ps_edge_h != hmesh_t::null_halfedge()) {
|
|
const fd_t f = ps.face(ps_edge_h);
|
|
// NOTE: ps_iface_to_m0_edge_list already contains [all] intersecting ps faces,
|
|
// which where added when we compute edge between intersection points.
|
|
// We associate "f" with "edge" using the local variable "ps_iface_to_m0_edge_list_LOCAL"
|
|
// because "ps_iface_to_m0_edge_list" is shared by all threads. Thus, each thread computes
|
|
// its local output first, and then we'll merge it into "ps_iface_to_m0_edge_list" later
|
|
bool is_intersecting_ps_face = f != hmesh_t::null_face() && ps_iface_to_m0_edge_list.find(f) != ps_iface_to_m0_edge_list.cend();
|
|
if (is_intersecting_ps_face) {
|
|
ps_iface_to_m0_edge_list_LOCAL[f].emplace_back(edge);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
|
|
MCUT_ASSERT(vertices_on_ps_edge.size() == 3);
|
|
|
|
const vd_t first = vertices_on_ps_edge[0];
|
|
const vd_t second = vertices_on_ps_edge[1];
|
|
const vd_t third = vertices_on_ps_edge[2];
|
|
|
|
hd_t h0;
|
|
hd_t h1;
|
|
|
|
ed_t m0_h0_edge_local;
|
|
ed_t m0_h1_edge_local;
|
|
|
|
if (!m0_is_intersection_point(first, ps_vtx_cnt)) { // o-->...
|
|
if (m0_is_intersection_point(second, ps_vtx_cnt)) {
|
|
|
|
// h0 = m0.add_edge(first, second);
|
|
// MCUT_ASSERT(h0 != hmesh_t::null_halfedge());
|
|
edges_LOCAL.push_back(std::make_pair(first, second));
|
|
m0_h0_edge_local = ed_t((ed_t::index_type)(edges_LOCAL.size() - 1));
|
|
h0 = hd_t(m0_h0_edge_local * 2); // mimmick halfedge descriptor
|
|
|
|
// MCUT_ASSERT(m0.target(h0) == second);
|
|
ivtx_to_incoming_hlist_LOCAL[second].push_back(h0);
|
|
|
|
// h1 = m0.add_edge(second, third);
|
|
// MCUT_ASSERT(h1 != hmesh_t::null_halfedge());
|
|
|
|
edges_LOCAL.push_back(std::make_pair(second, third));
|
|
m0_h1_edge_local = ed_t((ed_t::index_type)(edges_LOCAL.size() - 1));
|
|
h1 = hd_t(m0_h1_edge_local * 2);
|
|
|
|
// MCUT_ASSERT(m0.target(m0.opposite(h1)) == second);
|
|
// ivtx_to_incoming_hlist_LOCAL[second].push_back(m0.opposite(h1));
|
|
hd_t h1_opp_local = hd_t((uint32_t)h1 + 1);
|
|
ivtx_to_incoming_hlist_LOCAL[second].push_back(h1_opp_local);
|
|
} else {
|
|
// h0 = m0.add_edge(first, third);
|
|
// MCUT_ASSERT(h0 != hmesh_t::null_halfedge());
|
|
edges_LOCAL.push_back(std::make_pair(first, third));
|
|
m0_h0_edge_local = ed_t((ed_t::index_type)(edges_LOCAL.size() - 1));
|
|
h0 = hd_t(m0_h0_edge_local * 2);
|
|
|
|
ivtx_to_incoming_hlist_LOCAL[third].push_back(h0);
|
|
|
|
// h1 = m0.add_edge(third, second);
|
|
// MCUT_ASSERT(h1 != hmesh_t::null_halfedge());
|
|
edges_LOCAL.push_back(std::make_pair(third, second));
|
|
m0_h1_edge_local = ed_t((ed_t::index_type)(edges_LOCAL.size() - 1));
|
|
h1 = hd_t(m0_h1_edge_local * 2);
|
|
|
|
// ivtx_to_incoming_hlist_LOCAL[third].push_back(m0.opposite(h1));
|
|
hd_t h1_opp_local = hd_t((uint32_t)h1 + 1);
|
|
ivtx_to_incoming_hlist_LOCAL[third].push_back(h1_opp_local);
|
|
}
|
|
} else {
|
|
// h0 = m0.add_edge(second, first); // o-->x
|
|
// MCUT_ASSERT(h0 != hmesh_t::null_halfedge());
|
|
edges_LOCAL.push_back(std::make_pair(second, first));
|
|
m0_h0_edge_local = ed_t((ed_t::index_type)(edges_LOCAL.size() - 1));
|
|
h0 = hd_t(m0_h0_edge_local * 2);
|
|
|
|
ivtx_to_incoming_hlist_LOCAL[first].push_back(h0);
|
|
|
|
// MCUT_ASSERT(m0.target(m0.opposite(h0)) == second);
|
|
// h1 = m0.add_edge(first, third);
|
|
// MCUT_ASSERT(h1 != hmesh_t::null_halfedge());
|
|
edges_LOCAL.push_back(std::make_pair(first, third));
|
|
m0_h1_edge_local = ed_t((ed_t::index_type)(edges_LOCAL.size() - 1));
|
|
h1 = hd_t(m0_h1_edge_local * 2);
|
|
|
|
// MCUT_ASSERT(m0.target(m0.opposite(h1)) == first);
|
|
// ivtx_to_incoming_hlist_LOCAL[first].push_back(m0.opposite(h1));
|
|
hd_t h1_opp_local = hd_t((uint32_t)h1 + 1);
|
|
ivtx_to_incoming_hlist_LOCAL[first].push_back(h1_opp_local);
|
|
}
|
|
|
|
for (int i = 0; i < 2; ++i) {
|
|
const hd_t ps_edge_h = ps.halfedge(ps_edge, i);
|
|
if (ps_edge_h != hmesh_t::null_halfedge()) {
|
|
const fd_t f = ps.face(ps_edge_h);
|
|
if (f != hmesh_t::null_face()) {
|
|
// ps_iface_to_m0_edge_list_LOCAL[f].emplace_back(m0.edge(h0));
|
|
ps_iface_to_m0_edge_list_LOCAL[f].emplace_back(m0_h0_edge_local);
|
|
// ps_iface_to_m0_edge_list_LOCAL[f].emplace_back(m0.edge(h1));
|
|
ps_iface_to_m0_edge_list_LOCAL[f].emplace_back(m0_h1_edge_local);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return local_output;
|
|
};
|
|
|
|
std::vector<std::future<OutputStorageTypesTuple>> futures;
|
|
OutputStorageTypesTuple partial_res;
|
|
|
|
parallel_for(
|
|
*input.scheduler,
|
|
ps.edges_begin(),
|
|
ps.edges_end(),
|
|
fn_compute_polygon_boundary_edges,
|
|
partial_res, // output computed by master thread
|
|
futures);
|
|
|
|
const std::unordered_map<ed_t, ed_t> ps_to_m0_non_intersecting_edge_MASTER_THREAD_LOCAL = std::get<0>(partial_res);
|
|
const std::unordered_map<fd_t, std::vector<ed_t>>& ps_iface_to_m0_edge_list_MASTER_THREAD_LOCAL = std::get<1>(partial_res);
|
|
const std::unordered_map<vd_t, std::vector<hd_t>>& ivtx_to_incoming_hlist_MASTER_THREAD_LOCAL = std::get<2>(partial_res);
|
|
// local-edge-id to the m0-vertex-descriptors that are used to create edge
|
|
// the sequence of elements signifies the order in which they were computed
|
|
const std::vector<std::pair<vd_t, vd_t>>& edge_create_info_MASTER_THREAD_LOCAL = std::get<3>(partial_res);
|
|
|
|
// NOTE: this lambda adds the edges stored in "edge_create_info_" into m0
|
|
// the other iterable parameters are simply updated (i.e. ed_t of "m0" variables are given their proper offset)
|
|
auto merge_local_m0_edges = [](
|
|
hmesh_t& m0_,
|
|
const std::unordered_map<ed_t, ed_t>& ps_to_m0_non_intersecting_edge_FUTURE,
|
|
const std::unordered_map<fd_t, std::vector<ed_t>>& ps_iface_to_m0_edge_list_FUTURE,
|
|
const std::unordered_map<vd_t, std::vector<hd_t>>& ivtx_to_incoming_hlist_FUTURE,
|
|
const std::vector<std::pair<vd_t, vd_t>>& edge_create_info_FUTURE,
|
|
std::unordered_map<ed_t, ed_t>& ps_to_m0_non_intersecting_edge,
|
|
std::unordered_map<fd_t, std::vector<ed_t>>& ps_iface_to_m0_edge_list,
|
|
std::unordered_map<vd_t, std::vector<hd_t>>& ivtx_to_incoming_hlist) {
|
|
std::vector<ed_t> emap(edge_create_info_FUTURE.size());
|
|
// add edges using edge_create_info
|
|
for (std::vector<std::pair<vd_t, vd_t>>::const_iterator it = edge_create_info_FUTURE.begin();
|
|
it != edge_create_info_FUTURE.end();
|
|
++it) {
|
|
hd_t h = m0_.add_edge(it->first, it->second);
|
|
MCUT_ASSERT(h % 2 == 0); // this is what allows indexing trick
|
|
// MCUT_ASSERT(ed_t((uint32_t)h/2) == ed_t(m0_edge_descr_baseoffset_ + it->first));
|
|
size_t local_edge_idx = std::distance(edge_create_info_FUTURE.cbegin(), it);
|
|
emap[local_edge_idx] = m0_.edge(h);
|
|
}
|
|
|
|
// merge ps_to_m0_non_intersecting_edge
|
|
|
|
for (std::unordered_map<ed_t, ed_t>::const_iterator it = ps_to_m0_non_intersecting_edge_FUTURE.cbegin();
|
|
it != ps_to_m0_non_intersecting_edge_FUTURE.cend();
|
|
++it) {
|
|
ps_to_m0_non_intersecting_edge[it->first] = SAFE_ACCESS(emap, it->second);
|
|
}
|
|
|
|
// merge ps_iface_to_m0_edge_list
|
|
for (std::unordered_map<fd_t, std::vector<ed_t>>::const_iterator it = ps_iface_to_m0_edge_list_FUTURE.cbegin();
|
|
it != ps_iface_to_m0_edge_list_FUTURE.cend();
|
|
++it) {
|
|
for (std::vector<ed_t>::const_iterator i = it->second.cbegin(); i != it->second.cend(); ++i) {
|
|
ps_iface_to_m0_edge_list[it->first].push_back(SAFE_ACCESS(emap, *i));
|
|
}
|
|
}
|
|
|
|
// merge ivtx_to_incoming_hlist
|
|
for (std::unordered_map<vd_t, std::vector<hd_t>>::const_iterator it = ivtx_to_incoming_hlist_FUTURE.cbegin();
|
|
it != ivtx_to_incoming_hlist_FUTURE.cend();
|
|
++it) {
|
|
for (std::vector<hd_t>::const_iterator i = it->second.cbegin(); i != it->second.cend(); ++i) {
|
|
hd_t he_local = *i;
|
|
ed_t he_edge_local = ed_t((uint32_t)he_local / 2);
|
|
ivtx_to_incoming_hlist[it->first].push_back(m0_.halfedge(SAFE_ACCESS(emap, he_edge_local), (uint32_t)(he_local % 2)));
|
|
}
|
|
}
|
|
};
|
|
|
|
// add edges computed by master thread and update local edge (and halfedge) descriptors
|
|
merge_local_m0_edges(
|
|
m0,
|
|
ps_to_m0_non_intersecting_edge_MASTER_THREAD_LOCAL,
|
|
ps_iface_to_m0_edge_list_MASTER_THREAD_LOCAL,
|
|
ivtx_to_incoming_hlist_MASTER_THREAD_LOCAL,
|
|
edge_create_info_MASTER_THREAD_LOCAL,
|
|
ps_to_m0_non_intersecting_edge,
|
|
ps_iface_to_m0_edge_list,
|
|
ivtx_to_incoming_hlist);
|
|
|
|
// merge thread-local output into global data structures
|
|
for (int i = 0; i < (int)futures.size(); ++i) {
|
|
std::future<OutputStorageTypesTuple>& f = futures[i];
|
|
MCUT_ASSERT(f.valid());
|
|
OutputStorageTypesTuple future_result = f.get(); // "get()" is a blocking function
|
|
|
|
const std::unordered_map<ed_t, ed_t>& ps_to_m0_non_intersecting_edge_FUTURE = std::get<0>(future_result);
|
|
const std::unordered_map<fd_t, std::vector<ed_t>>& ps_iface_to_m0_edge_list_FUTURE = std::get<1>(future_result);
|
|
const std::unordered_map<vd_t, std::vector<hd_t>>& ivtx_to_incoming_hlist_FUTURE = std::get<2>(future_result);
|
|
const std::vector<std::pair<vd_t, vd_t>>& edge_create_info_FUTURE = std::get<3>(future_result);
|
|
|
|
merge_local_m0_edges(
|
|
m0,
|
|
ps_to_m0_non_intersecting_edge_FUTURE,
|
|
ps_iface_to_m0_edge_list_FUTURE,
|
|
ivtx_to_incoming_hlist_FUTURE,
|
|
edge_create_info_FUTURE,
|
|
ps_to_m0_non_intersecting_edge,
|
|
ps_iface_to_m0_edge_list,
|
|
ivtx_to_incoming_hlist);
|
|
}
|
|
|
|
} // end of parallel scope
|
|
|
|
#else
|
|
|
|
// for each ps-edge
|
|
for (edge_array_iterator_t iter_ps_edge = ps.edges_begin(); iter_ps_edge != ps.edges_end(); ++iter_ps_edge) {
|
|
|
|
if (ps_edge_to_vertices.empty() == false && ps_edge_to_vertices.find(*iter_ps_edge) != ps_edge_to_vertices.end()) {
|
|
continue; // the case of more than 3 vertices (handled above)
|
|
}
|
|
|
|
const ed_t ps_edge = *iter_ps_edge; // edge handle
|
|
const vd_t ps_v0 = ps.vertex(ps_edge, 0);
|
|
const vd_t ps_v1 = ps.vertex(ps_edge, 1);
|
|
|
|
MCUT_ASSERT((int)(ps_v0) < (int)ps_to_m0_vtx.size() /*ps_to_m0_vtx.find(ps_v0) != ps_to_m0_vtx.cend())*/);
|
|
const vd_t m0_v0 = ps_to_m0_vtx[ps_v0];
|
|
MCUT_ASSERT((int)(ps_v1) < (int)ps_to_m0_vtx.size() /*ps_to_m0_vtx.find(ps_v1) != ps_to_m0_vtx.cend()*/);
|
|
const vd_t m0_v1 = ps_to_m0_vtx[ps_v1];
|
|
|
|
std::vector<vd_t> vertices_on_ps_edge = { ps_v0, ps_v1 }; // get_vertices_on_ps_edge(*iter_ps_edge, m0_ivtx_to_ps_edge, ps, m0_to_ps_vtx);
|
|
std::unordered_map<ed_t, std::vector<vd_t>>::const_iterator ps_intersecting_edges_iter = ps_intersecting_edges.find(ps_edge);
|
|
if (ps_intersecting_edges_iter != ps_intersecting_edges.cend()) {
|
|
vertices_on_ps_edge.insert(vertices_on_ps_edge.end(), ps_intersecting_edges_iter->second.cbegin(), ps_intersecting_edges_iter->second.cend());
|
|
}
|
|
|
|
const uint32_t num_vertices_on_ps_edge = vertices_on_ps_edge.size();
|
|
|
|
if (num_vertices_on_ps_edge == 2u) // simple case (edge did not intersect with any polygon)
|
|
{
|
|
|
|
const hd_t h = m0.add_edge(vertices_on_ps_edge[1], vertices_on_ps_edge[0]);
|
|
|
|
MCUT_ASSERT(h != hmesh_t::null_halfedge());
|
|
|
|
const ed_t edge = m0.edge(h);
|
|
ps_to_m0_non_intersecting_edge[ps_edge] = edge; // associate
|
|
|
|
// similar to Part 1, we also associate the new edge with an intersecting ps-face.
|
|
for (int i = 0; i < 2; ++i) {
|
|
const hd_t ps_edge_h = ps.halfedge(ps_edge, i);
|
|
if (ps_edge_h != hmesh_t::null_halfedge()) { // note: ps_edge could be on the border!
|
|
const fd_t f = ps.face(ps_edge_h);
|
|
bool is_intersecting_ps_face = f != hmesh_t::null_face() && ps_iface_to_m0_edge_list.find(f) != ps_iface_to_m0_edge_list.cend();
|
|
if (is_intersecting_ps_face) {
|
|
ps_iface_to_m0_edge_list[f].emplace_back(edge);
|
|
}
|
|
}
|
|
}
|
|
} else { // this is the more complex case where we add minimal set of non overlapping edges between 3 vertices
|
|
|
|
MCUT_ASSERT(num_vertices_on_ps_edge == 3u);
|
|
|
|
const vd_t first = vertices_on_ps_edge[0];
|
|
const vd_t second = vertices_on_ps_edge[1];
|
|
const vd_t third = vertices_on_ps_edge[2];
|
|
|
|
hd_t h0;
|
|
hd_t h1;
|
|
|
|
if (!m0_is_intersection_point(first, ps_vtx_cnt)) { // o-->...
|
|
if (m0_is_intersection_point(second, ps_vtx_cnt)) {
|
|
//
|
|
// o x o
|
|
//
|
|
|
|
h0 = m0.add_edge(first, second);
|
|
MCUT_ASSERT(h0 != hmesh_t::null_halfedge());
|
|
|
|
MCUT_ASSERT(m0.target(h0) == second);
|
|
ivtx_to_incoming_hlist[second].push_back(h0);
|
|
|
|
h1 = m0.add_edge(second, third);
|
|
MCUT_ASSERT(h1 != hmesh_t::null_halfedge());
|
|
|
|
MCUT_ASSERT(m0.target(m0.opposite(h1)) == second);
|
|
ivtx_to_incoming_hlist[second].push_back(m0.opposite(h1));
|
|
} else {
|
|
//
|
|
// o o x
|
|
//
|
|
|
|
h0 = m0.add_edge(first, third);
|
|
MCUT_ASSERT(h0 != hmesh_t::null_halfedge());
|
|
ivtx_to_incoming_hlist[third].push_back(h0);
|
|
|
|
h1 = m0.add_edge(third, second);
|
|
MCUT_ASSERT(h1 != hmesh_t::null_halfedge());
|
|
ivtx_to_incoming_hlist[third].push_back(m0.opposite(h1));
|
|
}
|
|
} else {
|
|
//
|
|
// x o o
|
|
//
|
|
|
|
h0 = m0.add_edge(second, first); // o-->x
|
|
MCUT_ASSERT(h0 != hmesh_t::null_halfedge());
|
|
ivtx_to_incoming_hlist[first].push_back(h0);
|
|
|
|
MCUT_ASSERT(m0.target(m0.opposite(h0)) == second);
|
|
|
|
h1 = m0.add_edge(first, third); // x-->o
|
|
MCUT_ASSERT(h1 != hmesh_t::null_halfedge());
|
|
|
|
MCUT_ASSERT(m0.target(m0.opposite(h1)) == first);
|
|
ivtx_to_incoming_hlist[first].push_back(m0.opposite(h1));
|
|
}
|
|
|
|
// // associate the new edge with an intersecting ps-face
|
|
for (int i = 0; i < 2; ++i) { // for each halfedge of edge
|
|
const hd_t ps_edge_h = ps.halfedge(ps_edge, i);
|
|
|
|
if (ps_edge_h != hmesh_t::null_halfedge()) {
|
|
const fd_t f = ps.face(ps_edge_h);
|
|
if (f != hmesh_t::null_face()) // ps_edge could be on the border!
|
|
{
|
|
ps_iface_to_m0_edge_list[f].emplace_back(m0.edge(h0));
|
|
ps_iface_to_m0_edge_list[f].emplace_back(m0.edge(h1));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
|
|
TIMESTACK_POP(); // &&&&&
|
|
|
|
ps_intersecting_edges.clear();
|
|
ps_edge_to_vertices.clear(); // free
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Polygon tracing (clipping of intersecting polygon-soup faces)
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
// Now we start to clip every intersecting face
|
|
// -----------------------------------------------
|
|
|
|
TIMESTACK_PUSH("Clip polygons"); // &&&&&
|
|
|
|
// Stores the all polygons, including new polygons that are produced after clipping
|
|
// and the faces that remained unchanged because they were not intersecting. Note
|
|
// that faces in the polygon soup that were found to be intersecting are replaced
|
|
// with "child" faces that result from their clipping.
|
|
//
|
|
// I use the word "polygon" here because they are not yet used to define a mesh -
|
|
// at which point they become faces!
|
|
std::vector<traced_polygon_t> m0_polygons;
|
|
|
|
// m0 polygons adjacent to cutpath from source-mesh faces
|
|
std::vector<int> m0_sm_cutpath_adjacent_polygons;
|
|
// m0 polygons adjacent to cutpath from cut-mesh faces
|
|
std::vector<int> m0_cm_cutpath_adjacent_polygons;
|
|
|
|
int traced_sm_polygon_count = 0;
|
|
|
|
std::unordered_map<int, fd_t> m0_to_ps_face; // (we'll later also include reversed polygon patches)
|
|
|
|
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
{
|
|
typedef face_array_iterator_t InputStorageIteratorType;
|
|
typedef std::tuple<
|
|
std::vector<traced_polygon_t>, // m0_polygons;
|
|
std::vector<int>, // m0_sm_cutpath_adjacent_polygons
|
|
std::vector<int>, // m0_cm_cutpath_adjacent_polygons;
|
|
int, // traced_sm_polygon_count
|
|
std::unordered_map<int, fd_t>> // m0_to_ps_face
|
|
OutputStorageTypesTuple;
|
|
|
|
auto fn_trace_polygons = [&](
|
|
InputStorageIteratorType block_start_,
|
|
InputStorageIteratorType block_end_) -> OutputStorageTypesTuple {
|
|
OutputStorageTypesTuple local_output;
|
|
const uint32_t num_elems = (uint32_t)std::distance(block_start_, block_end_);
|
|
std::vector<traced_polygon_t>& m0_polygons_LOCAL = std::get<0>(local_output);
|
|
m0_polygons_LOCAL.reserve(num_elems);
|
|
std::vector<int>& m0_sm_cutpath_adjacent_polygons_LOCAL = std::get<1>(local_output);
|
|
m0_sm_cutpath_adjacent_polygons_LOCAL.reserve(ps_iface_to_m0_edge_list.size());
|
|
std::vector<int>& m0_cm_cutpath_adjacent_polygons_LOCAL = std::get<2>(local_output);
|
|
m0_cm_cutpath_adjacent_polygons_LOCAL.reserve(ps_iface_to_m0_edge_list.size());
|
|
int& traced_sm_polygon_count_LOCAL = std::get<3>(local_output);
|
|
std::unordered_map<int, fd_t>& m0_to_ps_face_LOCAL = std::get<4>(local_output);
|
|
m0_to_ps_face_LOCAL.reserve(num_elems);
|
|
traced_sm_polygon_count_LOCAL = 0;
|
|
|
|
for (face_array_iterator_t ps_face_iter = block_start_; ps_face_iter != block_end_; ++ps_face_iter) {
|
|
const fd_t& ps_face = *ps_face_iter;
|
|
|
|
// get all the edges that lie on "ps_face", including the new one after partiting acording to intersection
|
|
// points, and the old one which did not intersect any face
|
|
std::unordered_map<fd_t, std::vector<ed_t>>::iterator ps_iface_to_m0_edge_list_fiter = ps_iface_to_m0_edge_list.find(ps_face);
|
|
|
|
bool is_intersecting_ps_face = ps_iface_to_m0_edge_list_fiter != ps_iface_to_m0_edge_list.end();
|
|
bool is_from_cut_mesh = ps_is_cutmesh_face(ps_face, sm_face_count);
|
|
|
|
std::vector<traced_polygon_t> child_polygons; // new polygons traced on current face
|
|
|
|
if (is_intersecting_ps_face == false) { // non-intersecting face
|
|
|
|
traced_polygon_t retraced_poly; // ordered sequence of halfedges defining the unchanged polygon
|
|
std::vector<hd_t> halfedges_around_face = ps.get_halfedges_around_face(ps_face);
|
|
retraced_poly.reserve(halfedges_around_face.size()); // minimum 3 (triangle)
|
|
|
|
// for each halfedge in the current polygon
|
|
for (std::vector<hd_t>::const_iterator hbegin = halfedges_around_face.cbegin(); hbegin != halfedges_around_face.cend(); ++hbegin) {
|
|
// get the source and target vertex descriptors in the polygon soup
|
|
const vd_t ps_h_src = ps.source(*hbegin);
|
|
const vd_t ps_h_tgt = ps.target(*hbegin);
|
|
|
|
MCUT_ASSERT((int)(ps_h_src) < (int)ps_to_m0_vtx.size());
|
|
const vd_t m0_h_src = SAFE_ACCESS(ps_to_m0_vtx, ps_h_src);
|
|
MCUT_ASSERT((int)(ps_h_tgt) < (int)ps_to_m0_vtx.size());
|
|
const vd_t m0_h_tgt = SAFE_ACCESS(ps_to_m0_vtx, ps_h_tgt);
|
|
|
|
const ed_t ps_edge = ps.edge(*hbegin);
|
|
const ed_t m0_edge = SAFE_ACCESS(ps_to_m0_non_intersecting_edge, ps_edge);
|
|
const hd_t m0_edge_h0 = m0.halfedge(m0_edge, 0);
|
|
const hd_t m0_edge_h1 = m0.halfedge(m0_edge, 1);
|
|
|
|
// resolve the correct halfedge by match the source and target vertex descriptors
|
|
if (m0.source(m0_edge_h0) == m0_h_src && m0.target(m0_edge_h0) == m0_h_tgt) {
|
|
retraced_poly.emplace_back(m0_edge_h0);
|
|
} else {
|
|
retraced_poly.emplace_back(m0_edge_h1);
|
|
}
|
|
}
|
|
|
|
MCUT_ASSERT(retraced_poly.size() == halfedges_around_face.size());
|
|
|
|
const int poly_idx = (int)(m0_polygons_LOCAL.size() + child_polygons.size());
|
|
m0_to_ps_face_LOCAL[poly_idx] = ps_face;
|
|
|
|
child_polygons.emplace_back(retraced_poly);
|
|
} else {
|
|
|
|
// Here we enter the complex case of having to actually clip the current face
|
|
// because it is intersecting
|
|
// --------------------------------------------------------------------------
|
|
|
|
const std::vector<ed_t>& ps_iface_m0_edge_list = ps_iface_to_m0_edge_list_fiter->second;
|
|
static thread_local std::vector<vd_t> ps_coincident_vertices_tmp;
|
|
|
|
ps.get_vertices_around_face(ps_coincident_vertices_tmp, ps_face);
|
|
const std::vector<vd_t>& ps_coincident_vertices = ps_coincident_vertices_tmp;
|
|
static thread_local std::vector<vd_t> coincident_vertices; // "m0" versions of those stored in "ps_coincident_vertices"
|
|
coincident_vertices.resize(ps_coincident_vertices.size());
|
|
for (int i = 0; i < (int)ps_coincident_vertices.size(); ++i) {
|
|
const vd_t ps_v = ps_coincident_vertices[i];
|
|
MCUT_ASSERT((int)(ps_v) < (int)ps_to_m0_vtx.size() /*ps_to_m0_vtx.find(ps_v) != ps_to_m0_vtx.cend()*/);
|
|
const vd_t m0_v = ps_to_m0_vtx[ps_v];
|
|
coincident_vertices[i] = m0_v;
|
|
}
|
|
|
|
MCUT_ASSERT(coincident_vertices.size() == ps_coincident_vertices.size());
|
|
|
|
const std::unordered_map<fd_t, std::vector<vd_t>>::const_iterator ireg_entry_iter = ps_iface_to_ivtx_list.find(ps_face);
|
|
|
|
MCUT_ASSERT(ireg_entry_iter != ps_iface_to_ivtx_list.cend());
|
|
|
|
const std::vector<vd_t>& intersection_points_on_face = ireg_entry_iter->second;
|
|
coincident_vertices.reserve(coincident_vertices.size() + intersection_points_on_face.size());
|
|
coincident_vertices.insert(coincident_vertices.end(), intersection_points_on_face.cbegin(), intersection_points_on_face.cend());
|
|
|
|
MCUT_ASSERT(intersection_points_on_face.size() >= 2); // minimum (two intersecting convex polygons)
|
|
|
|
std::vector<ed_t> incident_edges = ps_iface_m0_edge_list; // COPY because "incident_edges" will be modified later
|
|
int incident_boundary_edge_count = 0;
|
|
|
|
std::partition(incident_edges.begin(), incident_edges.end(),
|
|
[&](const ed_t& e) {
|
|
const vd_t v0 = m0.vertex(e, 0);
|
|
const vd_t v1 = m0.vertex(e, 1);
|
|
const bool v0_is_ivtx = m0_is_intersection_point(v0, ps_vtx_cnt);
|
|
const bool v1_is_ivtx = m0_is_intersection_point(v1, ps_vtx_cnt);
|
|
const bool is_ambiguious_boundary_edge_case = (v0_is_ivtx && v1_is_ivtx);
|
|
bool is_valid_ambiguious_boundary_edge = false;
|
|
|
|
if (is_ambiguious_boundary_edge_case) {
|
|
MCUT_ASSERT((size_t)v0 - ps_vtx_cnt < m0_ivtx_to_intersection_registry_entry.size() /* m0_ivtx_to_intersection_registry_entry.find(v0) != m0_ivtx_to_intersection_registry_entry.cend()*/);
|
|
const std::pair<ed_t, fd_t>& v0_ipair = SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, (std::size_t)v0 - ps_vtx_cnt);
|
|
const ed_t v0_ps_edge = v0_ipair.first; // SAFE_ACCESS(m0_ivtx_to_ps_edge, v0); // ps.edge(v0_coincident_ps_halfedge);
|
|
|
|
MCUT_ASSERT((size_t)v1 - ps_vtx_cnt < m0_ivtx_to_intersection_registry_entry.size() /*m0_ivtx_to_intersection_registry_entry.find(v1) != m0_ivtx_to_intersection_registry_entry.cend()*/);
|
|
const std::pair<ed_t, fd_t>& v1_ipair = SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, (std::size_t)v1 - ps_vtx_cnt);
|
|
const ed_t v1_ps_edge = v1_ipair.first; // SAFE_ACCESS(m0_ivtx_to_ps_edge, v1); // ps.edge(v1_coincident_ps_halfedge);
|
|
|
|
is_valid_ambiguious_boundary_edge = (v0_ps_edge == v1_ps_edge);
|
|
}
|
|
|
|
bool is_boundary_edge = (!is_ambiguious_boundary_edge_case || is_valid_ambiguious_boundary_edge);
|
|
if (is_boundary_edge) {
|
|
incident_boundary_edge_count++; // count
|
|
}
|
|
return is_boundary_edge;
|
|
});
|
|
|
|
MCUT_ASSERT(incident_boundary_edge_count >= 3); // minimum is 3 edge which is for a triangle
|
|
|
|
// const int interior_edges_on_face = (int)incident_edges.size() - incident_boundary_edge_count;
|
|
std::vector<hd_t> incident_halfedges;
|
|
hd_t first_boundary_halfedge = hmesh_t::null_halfedge();
|
|
|
|
for (std::vector<ed_t>::const_iterator incident_edge_iter = incident_edges.cbegin();
|
|
incident_edge_iter != incident_edges.cbegin() + incident_boundary_edge_count;
|
|
++incident_edge_iter) {
|
|
const ed_t& edge = (*incident_edge_iter);
|
|
|
|
for (int edge_he_iter = 0; edge_he_iter < 2; ++edge_he_iter) {
|
|
const hd_t m0_edge_he = m0.halfedge(edge, edge_he_iter);
|
|
const vd_t m0_edge_he_src = m0.source(m0_edge_he);
|
|
const vd_t m0_edge_he_tgt = m0.target(m0_edge_he);
|
|
const bool m0_edge_he_src_is_ivertex = m0_is_intersection_point(m0_edge_he_src, ps_vtx_cnt);
|
|
const bool m0_edge_he_tgt_is_ivertex = m0_is_intersection_point(m0_edge_he_tgt, ps_vtx_cnt);
|
|
|
|
if (!m0_edge_he_src_is_ivertex && !m0_edge_he_tgt_is_ivertex) { // o-->o (unmodified original edge)
|
|
|
|
const vd_t ps_he_src = SAFE_ACCESS(m0_to_ps_vtx, m0_edge_he_src);
|
|
const vd_t ps_he_tgt = SAFE_ACCESS(m0_to_ps_vtx, m0_edge_he_tgt);
|
|
const hd_t ps_he = ps.halfedge(ps_he_src, ps_he_tgt);
|
|
|
|
if (ps_he == hmesh_t::null_halfedge()) {
|
|
continue;
|
|
}
|
|
|
|
if (ps.face(ps_he) == ps_face) {
|
|
first_boundary_halfedge = m0_edge_he;
|
|
break;
|
|
}
|
|
} else { // x-->x OR o-->x OR x-->o
|
|
const bool is_ox = (!m0_edge_he_src_is_ivertex && m0_edge_he_tgt_is_ivertex);
|
|
|
|
if (is_ox) {
|
|
MCUT_ASSERT((size_t)m0_edge_he_tgt - ps_vtx_cnt < m0_ivtx_to_intersection_registry_entry.size() /*m0_ivtx_to_intersection_registry_entry.find(m0_edge_he_tgt) != m0_ivtx_to_intersection_registry_entry.cend()*/);
|
|
const std::pair<ed_t, fd_t>& m0_edge_he_tgt_ipair = SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, (std::size_t)m0_edge_he_tgt - ps_vtx_cnt);
|
|
|
|
// get the incident ps-halfedge of tgt
|
|
ed_t tgt_ps_e = m0_edge_he_tgt_ipair.first;
|
|
hd_t tgt_ps_h = ps.halfedge(tgt_ps_e, 0);
|
|
|
|
if (ps.face(tgt_ps_h) != ps_face) {
|
|
tgt_ps_h = ps.opposite(tgt_ps_h);
|
|
MCUT_ASSERT(tgt_ps_h != hmesh_t::null_halfedge());
|
|
}
|
|
|
|
const vd_t& m0_edge_he_src_as_ps_vertex = SAFE_ACCESS(m0_to_ps_vtx, m0_edge_he_src);
|
|
|
|
if (m0_edge_he_src_as_ps_vertex == ps.source(tgt_ps_h)) { // is counter clock-wise halfedge
|
|
first_boundary_halfedge = m0_edge_he;
|
|
break;
|
|
}
|
|
} else { // x-->x OR x-->o
|
|
|
|
const bool is_xx = m0_edge_he_src_is_ivertex && m0_edge_he_tgt_is_ivertex;
|
|
|
|
if (is_xx) { // exterior interior-iedge
|
|
MCUT_ASSERT((size_t)m0_edge_he_src - ps_vtx_cnt < m0_ivtx_to_intersection_registry_entry.size() /*m0_ivtx_to_intersection_registry_entry.find(m0_edge_he_src) != m0_ivtx_to_intersection_registry_entry.cend()*/);
|
|
const std::pair<ed_t, fd_t>& m0_edge_he_src_ipair = SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, (std::size_t)m0_edge_he_src - ps_vtx_cnt);
|
|
MCUT_ASSERT((size_t)m0_edge_he_tgt - ps_vtx_cnt < m0_ivtx_to_intersection_registry_entry.size() /*m0_ivtx_to_intersection_registry_entry.find(m0_edge_he_tgt) != m0_ivtx_to_intersection_registry_entry.cend()*/);
|
|
const std::pair<ed_t, fd_t>& m0_edge_he_tgt_ipair = SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, (std::size_t)m0_edge_he_tgt - ps_vtx_cnt);
|
|
|
|
const ed_t src_ps_edge = m0_edge_he_src_ipair.first; // ps.edge(src_coincident_ps_halfedge)
|
|
const ed_t tgt_ps_edge = m0_edge_he_tgt_ipair.first; // ps.edge(tgt_ps_h);
|
|
const bool is_exterior_ih = (src_ps_edge == tgt_ps_edge);
|
|
|
|
if (!is_exterior_ih) {
|
|
continue; // interior ihalfedges cannot be used as the first exterior ihalfedge
|
|
}
|
|
|
|
hd_t ps_halfedge_of_face = ps.halfedge(tgt_ps_edge, 0); // tgt_ps_h;
|
|
|
|
if (ps.face(ps_halfedge_of_face) != ps_face) {
|
|
ps_halfedge_of_face = ps.opposite(ps_halfedge_of_face);
|
|
MCUT_ASSERT(ps_halfedge_of_face != hmesh_t::null_halfedge()); // guarranteed to exist since we have a poly-boundary interior ihalfedge
|
|
}
|
|
|
|
const ed_t& incident_ps_edge = ps.edge(ps_halfedge_of_face);
|
|
std::map<ed_t, std::vector<ed_t>>::const_iterator ps_to_m0_edges_find_iter = ps_to_m0_edges.find(incident_ps_edge);
|
|
MCUT_ASSERT(ps_to_m0_edges_find_iter != ps_to_m0_edges.cend()); // because incident_ps_edge contains a polygon exterior interior ihalfedge
|
|
|
|
const std::vector<ed_t>& sorted_m0_edges = ps_to_m0_edges_find_iter->second;
|
|
std::vector<hd_t> halfedge_sequence;
|
|
const ed_t& first_e = sorted_m0_edges.front();
|
|
hd_t first_he = m0.halfedge(first_e, 0);
|
|
vd_t first_he_src = m0.source(first_he);
|
|
|
|
if (m0_is_intersection_point(first_he_src, ps_vtx_cnt)) {
|
|
// TODO: I think this scope is never entered based on how we created "sorted_m0_edges".
|
|
// Thus, the source vertex of the first halfedge in the sequence cannot be an intersection point
|
|
first_he = m0.halfedge(first_e, 1);
|
|
first_he_src = m0.source(first_he);
|
|
MCUT_ASSERT(!m0_is_intersection_point(first_he_src, ps_vtx_cnt)); // expect original vertex since halfedge edge is the first in sequence
|
|
}
|
|
|
|
halfedge_sequence.push_back(first_he);
|
|
|
|
for (std::vector<ed_t>::const_iterator seq_edge_iter = sorted_m0_edges.cbegin() + 1; // we have already added the first halfedge
|
|
seq_edge_iter != sorted_m0_edges.cend(); ++seq_edge_iter) {
|
|
const ed_t& e = *seq_edge_iter;
|
|
const hd_t h0 = m0.halfedge(e, 0);
|
|
|
|
if (m0.source(h0) == m0.target(halfedge_sequence.back())) {
|
|
halfedge_sequence.push_back(h0);
|
|
} else {
|
|
const hd_t h1 = m0.halfedge(e, 1);
|
|
halfedge_sequence.push_back(h1);
|
|
}
|
|
}
|
|
|
|
MCUT_ASSERT(halfedge_sequence.size() == sorted_m0_edges.size());
|
|
|
|
const vd_t& first_he_src_as_ps_vertex = SAFE_ACCESS(m0_to_ps_vtx, first_he_src); // first he of sequence
|
|
|
|
if (first_he_src_as_ps_vertex != ps.source(ps_halfedge_of_face)) {
|
|
std::for_each(
|
|
halfedge_sequence.begin(),
|
|
halfedge_sequence.end(),
|
|
[&](hd_t& he) {
|
|
he = m0.opposite(he);
|
|
}); // flip seq
|
|
}
|
|
|
|
std::vector<hd_t>::const_iterator matching_he_find_iter = std::find_if(
|
|
halfedge_sequence.cbegin(),
|
|
halfedge_sequence.cend(),
|
|
[&](const hd_t& he) {
|
|
const vd_t& he_src = m0.source(he);
|
|
const vd_t& he_tgt = m0.target(he);
|
|
|
|
return (he_src == m0_edge_he_src && he_tgt == m0_edge_he_tgt /*|| (he_src == m0_edge_he_tgt && he_tgt == m0_edge_he_src*/);
|
|
});
|
|
|
|
MCUT_ASSERT(matching_he_find_iter != halfedge_sequence.cend()); // does the potential halfedge actually point in the correct direction or not
|
|
{
|
|
first_boundary_halfedge = *matching_he_find_iter;
|
|
break;
|
|
}
|
|
} // if (is_xx) {
|
|
else {
|
|
}
|
|
} // } else { // x-->x OR x-->o
|
|
} // } else { // x-->x OR o-->x OR x-->o
|
|
} // for (int edge_he_iter = 0; edge_he_iter < 2; ++edge_he_iter) {
|
|
|
|
if (first_boundary_halfedge != hmesh_t::null_halfedge()) {
|
|
break; // done
|
|
}
|
|
}
|
|
|
|
MCUT_ASSERT(first_boundary_halfedge != hmesh_t::null_halfedge());
|
|
|
|
hd_t current_exterior_halfedge = hmesh_t::null_halfedge();
|
|
hd_t next_exterior_halfedge = first_boundary_halfedge;
|
|
std::unordered_map<ed_t, bool> walked_edges;
|
|
|
|
do {
|
|
|
|
current_exterior_halfedge = next_exterior_halfedge;
|
|
incident_halfedges.push_back(current_exterior_halfedge);
|
|
walked_edges[m0.edge(current_exterior_halfedge)] = true;
|
|
|
|
const vd_t current_tgt = m0.target(current_exterior_halfedge);
|
|
next_exterior_halfedge = hmesh_t::null_halfedge(); // reset
|
|
|
|
// find next boundary halfedge from incident edges
|
|
for (std::vector<ed_t>::const_iterator incident_edge_iter = incident_edges.cbegin();
|
|
incident_edge_iter != incident_edges.cbegin() + incident_boundary_edge_count; // we only want exterior edges;
|
|
++incident_edge_iter) {
|
|
const ed_t& edge = *incident_edge_iter;
|
|
bool edge_walked = walked_edges.find(edge) != walked_edges.cend(); // std::find(walked_edges.cbegin(), walked_edges.cend(), edge) != walked_edges.cend();
|
|
|
|
if (edge_walked) {
|
|
continue; // skip edge is walked already
|
|
}
|
|
|
|
const vd_t v0 = m0.vertex(edge, 0);
|
|
const vd_t v1 = m0.vertex(edge, 1);
|
|
|
|
if (v0 == current_tgt || v1 == current_tgt) // check if connected to current (i.e. they share one vertex)
|
|
{
|
|
const bool v0_is_ivtx = m0_is_intersection_point(v0, ps_vtx_cnt);
|
|
const bool v1_is_ivtx = m0_is_intersection_point(v1, ps_vtx_cnt);
|
|
bool is_ambiguious_boundary_edge_case = v0_is_ivtx && v1_is_ivtx;
|
|
bool is_valid_ambiguious_boundary_edge = false;
|
|
|
|
if (is_ambiguious_boundary_edge_case) { // exterior edge with two intersection vertices (ambigious case arising from concave polyhedron cut)
|
|
|
|
MCUT_ASSERT((size_t)v0 - ps_vtx_cnt < m0_ivtx_to_intersection_registry_entry.size() /* m0_ivtx_to_intersection_registry_entry.find(v0) != m0_ivtx_to_intersection_registry_entry.cend()*/);
|
|
const std::pair<ed_t, fd_t>& v0_ipair = SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, (std::size_t)v0 - ps_vtx_cnt);
|
|
const ed_t v0_ps_edge = v0_ipair.first; // SAFE_ACCESS(m0_ivtx_to_ps_edge, v0); //ps.edge(v0_coincident_ps_halfedge);
|
|
|
|
MCUT_ASSERT((size_t)v1 - ps_vtx_cnt < m0_ivtx_to_intersection_registry_entry.size() /* m0_ivtx_to_intersection_registry_entry.find(v1) != m0_ivtx_to_intersection_registry_entry.cend()*/);
|
|
const std::pair<ed_t, fd_t>& v1_ipair = SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, (std::size_t)v1 - ps_vtx_cnt);
|
|
const ed_t v1_ps_edge = v1_ipair.first; // SAFE_ACCESS(m0_ivtx_to_ps_edge, v1); // ps.edge(v1_coincident_ps_halfedge);
|
|
|
|
is_valid_ambiguious_boundary_edge = (v0_ps_edge == v1_ps_edge); // see also above when gathering exterior incident edges
|
|
}
|
|
|
|
if (!is_ambiguious_boundary_edge_case || is_valid_ambiguious_boundary_edge) {
|
|
const hd_t h0 = m0.halfedge(edge, 0);
|
|
const hd_t h1 = m0.halfedge(edge, 1);
|
|
|
|
next_exterior_halfedge = h0;
|
|
|
|
if (m0.source(h0) != current_tgt) // h0 is facing the opposite dir
|
|
{
|
|
next_exterior_halfedge = h1;
|
|
}
|
|
|
|
break; // found
|
|
}
|
|
}
|
|
}
|
|
} while (next_exterior_halfedge != hmesh_t::null_halfedge() /*first_boundary_halfedge*/);
|
|
|
|
MCUT_ASSERT(incident_halfedges.size() >= 3); // minimum i.e. for a triangles!
|
|
|
|
const int exterior_halfedge_count = (int)incident_halfedges.size();
|
|
|
|
MCUT_ASSERT(exterior_halfedge_count == incident_boundary_edge_count);
|
|
|
|
for (std::vector<ed_t>::const_iterator incident_edge_iter = incident_edges.cbegin() + incident_boundary_edge_count; // start from polygon interior edges offset
|
|
incident_edge_iter != incident_edges.cend();
|
|
++incident_edge_iter) {
|
|
const ed_t& edge = (*incident_edge_iter);
|
|
const hd_t h0 = m0.halfedge(edge, 0);
|
|
const hd_t h1 = m0.halfedge(edge, 1);
|
|
incident_halfedges.push_back(h0);
|
|
incident_halfedges.push_back(h1);
|
|
}
|
|
|
|
std::vector<hd_t> incident_halfedges_to_be_walked(incident_halfedges.cbegin(), incident_halfedges.cend()); // copy
|
|
|
|
do { // each iteration traces a child polygon
|
|
|
|
traced_polygon_t child_polygon;
|
|
hd_t current_halfedge = hmesh_t::null_halfedge();
|
|
hd_t next_halfedge = incident_halfedges_to_be_walked.front();
|
|
|
|
MCUT_ASSERT(incident_halfedges_to_be_walked.size() >= 2);
|
|
|
|
bool is_valid_polygon = false;
|
|
do { // each iteration walks a halfedge to incremetally trace a child polygon
|
|
|
|
current_halfedge = next_halfedge;
|
|
child_polygon.push_back(current_halfedge);
|
|
const vd_t current_halfedge_target = m0.target(current_halfedge);
|
|
next_halfedge = hmesh_t::null_halfedge(); // reset
|
|
|
|
{
|
|
std::vector<hd_t>::iterator find_iter = std::find(incident_halfedges_to_be_walked.begin(), incident_halfedges_to_be_walked.end(), current_halfedge);
|
|
MCUT_ASSERT(find_iter != incident_halfedges_to_be_walked.cend());
|
|
incident_halfedges_to_be_walked.erase(find_iter); // remove
|
|
}
|
|
|
|
if (child_polygon.size() >= 3) { // minimum halfedge count to constitute a valid polygon (triangle)
|
|
|
|
// source of first halfedge is target of last
|
|
if (m0.source(child_polygon.front()) == m0.target(child_polygon.back())) {
|
|
if (current_halfedge != m0.opposite(child_polygon.front())) {
|
|
is_valid_polygon = true;
|
|
break;
|
|
} else { // ... if the current halfedge is the opposite of the first halfedge in list
|
|
std::vector<hd_t> premptive_candidate_halfedges;
|
|
for (std::vector<hd_t>::const_iterator incident_halfedges_to_be_walked_iter = incident_halfedges_to_be_walked.cbegin(); incident_halfedges_to_be_walked_iter != incident_halfedges_to_be_walked.cend(); ++incident_halfedges_to_be_walked_iter) {
|
|
const hd_t& potential_candidate = *incident_halfedges_to_be_walked_iter;
|
|
if (m0.source(potential_candidate) == current_halfedge_target) {
|
|
premptive_candidate_halfedges.push_back(potential_candidate);
|
|
}
|
|
}
|
|
|
|
hd_t prime_candidate = hmesh_t::null_halfedge();
|
|
if (premptive_candidate_halfedges.size() == 2) {
|
|
|
|
const std::vector<hd_t>::const_iterator current_halfedge_find_iter = std::find(incident_halfedges.cbegin(), incident_halfedges.cend(), current_halfedge);
|
|
MCUT_ASSERT(current_halfedge_find_iter != incident_halfedges.cend());
|
|
const bool current_halfedge_is_exterior = std::distance(incident_halfedges.cbegin(), current_halfedge_find_iter) < exterior_halfedge_count;
|
|
|
|
if (current_halfedge_is_exterior) {
|
|
|
|
// pick interior candidate
|
|
const std::vector<hd_t>::const_iterator prime_candidate_find_iter = std::find(incident_halfedges.cbegin(), incident_halfedges.cend(), current_halfedge);
|
|
MCUT_ASSERT(prime_candidate_find_iter != incident_halfedges.cend());
|
|
const bool prime_candidate_is_exterior = std::distance(incident_halfedges.cbegin(), prime_candidate_find_iter) < exterior_halfedge_count;
|
|
|
|
if (prime_candidate_is_exterior) {
|
|
prime_candidate = premptive_candidate_halfedges.back(); // select correct interior halfedge
|
|
}
|
|
} else { // interior
|
|
|
|
// pick non-opposite
|
|
const bool prime_candidate_is_opposite = m0.opposite(current_halfedge) == prime_candidate;
|
|
|
|
if (prime_candidate_is_opposite) {
|
|
prime_candidate = premptive_candidate_halfedges.back(); // select correct non-opposite halfedge
|
|
}
|
|
}
|
|
}
|
|
|
|
const hd_t premptive_next = prime_candidate;
|
|
|
|
MCUT_ASSERT(premptive_candidate_halfedges.size() <= 2);
|
|
|
|
if (std::find(child_polygon.cbegin(), child_polygon.cend(), premptive_next) != child_polygon.cend()) {
|
|
is_valid_polygon = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<hd_t> candidate_halfedges;
|
|
candidate_halfedges.reserve(2); // two candidates at most because we filtered out exterior interior-halfedges as well as clockwise (cw) halfedge
|
|
|
|
for (std::vector<hd_t>::const_iterator incident_halfedges_to_be_walked_iter = incident_halfedges_to_be_walked.cbegin(); incident_halfedges_to_be_walked_iter != incident_halfedges_to_be_walked.cend(); ++incident_halfedges_to_be_walked_iter) {
|
|
const hd_t& potential_candidate = *incident_halfedges_to_be_walked_iter;
|
|
if (m0.source(potential_candidate) == current_halfedge_target) {
|
|
candidate_halfedges.push_back(potential_candidate);
|
|
}
|
|
}
|
|
|
|
MCUT_ASSERT(candidate_halfedges.size() <= 2);
|
|
|
|
// 2.2. select prime candidate
|
|
hd_t prime_candidate = hmesh_t::null_halfedge();
|
|
|
|
if (!candidate_halfedges.empty()) {
|
|
prime_candidate = candidate_halfedges.front(); // assuming: candidate_halfedges.size() == 1
|
|
}
|
|
|
|
if (candidate_halfedges.size() == 2) {
|
|
|
|
const std::vector<hd_t>::const_iterator current_halfedge_find_iter = std::find(incident_halfedges.cbegin(), incident_halfedges.cend(), current_halfedge);
|
|
MCUT_ASSERT(current_halfedge_find_iter != incident_halfedges.cend());
|
|
const bool current_halfedge_is_exterior = std::distance(incident_halfedges.cbegin(), current_halfedge_find_iter) < exterior_halfedge_count;
|
|
|
|
if (current_halfedge_is_exterior) {
|
|
// pick interior candidate
|
|
const std::vector<hd_t>::const_iterator prime_candidate_find_iter = std::find(incident_halfedges.cbegin(), incident_halfedges.cend(), current_halfedge);
|
|
MCUT_ASSERT(prime_candidate_find_iter != incident_halfedges.cend());
|
|
const bool prime_candidate_is_exterior = std::distance(incident_halfedges.cbegin(), prime_candidate_find_iter) < exterior_halfedge_count;
|
|
|
|
if (prime_candidate_is_exterior) {
|
|
prime_candidate = candidate_halfedges.back(); // select correct interior halfedge
|
|
}
|
|
} else { // interior
|
|
const bool prime_candidate_is_opposite = m0.opposite(current_halfedge) == prime_candidate;
|
|
|
|
if (prime_candidate_is_opposite) {
|
|
prime_candidate = candidate_halfedges.back(); // select correct non-opposite halfedge
|
|
}
|
|
}
|
|
}
|
|
|
|
next_halfedge = prime_candidate;
|
|
} while (next_halfedge != hmesh_t::null_halfedge());
|
|
|
|
if (is_valid_polygon) {
|
|
|
|
const int poly_idx = (int)(m0_polygons_LOCAL.size() + child_polygons.size());
|
|
if (ps_is_cutmesh_face(ps_face, sm_face_count)) {
|
|
m0_cm_cutpath_adjacent_polygons_LOCAL.push_back(poly_idx);
|
|
} else {
|
|
m0_sm_cutpath_adjacent_polygons_LOCAL.push_back(poly_idx);
|
|
}
|
|
|
|
m0_to_ps_face_LOCAL[poly_idx] = ps_face;
|
|
child_polygons.emplace_back(child_polygon);
|
|
}
|
|
|
|
} while (!incident_halfedges_to_be_walked.empty());
|
|
} // if (!is_intersecting_ps_face) {
|
|
|
|
m0_polygons_LOCAL.insert(m0_polygons_LOCAL.end(), child_polygons.cbegin(), child_polygons.cend());
|
|
|
|
if (!is_from_cut_mesh) {
|
|
traced_sm_polygon_count_LOCAL += (int)child_polygons.size();
|
|
}
|
|
}
|
|
|
|
return local_output;
|
|
};
|
|
|
|
std::vector<std::future<OutputStorageTypesTuple>> futures;
|
|
OutputStorageTypesTuple partial_res;
|
|
|
|
parallel_for(
|
|
*input.scheduler,
|
|
ps.faces_begin(),
|
|
ps.faces_end(),
|
|
fn_trace_polygons,
|
|
partial_res, // output computed by master thread
|
|
futures);
|
|
|
|
// This lambda merges the local traced face data structures computed by each
|
|
// thread into their corresponding global data structure.
|
|
auto merge_local_traced_faces = [](
|
|
const std::vector<traced_polygon_t>& m0_polygons_FUTURE,
|
|
const std::vector<int>& m0_sm_cutpath_adjacent_polygons_FUTURE,
|
|
const std::vector<int>& m0_cm_cutpath_adjacent_polygons_FUTURE,
|
|
const int& traced_sm_polygon_count_FUTURE,
|
|
const std::unordered_map<int, fd_t>& m0_to_ps_face_FUTURE,
|
|
std::vector<traced_polygon_t>& m0_polygons,
|
|
std::vector<int>& m0_sm_cutpath_adjacent_polygons,
|
|
std::vector<int>& m0_cm_cutpath_adjacent_polygons,
|
|
int& traced_sm_polygon_count,
|
|
std::unordered_map<int, fd_t>& m0_to_ps_face) {
|
|
int base_offset = (int)m0_polygons.size();
|
|
m0_polygons.reserve(m0_polygons.size() + m0_polygons_FUTURE.size());
|
|
m0_polygons.insert(m0_polygons.end(), m0_polygons_FUTURE.cbegin(), m0_polygons_FUTURE.cend());
|
|
|
|
m0_sm_cutpath_adjacent_polygons.reserve(m0_sm_cutpath_adjacent_polygons.size() + m0_sm_cutpath_adjacent_polygons_FUTURE.size());
|
|
for (int i = 0; i < (int)m0_sm_cutpath_adjacent_polygons_FUTURE.size(); ++i) {
|
|
const int local_polygon_idx = m0_sm_cutpath_adjacent_polygons_FUTURE[i];
|
|
const int global_polygon_idx = local_polygon_idx + base_offset;
|
|
m0_sm_cutpath_adjacent_polygons.push_back(global_polygon_idx);
|
|
}
|
|
|
|
m0_cm_cutpath_adjacent_polygons.reserve(m0_cm_cutpath_adjacent_polygons.size() + m0_cm_cutpath_adjacent_polygons_FUTURE.size());
|
|
for (int i = 0; i < (int)m0_cm_cutpath_adjacent_polygons_FUTURE.size(); ++i) {
|
|
const int local_polygon_idx = m0_cm_cutpath_adjacent_polygons_FUTURE[i];
|
|
const int global_polygon_idx = local_polygon_idx + base_offset;
|
|
m0_cm_cutpath_adjacent_polygons.push_back(global_polygon_idx);
|
|
}
|
|
|
|
traced_sm_polygon_count += traced_sm_polygon_count_FUTURE;
|
|
|
|
for (std::unordered_map<int, fd_t>::const_iterator i = m0_to_ps_face_FUTURE.cbegin(); i != m0_to_ps_face_FUTURE.cend(); ++i) {
|
|
const int local_polygon_idx = i->first;
|
|
const int global_polygon_idx = local_polygon_idx + base_offset;
|
|
m0_to_ps_face[global_polygon_idx] = i->second;
|
|
}
|
|
};
|
|
|
|
// merge thread-local output into global data structures
|
|
for (int i = 0; i < (int)futures.size(); ++i) {
|
|
std::future<OutputStorageTypesTuple>& f = futures[i];
|
|
MCUT_ASSERT(f.valid());
|
|
OutputStorageTypesTuple future_result = f.get(); // "get()" is a blocking function
|
|
|
|
const std::vector<traced_polygon_t>& m0_polygons_FUTURE = std::get<0>(future_result);
|
|
const std::vector<int>& m0_sm_cutpath_adjacent_polygons_FUTURE = std::get<1>(future_result);
|
|
const std::vector<int>& m0_cm_cutpath_adjacent_polygons_FUTURE = std::get<2>(future_result);
|
|
const int& traced_sm_polygon_count_FUTURE = std::get<3>(future_result);
|
|
const std::unordered_map<int, fd_t>& m0_to_ps_face_FUTURE = std::get<4>(future_result);
|
|
|
|
merge_local_traced_faces(
|
|
m0_polygons_FUTURE,
|
|
m0_sm_cutpath_adjacent_polygons_FUTURE,
|
|
m0_cm_cutpath_adjacent_polygons_FUTURE,
|
|
traced_sm_polygon_count_FUTURE,
|
|
m0_to_ps_face_FUTURE,
|
|
m0_polygons,
|
|
m0_sm_cutpath_adjacent_polygons,
|
|
m0_cm_cutpath_adjacent_polygons,
|
|
traced_sm_polygon_count,
|
|
m0_to_ps_face);
|
|
}
|
|
|
|
// merge master thread output at the end to that we maintain the order of the traced polygons
|
|
// This order is important for a number of tricks employed throughout the code in following parts
|
|
// e.g. using integer offsets to infer the start of cm traced polygons etc.
|
|
const std::vector<traced_polygon_t>& m0_polygons_MASTER_THREAD_LOCAL = std::get<0>(partial_res);
|
|
const std::vector<int>& m0_sm_cutpath_adjacent_polygons_MASTER_THREAD_LOCAL = std::get<1>(partial_res);
|
|
const std::vector<int>& m0_cm_cutpath_adjacent_polygons_MASTER_THREAD_LOCAL = std::get<2>(partial_res);
|
|
const int& traced_sm_polygon_count_MASTER_THREAD_LOCAL = std::get<3>(partial_res);
|
|
const std::unordered_map<int, fd_t>& m0_to_ps_face_MASTER_THREAD_LOCAL = std::get<4>(partial_res);
|
|
|
|
merge_local_traced_faces(
|
|
m0_polygons_MASTER_THREAD_LOCAL,
|
|
m0_sm_cutpath_adjacent_polygons_MASTER_THREAD_LOCAL,
|
|
m0_cm_cutpath_adjacent_polygons_MASTER_THREAD_LOCAL,
|
|
traced_sm_polygon_count_MASTER_THREAD_LOCAL,
|
|
m0_to_ps_face_MASTER_THREAD_LOCAL,
|
|
m0_polygons,
|
|
m0_sm_cutpath_adjacent_polygons,
|
|
m0_cm_cutpath_adjacent_polygons,
|
|
traced_sm_polygon_count,
|
|
m0_to_ps_face);
|
|
} // end of parallel scope
|
|
#else
|
|
// for each face in the polygon-soup mesh
|
|
for (face_array_iterator_t ps_face_iter = ps.faces_begin(); ps_face_iter != ps.faces_end(); ++ps_face_iter) {
|
|
|
|
const fd_t& ps_face = *ps_face_iter;
|
|
|
|
// get all the edges that lie on "ps_face", including the new one after partiting acording to intersection
|
|
// points, and the old one which did not intersect any face
|
|
std::unordered_map<fd_t, std::vector<ed_t>>::iterator ps_iface_to_m0_edge_list_fiter = ps_iface_to_m0_edge_list.find(ps_face);
|
|
|
|
bool is_intersecting_ps_face = ps_iface_to_m0_edge_list_fiter != ps_iface_to_m0_edge_list.end();
|
|
bool is_from_cut_mesh = ps_is_cutmesh_face(ps_face, sm_face_count);
|
|
|
|
std::vector<traced_polygon_t> child_polygons; // new polygons traced on current face
|
|
|
|
if (is_intersecting_ps_face == false) { // non-intersecting face
|
|
|
|
// NOTE: here we just copy the polygon as-is because it does not change.
|
|
// --------------------------------------------------------------------
|
|
|
|
traced_polygon_t retraced_poly; // ordered sequence of halfedges defining the unchanged polygon
|
|
|
|
// query the halfedge sequence in the polygon soup that defines our polygon
|
|
std::vector<hd_t> halfedges_around_face = ps.get_halfedges_around_face(ps_face);
|
|
|
|
retraced_poly.reserve(halfedges_around_face.size()); // minimum 3 (triangle)
|
|
|
|
// what we are going to do now is:
|
|
// for each halfedge in "halfedges_around_face" (ps), find its equivalent halfedge in "m0"
|
|
// The found m0-halfedges will then define "retraced_poly"
|
|
// ----------------------------------------------------------------------------------------
|
|
|
|
// for each halfedge in the current polygon
|
|
for (std::vector<hd_t>::const_iterator hbegin = halfedges_around_face.cbegin(); hbegin != halfedges_around_face.cend(); ++hbegin) {
|
|
|
|
// get the source and target vertex descriptors in the polygon soup
|
|
const vd_t ps_h_src = ps.source(*hbegin);
|
|
const vd_t ps_h_tgt = ps.target(*hbegin);
|
|
|
|
MCUT_ASSERT((int)(ps_h_src) < (int)ps_to_m0_vtx.size() /*ps_to_m0_vtx.find(ps_h_src) != ps_to_m0_vtx.cend()*/);
|
|
const vd_t m0_h_src = SAFE_ACCESS(ps_to_m0_vtx, ps_h_src);
|
|
MCUT_ASSERT((int)(ps_h_tgt) < (int)ps_to_m0_vtx.size() /*ps_to_m0_vtx.find(ps_h_tgt) != ps_to_m0_vtx.cend()*/);
|
|
const vd_t m0_h_tgt = SAFE_ACCESS(ps_to_m0_vtx, ps_h_tgt);
|
|
|
|
// Now we find the actual "m0" halfedge equivalent to "*hbegin" using
|
|
// our "m0" source and target descriptors
|
|
// --------------------------------------------------------------------
|
|
|
|
const ed_t ps_edge = ps.edge(*hbegin); // polygon soup version of the edge of the current halfedge
|
|
const ed_t m0_edge = SAFE_ACCESS(ps_to_m0_non_intersecting_edge, ps_edge); // "m0" version of edge
|
|
const hd_t m0_edge_h0 = m0.halfedge(m0_edge, 0);
|
|
const hd_t m0_edge_h1 = m0.halfedge(m0_edge, 1);
|
|
|
|
// resolve the correct halfedge by match the source and target vertex descriptors
|
|
if (m0.source(m0_edge_h0) == m0_h_src && m0.target(m0_edge_h0) == m0_h_tgt) {
|
|
|
|
retraced_poly.emplace_back(m0_edge_h0);
|
|
} else {
|
|
|
|
retraced_poly.emplace_back(m0_edge_h1);
|
|
}
|
|
}
|
|
|
|
MCUT_ASSERT(retraced_poly.size() == halfedges_around_face.size());
|
|
|
|
const int poly_idx = (int)(m0_polygons.size() + child_polygons.size());
|
|
m0_to_ps_face[poly_idx] = ps_face;
|
|
|
|
// save the retraced polygon, using the information in "m0" from "ps"
|
|
child_polygons.emplace_back(retraced_poly);
|
|
} else {
|
|
|
|
// Here we enter the complex case of having to actually clip the current face
|
|
// because it is intersecting
|
|
// --------------------------------------------------------------------------
|
|
|
|
// retrieve the list of edges which lie on the face (some new and some original)
|
|
const std::vector<ed_t>& ps_iface_m0_edge_list = ps_iface_to_m0_edge_list_fiter->second;
|
|
|
|
// Now we gather vertices on face (including intersection points)
|
|
// ------------------------------------------------------
|
|
|
|
// Get the original vertices first, which we do by first querying them from "ps"
|
|
// and then using our maps to get their "m0" versions.
|
|
static thread_local std::vector<vd_t> ps_coincident_vertices_tmp;
|
|
ps.get_vertices_around_face(ps_coincident_vertices_tmp, ps_face);
|
|
const std::vector<vd_t>& ps_coincident_vertices = ps_coincident_vertices_tmp;
|
|
static thread_local std::vector<vd_t> coincident_vertices; // "m0" versions of those stored in "ps_coincident_vertices"
|
|
coincident_vertices.resize(ps_coincident_vertices.size());
|
|
// gather the original (m0) vertices on the face
|
|
for (int i = 0; i < (int)ps_coincident_vertices.size(); ++i) {
|
|
const vd_t ps_v = ps_coincident_vertices[i];
|
|
|
|
MCUT_ASSERT((int)(ps_v) < (int)ps_to_m0_vtx.size() /*ps_to_m0_vtx.find(ps_v) != ps_to_m0_vtx.cend()*/);
|
|
const vd_t m0_v = ps_to_m0_vtx[ps_v];
|
|
|
|
coincident_vertices[i] = m0_v;
|
|
}
|
|
|
|
MCUT_ASSERT(coincident_vertices.size() == ps_coincident_vertices.size());
|
|
|
|
// now we gather the intersection-points on the face
|
|
const std::unordered_map<fd_t, std::vector<vd_t>>::const_iterator ireg_entry_iter = ps_iface_to_ivtx_list.find(ps_face);
|
|
|
|
MCUT_ASSERT(ireg_entry_iter != ps_iface_to_ivtx_list.cend());
|
|
|
|
// const int coincident_ps_vertex_count = (int)coincident_vertices.size();
|
|
const std::vector<vd_t>& intersection_points_on_face = ireg_entry_iter->second;
|
|
coincident_vertices.reserve(coincident_vertices.size() + intersection_points_on_face.size());
|
|
coincident_vertices.insert(coincident_vertices.end(), intersection_points_on_face.cbegin(), intersection_points_on_face.cend());
|
|
|
|
MCUT_ASSERT(intersection_points_on_face.size() >= 2); // minimum (two intersecting convex polygons)
|
|
#if 0
|
|
// dump to log
|
|
if (input.verbose) {
|
|
|
|
for (std::vector<vd_t>::const_iterator j = coincident_vertices.cbegin(); j != coincident_vertices.cend(); ++j) {
|
|
}
|
|
}
|
|
#endif
|
|
// After gathering the vertices above, we will now collect edges on the face
|
|
// -------------------------------------------------------------------------
|
|
|
|
// edges on face
|
|
std::vector<ed_t> incident_edges = ps_iface_m0_edge_list; // COPY because "incident_edges" will be modified later
|
|
|
|
// number of boundary edges on the face
|
|
int incident_boundary_edge_count = 0;
|
|
|
|
// We will now partition the list of incident edges into boundary/exterior and interior.
|
|
// Boundary edges come first, then interior ones. We do this because it makes it easier for us
|
|
// to filter our interior edges if they are consecutive in the list (i.e. in "incident_edges")
|
|
std::partition(incident_edges.begin(), incident_edges.end(),
|
|
[&](const ed_t& e) {
|
|
// calculate if edge is exterior
|
|
// -----------------------------
|
|
|
|
// get vertices defining edge
|
|
const vd_t v0 = m0.vertex(e, 0);
|
|
const vd_t v1 = m0.vertex(e, 1);
|
|
|
|
// are both vertices intersection points (the main property of interior edges)
|
|
const bool v0_is_ivtx = m0_is_intersection_point(v0, ps_vtx_cnt);
|
|
const bool v1_is_ivtx = m0_is_intersection_point(v1, ps_vtx_cnt);
|
|
|
|
// if both vertices are intersection points, we must be careful to make an extra
|
|
// check that the edge is really on the boundary. Moreover, it is possible that
|
|
// their exist an edge on the boundary whose vertices are both intersection
|
|
// points - hence the possible ambiguity.
|
|
// A boundary edge defined by two intersection points can arise from "carve-out"
|
|
// cuts..
|
|
const bool is_ambiguious_boundary_edge_case = (v0_is_ivtx && v1_is_ivtx);
|
|
bool is_valid_ambiguious_boundary_edge = false;
|
|
|
|
if (is_ambiguious_boundary_edge_case) {
|
|
// get their edges
|
|
MCUT_ASSERT((size_t)v0 - ps_vtx_cnt < m0_ivtx_to_intersection_registry_entry.size() /* m0_ivtx_to_intersection_registry_entry.find(v0) != m0_ivtx_to_intersection_registry_entry.cend()*/);
|
|
const std::pair<ed_t, fd_t>& v0_ipair = SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, v0 - ps_vtx_cnt);
|
|
const ed_t v0_ps_edge = v0_ipair.first; // m0_ivtx_to_ps_edge, v0); // ps.edge(v0_coincident_ps_halfedge);
|
|
|
|
MCUT_ASSERT((size_t)v1 - ps_vtx_cnt < m0_ivtx_to_intersection_registry_entry.size() /*m0_ivtx_to_intersection_registry_entry.find(v1) != m0_ivtx_to_intersection_registry_entry.cend()*/);
|
|
const std::pair<ed_t, fd_t>& v1_ipair = SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, v1 - ps_vtx_cnt);
|
|
const ed_t v1_ps_edge = v1_ipair.first; // m0_ivtx_to_ps_edge, v1); // ps.edge(v1_coincident_ps_halfedge);
|
|
|
|
// This is true if v0 and v1 where produced by multiple intersections of one edge
|
|
// with two different faces
|
|
is_valid_ambiguious_boundary_edge = (v0_ps_edge == v1_ps_edge);
|
|
}
|
|
|
|
bool is_boundary_edge = (!is_ambiguious_boundary_edge_case || is_valid_ambiguious_boundary_edge);
|
|
if (is_boundary_edge) {
|
|
incident_boundary_edge_count++; // count
|
|
}
|
|
return is_boundary_edge;
|
|
});
|
|
|
|
// dump info to log
|
|
#if 0
|
|
if (lg.verbose()) {
|
|
for (std::vector<ed_t>::const_iterator exterior_edge_iter = incident_edges.cbegin();
|
|
exterior_edge_iter != incident_edges.cbegin() + incident_boundary_edge_count;
|
|
++exterior_edge_iter) {
|
|
}
|
|
}
|
|
#endif
|
|
MCUT_ASSERT(incident_boundary_edge_count >= 3); // minimum is 3 edge which is for a triangle
|
|
|
|
// const int interior_edges_on_face = (int)incident_edges.size() - incident_boundary_edge_count;
|
|
|
|
//
|
|
// We have the essential set of edges which will be used for clipping, the next step
|
|
// is to gather the halfedges on the clipped face from these edges.
|
|
//
|
|
// Note that the gathered set of halfedges will contain some halfedges that are redundant.
|
|
// These redundant halfedges are those which lie on the boundary of the clipped polygon and
|
|
// have a winding order which is opposite to the winding order of the input mesh which contained
|
|
// "ps_face" (i.e. either the cut-mesh or source-mesh).
|
|
//
|
|
// Thus, we need one more filtering step which will remove these redundant halfedges from
|
|
// the gather set.
|
|
|
|
std::vector<hd_t> incident_halfedges;
|
|
|
|
// 1. find an exterior halfedge (any)
|
|
hd_t first_boundary_halfedge = hmesh_t::null_halfedge();
|
|
|
|
// for each edge on clipped polygon, (i.e. from the filtered set)
|
|
for (std::vector<ed_t>::const_iterator incident_edge_iter = incident_edges.cbegin();
|
|
incident_edge_iter != incident_edges.cbegin() + incident_boundary_edge_count; // we only want exterior edges
|
|
++incident_edge_iter) {
|
|
const ed_t& edge = (*incident_edge_iter);
|
|
|
|
// for each halfedge on the current edge
|
|
for (int edge_he_iter = 0; edge_he_iter < 2; ++edge_he_iter) {
|
|
|
|
const hd_t m0_edge_he = m0.halfedge(edge, edge_he_iter);
|
|
const vd_t m0_edge_he_src = m0.source(m0_edge_he);
|
|
const vd_t m0_edge_he_tgt = m0.target(m0_edge_he);
|
|
const bool m0_edge_he_src_is_ivertex = m0_is_intersection_point(m0_edge_he_src, ps_vtx_cnt);
|
|
const bool m0_edge_he_tgt_is_ivertex = m0_is_intersection_point(m0_edge_he_tgt, ps_vtx_cnt);
|
|
|
|
if (!m0_edge_he_src_is_ivertex && !m0_edge_he_tgt_is_ivertex) { // o-->o (unmodified original edge)
|
|
|
|
const vd_t ps_he_src = m0_to_ps_vtx[m0_edge_he_src];
|
|
const vd_t ps_he_tgt = m0_to_ps_vtx[m0_edge_he_tgt];
|
|
const hd_t ps_he = ps.halfedge(ps_he_src, ps_he_tgt);
|
|
|
|
if (ps_he == hmesh_t::null_halfedge()) {
|
|
continue;
|
|
}
|
|
|
|
if (ps.face(ps_he) == ps_face) {
|
|
first_boundary_halfedge = m0_edge_he;
|
|
break;
|
|
}
|
|
} else { // x-->x OR o-->x OR x-->o
|
|
|
|
// o-->x : We want the ihalfedges which point into the sm whose tgt lays on the
|
|
// sm-face of tgt (they have an opposite direction wrt the face normal)
|
|
const bool is_ox = (!m0_edge_he_src_is_ivertex && m0_edge_he_tgt_is_ivertex);
|
|
|
|
if (is_ox) {
|
|
MCUT_ASSERT((size_t)m0_edge_he_tgt - ps_vtx_cnt < m0_ivtx_to_intersection_registry_entry.size() /*m0_ivtx_to_intersection_registry_entry.find(m0_edge_he_tgt) != m0_ivtx_to_intersection_registry_entry.cend()*/);
|
|
const std::pair<ed_t, fd_t>& m0_edge_he_tgt_ipair = SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, m0_edge_he_tgt - ps_vtx_cnt);
|
|
|
|
// get the incident ps-halfedge of tgt
|
|
ed_t tgt_ps_e = m0_edge_he_tgt_ipair.first; // SAFE_ACCESS(m0_ivtx_to_ps_edge, m0_edge_he_tgt);
|
|
hd_t tgt_ps_h = ps.halfedge(tgt_ps_e, 0);
|
|
|
|
if (ps.face(tgt_ps_h) != ps_face) {
|
|
tgt_ps_h = ps.opposite(tgt_ps_h); // i.e. "m0.halfedge(tgt_ps_e, 1);"
|
|
MCUT_ASSERT(tgt_ps_h != hmesh_t::null_halfedge()); // must be true if ps_face exists!
|
|
}
|
|
|
|
const vd_t& m0_edge_he_src_as_ps_vertex = m0_to_ps_vtx[m0_edge_he_src];
|
|
|
|
if (m0_edge_he_src_as_ps_vertex == ps.source(tgt_ps_h)) { // is counter clock-wise halfedge
|
|
first_boundary_halfedge = m0_edge_he;
|
|
break;
|
|
}
|
|
} else { // x-->x OR x-->o
|
|
|
|
const bool is_xx = m0_edge_he_src_is_ivertex && m0_edge_he_tgt_is_ivertex;
|
|
|
|
if (is_xx) { // exterior interior-iedge
|
|
|
|
// const hd_t src_coincident_ps_halfedge = m0_ivtx_to_ps_edge, m0_edge_he_src);
|
|
// const hd_t tgt_ps_h = m0_ivtx_to_ps_edge, m0_edge_he_tgt);
|
|
MCUT_ASSERT((size_t)m0_edge_he_src - ps_vtx_cnt < m0_ivtx_to_intersection_registry_entry.size() /*m0_ivtx_to_intersection_registry_entry.find(m0_edge_he_src) != m0_ivtx_to_intersection_registry_entry.cend()*/);
|
|
const std::pair<ed_t, fd_t>& m0_edge_he_src_ipair = SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, m0_edge_he_src - ps_vtx_cnt);
|
|
MCUT_ASSERT((size_t)m0_edge_he_tgt - ps_vtx_cnt < m0_ivtx_to_intersection_registry_entry.size() /*m0_ivtx_to_intersection_registry_entry.find(m0_edge_he_tgt) != m0_ivtx_to_intersection_registry_entry.cend()*/);
|
|
const std::pair<ed_t, fd_t>& m0_edge_he_tgt_ipair = SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, m0_edge_he_tgt - ps_vtx_cnt);
|
|
|
|
const ed_t src_ps_edge = m0_edge_he_src_ipair.first; // SAFE_ACCESS(m0_ivtx_to_ps_edge, m0_edge_he_src); // ps.edge(src_coincident_ps_halfedge)
|
|
|
|
const ed_t tgt_ps_edge = m0_edge_he_tgt_ipair.first; // SAFE_ACCESS(m0_ivtx_to_ps_edge, m0_edge_he_tgt); // ps.edge(tgt_ps_h);
|
|
const bool is_exterior_ih = (src_ps_edge == tgt_ps_edge);
|
|
|
|
if (!is_exterior_ih) {
|
|
continue; // interior ihalfedges cannot be used as the first exterior ihalfedge
|
|
}
|
|
|
|
/*
|
|
At this point, vertex information alone is insufficient to select the correct
|
|
ihalfedge from the edge. This is because the two vertices are topologically equivalent
|
|
if we just try to distinguish them following similar rules as other iedge types.
|
|
To solve this problem, we must instead use the connectivity information of
|
|
the polygon soup by relying on the incident ps-halfedge common to both vertices
|
|
(in their registry entries). The rough idea/steps:
|
|
|
|
1. get incident ps-halfedge incident to both src and tgt
|
|
2. get m0 edges incident to the edge on <1>
|
|
3. sort <2> with the first edge containing the source of <1>
|
|
4. get the halfedge sequence in <3> where src of the first he is the src of <1> and the tgt of the last he is the tgt of <1>
|
|
5. get the halfedge in <4> which is incident to the same ivertices as the current potential first-boundary halfedge
|
|
6. if the ps-face of <1> is the same as the current face
|
|
7. set first polygon-boundary halfedge as <5>
|
|
8. else
|
|
9. set first polygon-boundary halfedge as opposite of <5>
|
|
*/
|
|
|
|
// 1. get incident ps-halfedge incident to both src and tgt
|
|
hd_t ps_halfedge_of_face = ps.halfedge(tgt_ps_edge, 0); // tgt_ps_h;
|
|
|
|
// equivalent to the check done at step 6. so that we know the correct halfedge to use in the steps ahead
|
|
if (ps.face(ps_halfedge_of_face) != ps_face) {
|
|
ps_halfedge_of_face = ps.opposite(ps_halfedge_of_face);
|
|
MCUT_ASSERT(ps_halfedge_of_face != hmesh_t::null_halfedge()); // guarranteed to exist since we have a poly-boundary interior ihalfedge
|
|
}
|
|
|
|
// 2. get m0 edges incident to the edge on <1>
|
|
const ed_t& incident_ps_edge = ps.edge(ps_halfedge_of_face);
|
|
std::map<ed_t, std::vector<ed_t>>::const_iterator ps_to_m0_edges_find_iter = ps_to_m0_edges.find(incident_ps_edge);
|
|
MCUT_ASSERT(ps_to_m0_edges_find_iter != ps_to_m0_edges.cend()); // because incident_ps_edge contains a polygon exterior interior ihalfedge
|
|
|
|
// 3. sort <2> with the first edge containing the source of <1>
|
|
// NOTE: The edges are already sorted based on how we created "edges between the sorted vertices that are coincident on the same
|
|
// ps-edge that has more-than 3 incident vertices." (Refer to that step for details)
|
|
const std::vector<ed_t>& sorted_m0_edges = ps_to_m0_edges_find_iter->second;
|
|
|
|
// 4. get the halfedge sequence in <3> where src of the first he is the src of <1> and the tgt of the last he is the tgt of <1>
|
|
std::vector<hd_t> halfedge_sequence;
|
|
|
|
// add the first halfedge (its source must be an original vertex)
|
|
// NOTE: its does not matter whether "first_he" point in the wrong direction or not right now (read on...)
|
|
const ed_t& first_e = sorted_m0_edges.front();
|
|
hd_t first_he = m0.halfedge(first_e, 0);
|
|
vd_t first_he_src = m0.source(first_he);
|
|
|
|
if (m0_is_intersection_point(first_he_src, ps_vtx_cnt)) {
|
|
// TODO: I think this scope is never entered based on how we created "sorted_m0_edges".
|
|
// Thus, the source vertex of the first halfedge in the sequence cannot be an intersection point
|
|
first_he = m0.halfedge(first_e, 1);
|
|
first_he_src = m0.source(first_he);
|
|
MCUT_ASSERT(!m0_is_intersection_point(first_he_src, ps_vtx_cnt)); // expect original vertex since halfedge edge is the first in sequence
|
|
}
|
|
|
|
halfedge_sequence.push_back(first_he);
|
|
|
|
// get the remaining halfedge of sequence
|
|
for (std::vector<ed_t>::const_iterator seq_edge_iter = sorted_m0_edges.cbegin() + 1; // we have already added the first halfedge
|
|
seq_edge_iter != sorted_m0_edges.cend(); ++seq_edge_iter) {
|
|
|
|
// if (seq_edge_iter == sorted_m0_edges.cbegin()) {
|
|
// continue; // we have already added the first halfedge
|
|
// }
|
|
|
|
const ed_t& e = *seq_edge_iter;
|
|
const hd_t h0 = m0.halfedge(e, 0);
|
|
|
|
if (m0.source(h0) == m0.target(halfedge_sequence.back())) {
|
|
halfedge_sequence.emplace_back(h0);
|
|
} else {
|
|
const hd_t h1 = m0.halfedge(e, 1);
|
|
halfedge_sequence.emplace_back(h1);
|
|
}
|
|
}
|
|
|
|
// we have our sequence but it is not gurranteed to point in the correct direction
|
|
MCUT_ASSERT(halfedge_sequence.size() == sorted_m0_edges.size());
|
|
|
|
const vd_t& first_he_src_as_ps_vertex = m0_to_ps_vtx[first_he_src]; // first he of sequence
|
|
|
|
if (first_he_src_as_ps_vertex != ps.source(ps_halfedge_of_face)) {
|
|
|
|
// flip the sequence to make it point in the right direction
|
|
std::for_each(
|
|
halfedge_sequence.begin(),
|
|
halfedge_sequence.end(),
|
|
[&](hd_t& he) {
|
|
he = m0.opposite(he);
|
|
}); // flip seq
|
|
}
|
|
|
|
// 5. get the halfedge in <4> which is incident to the same ivertices as the current potential first-exterior halfedge
|
|
|
|
std::vector<hd_t>::const_iterator matching_he_find_iter = std::find_if(
|
|
halfedge_sequence.cbegin(),
|
|
halfedge_sequence.cend(),
|
|
[&](const hd_t& he) {
|
|
const vd_t& he_src = m0.source(he);
|
|
const vd_t& he_tgt = m0.target(he);
|
|
|
|
return (he_src == m0_edge_he_src && he_tgt == m0_edge_he_tgt /*|| (he_src == m0_edge_he_tgt && he_tgt == m0_edge_he_src*/);
|
|
});
|
|
|
|
MCUT_ASSERT(matching_he_find_iter != halfedge_sequence.cend()); // does the potential halfedge actually point in the correct direction or not
|
|
{
|
|
// 6. if the ps-face of <1> is the same as the current face
|
|
// 7. set first polygon-exterior halfedge as <5>
|
|
// 8. else
|
|
// 9. set first polygon-exterior halfedge as opposite of <5>
|
|
// if (ps.face(incident_ps_halfedge) == ps_face) {
|
|
first_boundary_halfedge = *matching_he_find_iter;
|
|
break;
|
|
}
|
|
} // if (is_xx) {
|
|
else {
|
|
// TODO: implement logic for is_xo
|
|
|
|
// NOTE: The code is able to work without implementing this scope due to the order-dependent nature in which
|
|
// edges are traversed (its guarranteed that oo ox and xx edges are encountered first) to find the first
|
|
// boundary halfedge
|
|
}
|
|
} // } else { // x-->x OR x-->o
|
|
} // } else { // x-->x OR o-->x OR x-->o
|
|
} // for (int edge_he_iter = 0; edge_he_iter < 2; ++edge_he_iter) {
|
|
|
|
if (first_boundary_halfedge != hmesh_t::null_halfedge()) {
|
|
break; // done
|
|
}
|
|
}
|
|
|
|
MCUT_ASSERT(first_boundary_halfedge != hmesh_t::null_halfedge());
|
|
|
|
// Now that we have a halfedge which lies on the boundary of the clipped polygon,
|
|
// we will traverse/walk the clipped polygon's exterior to collect all other boundary halfedges
|
|
// that have the same winding order as the input meshes (i.e. the source-mesh and cut-mesh).
|
|
|
|
hd_t current_exterior_halfedge = hmesh_t::null_halfedge();
|
|
hd_t next_exterior_halfedge = first_boundary_halfedge;
|
|
std::unordered_map<ed_t, bool> walked_edges;
|
|
// walked_edges.reserve(incident_edges.size());
|
|
do {
|
|
|
|
current_exterior_halfedge = next_exterior_halfedge;
|
|
incident_halfedges.emplace_back(current_exterior_halfedge);
|
|
walked_edges[m0.edge(current_exterior_halfedge)] = true;
|
|
|
|
const vd_t current_tgt = m0.target(current_exterior_halfedge);
|
|
next_exterior_halfedge = hmesh_t::null_halfedge(); // reset
|
|
|
|
// find next boundary halfedge from incident edges
|
|
for (std::vector<ed_t>::const_iterator incident_edge_iter = incident_edges.cbegin();
|
|
incident_edge_iter != incident_edges.cbegin() + incident_boundary_edge_count; // we only want exterior edges;
|
|
++incident_edge_iter) {
|
|
// const int incident_edge_idx = (int)std::distance(incident_edges.cbegin(), incident_edge_iter);
|
|
// if (incident_edge_idx >= incident_boundary_edge_count) {
|
|
// continue; // we only want exterior halfedge
|
|
// }
|
|
|
|
const ed_t& edge = *incident_edge_iter;
|
|
|
|
bool edge_walked = walked_edges.find(edge) != walked_edges.cend(); // std::find(walked_edges.cbegin(), walked_edges.cend(), edge) != walked_edges.cend();
|
|
|
|
if (edge_walked) {
|
|
continue; // skip edge is walked already
|
|
}
|
|
|
|
const vd_t v0 = m0.vertex(edge, 0);
|
|
const vd_t v1 = m0.vertex(edge, 1);
|
|
|
|
if (v0 == current_tgt || v1 == current_tgt) // check if connected to current (i.e. they share one vertex)
|
|
{
|
|
const bool v0_is_ivtx = m0_is_intersection_point(v0, ps_vtx_cnt);
|
|
const bool v1_is_ivtx = m0_is_intersection_point(v1, ps_vtx_cnt);
|
|
bool is_ambiguious_boundary_edge_case = v0_is_ivtx && v1_is_ivtx;
|
|
bool is_valid_ambiguious_boundary_edge = false;
|
|
|
|
if (is_ambiguious_boundary_edge_case) { // exterior edge with two intersection vertices (ambigious case arising from concave polyhedron cut)
|
|
|
|
MCUT_ASSERT((size_t)v0 - ps_vtx_cnt < m0_ivtx_to_intersection_registry_entry.size() /* m0_ivtx_to_intersection_registry_entry.find(v0) != m0_ivtx_to_intersection_registry_entry.cend()*/);
|
|
const std::pair<ed_t, fd_t>& v0_ipair = SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, v0 - ps_vtx_cnt);
|
|
const ed_t v0_ps_edge = v0_ipair.first; // SAFE_ACCESS(m0_ivtx_to_ps_edge, v0); //ps.edge(v0_coincident_ps_halfedge);
|
|
|
|
MCUT_ASSERT((size_t)v1 - ps_vtx_cnt < m0_ivtx_to_intersection_registry_entry.size() /* m0_ivtx_to_intersection_registry_entry.find(v1) != m0_ivtx_to_intersection_registry_entry.cend()*/);
|
|
const std::pair<ed_t, fd_t>& v1_ipair = SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, v1 - ps_vtx_cnt);
|
|
const ed_t v1_ps_edge = v1_ipair.first; // SAFE_ACCESS(m0_ivtx_to_ps_edge, v1); // ps.edge(v1_coincident_ps_halfedge);
|
|
|
|
is_valid_ambiguious_boundary_edge = (v0_ps_edge == v1_ps_edge); // see also above when gathering exterior incident edges
|
|
}
|
|
|
|
if (!is_ambiguious_boundary_edge_case || is_valid_ambiguious_boundary_edge) {
|
|
const hd_t h0 = m0.halfedge(edge, 0);
|
|
const hd_t h1 = m0.halfedge(edge, 1);
|
|
|
|
next_exterior_halfedge = h0;
|
|
|
|
if (m0.source(h0) != current_tgt) // h0 is facing the opposite dir
|
|
{
|
|
next_exterior_halfedge = h1;
|
|
}
|
|
|
|
break; // found
|
|
}
|
|
}
|
|
}
|
|
|
|
} while (next_exterior_halfedge != hmesh_t::null_halfedge() /*first_boundary_halfedge*/);
|
|
|
|
MCUT_ASSERT(incident_halfedges.size() >= 3); // minimum i.e. for a triangles!
|
|
|
|
// Note: at this stage we have gathered all of the [exterior] halfedges needed to traced child polygons
|
|
|
|
const int exterior_halfedge_count = (int)incident_halfedges.size();
|
|
|
|
MCUT_ASSERT(exterior_halfedge_count == incident_boundary_edge_count);
|
|
|
|
// Now we are going to also gather interior halfedges (those passing inside the area of "ps_face", and defined only by intersection points
|
|
// where the src and tgt vertex do not share the same incident ihalfedge in their registry entry.
|
|
|
|
for (std::vector<ed_t>::const_iterator incident_edge_iter = incident_edges.cbegin() + incident_boundary_edge_count; // start from polygon interior edges offset
|
|
incident_edge_iter != incident_edges.cend();
|
|
++incident_edge_iter) {
|
|
|
|
const ed_t& edge = (*incident_edge_iter);
|
|
|
|
const hd_t h0 = m0.halfedge(edge, 0);
|
|
const hd_t h1 = m0.halfedge(edge, 1);
|
|
incident_halfedges.emplace_back(h0);
|
|
incident_halfedges.emplace_back(h1);
|
|
}
|
|
|
|
// Note: at this stage, we have all the halfedges that we need to trace child polygons.
|
|
// Thus, the next step is the actual tracing to clip ps_face
|
|
|
|
// Trace child polygons on ps_face to clip it
|
|
//-------------------------------------------
|
|
|
|
std::vector<hd_t> incident_halfedges_to_be_walked(incident_halfedges.cbegin(), incident_halfedges.cend()); // copy
|
|
|
|
do { // each iteration traces a child polygon
|
|
|
|
traced_polygon_t child_polygon;
|
|
|
|
hd_t current_halfedge = hmesh_t::null_halfedge();
|
|
// can be any boundary halfedge in vector (NOTE: boundary halfedges come first in the std::vector)
|
|
// Its important that we start from boundary halfedge as it simplies the conditions for when a
|
|
// valid polygon has been constructed
|
|
hd_t next_halfedge = incident_halfedges_to_be_walked[0];
|
|
|
|
MCUT_ASSERT(incident_halfedges_to_be_walked.size() >= 2);
|
|
|
|
bool is_valid_polygon = false;
|
|
do { // each iteration walks a halfedge to incremetally trace a child polygon
|
|
|
|
// 1. update state
|
|
current_halfedge = next_halfedge;
|
|
|
|
child_polygon.push_back(current_halfedge);
|
|
const vd_t current_halfedge_target = m0.target(current_halfedge);
|
|
next_halfedge = hmesh_t::null_halfedge(); // reset
|
|
|
|
// remove next halfedge so that we dont walk it again
|
|
{
|
|
std::vector<hd_t>::iterator find_iter = std::find(incident_halfedges_to_be_walked.begin(), incident_halfedges_to_be_walked.end(), current_halfedge);
|
|
MCUT_ASSERT(find_iter != incident_halfedges_to_be_walked.end());
|
|
incident_halfedges_to_be_walked.erase(find_iter); // remove
|
|
}
|
|
|
|
if (child_polygon.size() >= 3) { // minimum halfedge count to constitute a valid polygon (triangle)
|
|
|
|
// source of first halfedge is target of last
|
|
if (m0.source(child_polygon.front()) == m0.target(child_polygon.back())) {
|
|
|
|
// the current halfedge is [not] the opposite of the first halfedge in list
|
|
// This is an important edge case for when you walk a halfedge connecting two vertices not in alpha (intersection).
|
|
// Example: case of tracing a polygon analogous to a cheek slash.
|
|
// See also the comment above the declaration of "next_halfedge"
|
|
if (current_halfedge != m0.opposite(child_polygon.front())) {
|
|
is_valid_polygon = true;
|
|
|
|
break;
|
|
} else { // ... if the current halfedge is the opposite of the first halfedge in list
|
|
|
|
// peak forward to see what the next halfedge will be (if the next halfedge is in "child_polygon" then we are done)
|
|
std::vector<hd_t> premptive_candidate_halfedges;
|
|
for (std::vector<hd_t>::const_iterator incident_halfedges_to_be_walked_iter = incident_halfedges_to_be_walked.cbegin(); incident_halfedges_to_be_walked_iter != incident_halfedges_to_be_walked.cend(); ++incident_halfedges_to_be_walked_iter) {
|
|
const hd_t& potential_candidate = *incident_halfedges_to_be_walked_iter;
|
|
if (m0.source(potential_candidate) == current_halfedge_target) {
|
|
premptive_candidate_halfedges.push_back(potential_candidate);
|
|
}
|
|
}
|
|
|
|
hd_t prime_candidate = hmesh_t::null_halfedge();
|
|
if (premptive_candidate_halfedges.size() == 2) {
|
|
|
|
const std::vector<hd_t>::const_iterator current_halfedge_find_iter = std::find(incident_halfedges.cbegin(), incident_halfedges.cend(), current_halfedge);
|
|
MCUT_ASSERT(current_halfedge_find_iter != incident_halfedges.cend());
|
|
const bool current_halfedge_is_exterior = std::distance(incident_halfedges.cbegin(), current_halfedge_find_iter) < exterior_halfedge_count;
|
|
|
|
if (current_halfedge_is_exterior) {
|
|
|
|
// pick interior candidate
|
|
const std::vector<hd_t>::const_iterator prime_candidate_find_iter = std::find(incident_halfedges.cbegin(), incident_halfedges.cend(), current_halfedge);
|
|
MCUT_ASSERT(prime_candidate_find_iter != incident_halfedges.cend());
|
|
const bool prime_candidate_is_exterior = std::distance(incident_halfedges.cbegin(), prime_candidate_find_iter) < exterior_halfedge_count;
|
|
|
|
if (prime_candidate_is_exterior) {
|
|
prime_candidate = premptive_candidate_halfedges.back(); // select correct interior halfedge
|
|
}
|
|
} else { // interior
|
|
|
|
// pick non-opposite
|
|
const bool prime_candidate_is_opposite = m0.opposite(current_halfedge) == prime_candidate;
|
|
|
|
if (prime_candidate_is_opposite) {
|
|
prime_candidate = premptive_candidate_halfedges.back(); // select correct non-opposite halfedge
|
|
}
|
|
}
|
|
}
|
|
|
|
const hd_t premptive_next = prime_candidate;
|
|
|
|
MCUT_ASSERT(premptive_candidate_halfedges.size() <= 2);
|
|
|
|
if (std::find(child_polygon.cbegin(), child_polygon.cend(), premptive_next) != child_polygon.cend()) {
|
|
is_valid_polygon = true;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 2. find next halfedge
|
|
|
|
// 2.1. get candidates (halfedges whose source vertex is the target of current)
|
|
std::vector<hd_t> candidate_halfedges;
|
|
candidate_halfedges.reserve(2); // two candidates at most because we filtered out exterior interior-halfedges as well as clockwise (cw) halfedge
|
|
|
|
for (std::vector<hd_t>::const_iterator incident_halfedges_to_be_walked_iter = incident_halfedges_to_be_walked.cbegin(); incident_halfedges_to_be_walked_iter != incident_halfedges_to_be_walked.cend(); ++incident_halfedges_to_be_walked_iter) {
|
|
const hd_t& potential_candidate = *incident_halfedges_to_be_walked_iter;
|
|
if (m0.source(potential_candidate) == current_halfedge_target) {
|
|
candidate_halfedges.push_back(potential_candidate);
|
|
}
|
|
}
|
|
|
|
MCUT_ASSERT(candidate_halfedges.size() <= 2);
|
|
|
|
// 2.2. select prime candidate
|
|
hd_t prime_candidate = hmesh_t::null_halfedge();
|
|
|
|
if (!candidate_halfedges.empty()) {
|
|
prime_candidate = candidate_halfedges[0]; // assuming: candidate_halfedges.size() == 1
|
|
}
|
|
|
|
if (candidate_halfedges.size() == 2) {
|
|
|
|
const std::vector<hd_t>::const_iterator current_halfedge_find_iter = std::find(incident_halfedges.cbegin(), incident_halfedges.cend(), current_halfedge);
|
|
MCUT_ASSERT(current_halfedge_find_iter != incident_halfedges.cend());
|
|
const bool current_halfedge_is_exterior = std::distance(incident_halfedges.cbegin(), current_halfedge_find_iter) < exterior_halfedge_count;
|
|
|
|
if (current_halfedge_is_exterior) {
|
|
|
|
// pick interior candidate
|
|
const std::vector<hd_t>::const_iterator prime_candidate_find_iter = std::find(incident_halfedges.cbegin(), incident_halfedges.cend(), current_halfedge);
|
|
MCUT_ASSERT(prime_candidate_find_iter != incident_halfedges.cend());
|
|
const bool prime_candidate_is_exterior = std::distance(incident_halfedges.cbegin(), prime_candidate_find_iter) < exterior_halfedge_count;
|
|
|
|
if (prime_candidate_is_exterior) {
|
|
prime_candidate = candidate_halfedges.back(); // select correct interior halfedge
|
|
}
|
|
} else { // interior
|
|
|
|
// pick non-opposite
|
|
const bool prime_candidate_is_opposite = m0.opposite(current_halfedge) == prime_candidate;
|
|
|
|
if (prime_candidate_is_opposite) {
|
|
prime_candidate = candidate_halfedges.back(); // select correct non-opposite halfedge
|
|
}
|
|
}
|
|
}
|
|
|
|
next_halfedge = prime_candidate;
|
|
|
|
} while (next_halfedge != hmesh_t::null_halfedge());
|
|
|
|
if (is_valid_polygon) {
|
|
|
|
const int poly_idx = (int)(m0_polygons.size() + child_polygons.size());
|
|
if (ps_is_cutmesh_face(ps_face, sm_face_count)) {
|
|
m0_cm_cutpath_adjacent_polygons.push_back(poly_idx);
|
|
} else {
|
|
m0_sm_cutpath_adjacent_polygons.push_back(poly_idx);
|
|
}
|
|
|
|
m0_to_ps_face[poly_idx] = ps_face;
|
|
|
|
child_polygons.emplace_back(child_polygon);
|
|
}
|
|
|
|
} while (!incident_halfedges_to_be_walked.empty());
|
|
} // if (!is_intersecting_ps_face) {
|
|
|
|
m0_polygons.insert(m0_polygons.end(), child_polygons.cbegin(), child_polygons.cend());
|
|
|
|
if (!is_from_cut_mesh /*!ps_is_cutmesh_face(ps_face, sm_face_count)*/) {
|
|
traced_sm_polygon_count += (int)child_polygons.size();
|
|
}
|
|
|
|
} // for each ps-face to trace
|
|
|
|
#endif // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
|
|
TIMESTACK_POP(); // &&&&&
|
|
|
|
// m0_ivtx_to_ps_faces.clear(); // free
|
|
ps_iface_to_m0_edge_list.clear(); // free
|
|
ps_to_m0_edges.clear(); // free
|
|
ps_to_m0_non_intersecting_edge.clear(); // free
|
|
ps_iface_to_ivtx_list.clear(); // free
|
|
|
|
// Note: at this stage, we have traced all polygons. This means that any intersecting face in the polygon
|
|
// soup data structure will also now have been clipped.
|
|
//
|
|
// The connectivity of all traced polygons is stored as a vector/array of halfedges, for each
|
|
// traced polygon. The halfedge data structure (i.e. "m0") still holds the underlying mesh data
|
|
// over-which we are abstracting this connectivity i.e. "m0" stores vertices (e.g. intersection
|
|
// points), edges, and halfeges.
|
|
//
|
|
// The lists of halfedges that we are using to represent the traced polygons avoids "2-manifold restrictions":
|
|
// Storing the traced polygons inside a halfedge data structure is not always possible because we could violate the
|
|
// priniciple rule that an edge must be incident to at-most 2 faces (2-manifold surface mesh rule).
|
|
//
|
|
// There is a other benefit to using lists: it makes for a more logical implementation for the remainder of the
|
|
// cutting algorithm i.e when duplicating intersection points, creating cut-mesh patches, stitching (hole
|
|
// filling), and more.
|
|
|
|
MCUT_ASSERT((int)m0_polygons.size() >= ps.number_of_faces());
|
|
|
|
const std::vector<traced_polygon_t>::iterator traced_sm_polygons_iter_end = m0_polygons.begin() + traced_sm_polygon_count;
|
|
// const std::vector<traced_polygon_t>::iterator& traced_cs_polygons_iter_begin = traced_sm_polygons_iter_end;
|
|
const std::vector<traced_polygon_t>::const_iterator m0_traced_sm_polygons_iter_cend = m0_polygons.cbegin() + traced_sm_polygon_count;
|
|
const std::vector<traced_polygon_t>::const_iterator& traced_cs_polygons_iter_cbegin = traced_sm_polygons_iter_end;
|
|
|
|
TIMESTACK_PUSH("Mark seam edges *");
|
|
// extract the seam vertices
|
|
std::vector<bool> m0_vertex_to_seam_flag;
|
|
mark_seam_vertices(m0_vertex_to_seam_flag, m0, ps_vtx_cnt);
|
|
TIMESTACK_POP();
|
|
|
|
MCUT_ASSERT(!m0_vertex_to_seam_flag.empty());
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Dump meshes for the source-mesh and cut-mesh using the traced polygons
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
// NOTE: we cannot always create meshes using the traced polygons because of
|
|
// a possible violation of the surface mesh contruction rules. Basically, we cannot
|
|
// reference a halfedge and its opposite in the same face because it violates
|
|
// halfedge construction rules (2-manifold surface mesh). This issue occurs
|
|
// whenever ps polygon is partially cut.
|
|
//
|
|
// Thus, we will only dump meshes if can gaurranteed not to violate halfedge
|
|
// mesh rules (to avoid potentially crashing the program due to logic error).
|
|
//
|
|
|
|
bool all_cutpaths_are_circular = (num_explicit_circular_cutpaths == num_explicit_cutpath_sequences);
|
|
|
|
// dump traced src-mesh polygons.
|
|
if (input.keep_srcmesh_seam) {
|
|
// dump traced polygons only if the cut paths are circular or complete linear cuts (prevents us
|
|
// from violating halfedge construction rules)
|
|
|
|
bool all_cutpaths_linear_and_without_making_holes = (num_explicit_circular_cutpaths == 0) && ((int)explicit_cutpaths_severing_srcmesh.size() == num_explicit_linear_cutpaths);
|
|
|
|
if (cm_is_watertight || (all_cutpaths_are_circular || all_cutpaths_linear_and_without_making_holes)) {
|
|
|
|
std::map<std::size_t, std::vector<std::pair<std::shared_ptr<hmesh_t>, connected_component_info_t>>> separated_src_mesh_fragments;
|
|
std::unordered_map<int, int> _1;
|
|
// NOTE: The result is a mesh identical to the original source mesh except at the edges introduced by the cut..
|
|
extract_connected_components(
|
|
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
*input.scheduler,
|
|
#endif
|
|
separated_src_mesh_fragments,
|
|
m0,
|
|
0, // no offset because traced source-mesh polygons start from the beginning of "m0_polygons"
|
|
std::vector<traced_polygon_t>(m0_polygons.begin(), traced_sm_polygons_iter_end),
|
|
std::vector<int>(), // sm_polygons_below_cs
|
|
std::vector<int>(), // sm_polygons_above_cs
|
|
m0_vertex_to_seam_flag,
|
|
std::vector<vd_t>(), // Unused ... because we are extracting from "m0"
|
|
std::unordered_map<vd_t, vd_t>(), // Unused ... because we are extracting from "m0"
|
|
_1, // Unused ... because we are extracting from "m0"
|
|
m0_to_ps_vtx,
|
|
m0_to_ps_face,
|
|
ps_to_sm_vtx,
|
|
ps_to_sm_face,
|
|
ps_to_cm_vtx,
|
|
ps_to_cm_face,
|
|
sm_vtx_cnt,
|
|
sm_face_count,
|
|
input.populate_vertex_maps,
|
|
input.populate_face_maps,
|
|
false, // unused ...
|
|
false, // unused ...
|
|
false // unused ...
|
|
);
|
|
|
|
MCUT_ASSERT(separated_src_mesh_fragments.size() == 1); // one cc
|
|
MCUT_ASSERT(separated_src_mesh_fragments.cbegin()->second.size() == 1); // one instance
|
|
output.seamed_src_mesh = std::shared_ptr<output_mesh_info_t>(new output_mesh_info_t);
|
|
output.seamed_src_mesh->mesh = (separated_src_mesh_fragments.begin()->second.front().first);
|
|
output.seamed_src_mesh->seam_vertices = std::move(separated_src_mesh_fragments.begin()->second.front().second.seam_vertices);
|
|
output.seamed_src_mesh->data_maps = std::move(separated_src_mesh_fragments.begin()->second.front().second.data_maps);
|
|
|
|
if (input.verbose) {
|
|
dump_mesh(output.seamed_src_mesh->mesh.get()[0], "src-mesh-traced-poly");
|
|
}
|
|
}
|
|
} // if (input.include_seam_srcmesh) {
|
|
|
|
// dump traced cut-mesh polygons
|
|
if (input.keep_cutmesh_seam) {
|
|
|
|
// bool all_cutpaths_linear_and_make_holes = (num_explicit_circular_cutpaths == 0) && (explicit_cutpaths_severing_srcmesh.size() == 0);
|
|
|
|
if (sm_is_watertight || (all_cutpaths_are_circular /*|| all_cutpaths_linear_and_make_holes*/)) {
|
|
std::map<std::size_t, std::vector<std::pair<std::shared_ptr<hmesh_t>, connected_component_info_t>>> separated_cut_mesh_fragments;
|
|
std::unordered_map<int, int> _1;
|
|
hmesh_t merged = extract_connected_components(
|
|
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
*input.scheduler,
|
|
#endif
|
|
separated_cut_mesh_fragments,
|
|
m0,
|
|
traced_sm_polygon_count, // offset to start of traced cut-mesh polygons in "m0_polygons".
|
|
std::vector<traced_polygon_t>(traced_cs_polygons_iter_cbegin, m0_polygons.cend()),
|
|
std::vector<int>(),
|
|
std::vector<int>(),
|
|
m0_vertex_to_seam_flag,
|
|
std::vector<vd_t>(), // Unused ... because we are extracting from "m0"
|
|
std::unordered_map<vd_t, vd_t>(), // Unused ... because we are extracting from "m0"
|
|
_1, // Unused ... because we are extracting from "m0"
|
|
m0_to_ps_vtx,
|
|
m0_to_ps_face,
|
|
ps_to_sm_vtx,
|
|
ps_to_sm_face,
|
|
ps_to_cm_vtx,
|
|
ps_to_cm_face,
|
|
sm_vtx_cnt,
|
|
sm_face_count,
|
|
input.populate_vertex_maps,
|
|
input.populate_face_maps,
|
|
false, // Unused ...
|
|
false, // Unused ...
|
|
false // Unused ...
|
|
);
|
|
|
|
if (separated_cut_mesh_fragments.size() == 1) { // usual case
|
|
MCUT_ASSERT(separated_cut_mesh_fragments.cbegin()->second.size() == 1); // one instance
|
|
output.seamed_cut_mesh = std::shared_ptr<output_mesh_info_t>(new output_mesh_info_t);
|
|
output.seamed_cut_mesh->mesh = (separated_cut_mesh_fragments.begin()->second.front().first);
|
|
output.seamed_cut_mesh->seam_vertices = std::move(separated_cut_mesh_fragments.begin()->second.front().second.seam_vertices);
|
|
output.seamed_cut_mesh->data_maps = std::move(separated_cut_mesh_fragments.begin()->second.front().second.data_maps);
|
|
|
|
if (input.verbose) {
|
|
dump_mesh(output.seamed_cut_mesh->mesh.get()[0], "cut-mesh-traced-poly");
|
|
}
|
|
}
|
|
}
|
|
} // if (input.include_seam_srcmesh) {
|
|
|
|
if (false == (input.keep_fragments_below_cutmesh || //
|
|
input.keep_fragments_above_cutmesh || //
|
|
input.keep_fragments_partially_cut || //
|
|
input.keep_unsealed_fragments || //
|
|
input.keep_fragments_sealed_inside || //
|
|
input.keep_fragments_sealed_outside || input.keep_fragments_sealed_inside_exhaustive || //
|
|
input.keep_fragments_sealed_outside_exhaustive || //
|
|
input.keep_inside_patches || //
|
|
input.keep_outside_patches)) {
|
|
// if the user simply wants seams, then we should not have to proceed further.
|
|
return;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Map each halfedge to the traced polygons that uses it
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
// We now need to manually maintain halfedge incidence (i.e. "used-by") information since
|
|
// the traced-polygon connectivity is not stored inside our halfedge mesh data structure.
|
|
// A halfedge data structure would normally store such incidence information for us but this is
|
|
// no longer possible for reasons mentioned above (see long comment after tracing loop).
|
|
//
|
|
// So the first incidence information that we need to keep around is the mapping from every
|
|
// halfedge (in "m0") which is used to trace a polygon, to the traced polygon(s) that uses
|
|
// that halfedge. Thus, halfedges which are not used for tracing [at all] have an entry in this
|
|
// vector but the value (std::vector) is empty. We will use this information later, like to
|
|
// stitch cut-mesh patches to src-mesh fragments.
|
|
|
|
TIMESTACK_PUSH("Map halfedges to polygons");
|
|
// std::map<
|
|
// hd_t, // a halfedge that is used to trace a polygon
|
|
// std::vector<int> // list of indices of traced polygons that are traced with the halfedge
|
|
// >
|
|
// m0_h_to_ply;
|
|
std::vector<std::vector<int>> m0_h_to_ply(m0.number_of_halfedges());
|
|
|
|
// for each traced polygon
|
|
for (std::vector<traced_polygon_t>::const_iterator traced_polygon_iter = m0_polygons.cbegin();
|
|
traced_polygon_iter != m0_polygons.cend();
|
|
++traced_polygon_iter) {
|
|
|
|
const traced_polygon_t& traced_polygon = *traced_polygon_iter;
|
|
const int traced_polygon_index = (int)std::distance(m0_polygons.cbegin(), traced_polygon_iter);
|
|
|
|
// for each halfedge in polygon
|
|
for (traced_polygon_t::const_iterator traced_polygon_halfedge_iter = traced_polygon.cbegin();
|
|
traced_polygon_halfedge_iter != traced_polygon.cend();
|
|
++traced_polygon_halfedge_iter) {
|
|
|
|
const hd_t& traced_polygon_halfedge = *traced_polygon_halfedge_iter;
|
|
#if 0
|
|
|
|
|
|
std::pair<std::map<hd_t, std::vector<int>>::iterator, bool> pair = m0_h_to_ply.insert(std::make_pair(traced_polygon_halfedge, std::vector<int>()));
|
|
|
|
if (pair.second == false) // element exists (m0 halfedges (only interior ihalfedges) can be reused by more than one polygon. upto two polygons!)
|
|
{
|
|
MCUT_ASSERT(!pair.first->second.empty());
|
|
MCUT_ASSERT(std::find(pair.first->second.cbegin(), pair.first->second.cend(), traced_polygon_index) == pair.first->second.cend());
|
|
}
|
|
|
|
pair.first->second.push_back(traced_polygon_index);
|
|
MCUT_ASSERT(pair.first->second.size() <= 2);
|
|
#else
|
|
SAFE_ACCESS(m0_h_to_ply, traced_polygon_halfedge).push_back(traced_polygon_index);
|
|
MCUT_ASSERT(SAFE_ACCESS(m0_h_to_ply, traced_polygon_halfedge).size() <= 2);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
// dump
|
|
for (std::map<hd_t, std::vector<int>>::const_iterator i = m0_h_to_ply.cbegin(); i != m0_h_to_ply.cend(); ++i) {
|
|
|
|
for (std::vector<int>::const_iterator j = i->second.cbegin(); j != i->second.cend(); ++j) {
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
#endif
|
|
|
|
TIMESTACK_POP();
|
|
|
|
// bool all_cutpaths_make_holes = ((int)explicit_cutpaths_making_holes.size() == num_explicit_cutpath_sequences);
|
|
|
|
TIMESTACK_PUSH("Find exterior cut-mesh polygons");
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Find all cut-mesh polygons which are "exterior" relative to the source-mesh
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
// Here we will explicitly find a subset of the traced cut-mesh polygons which lie
|
|
// outside/exterior w.r.t the source-mesh. We find these polygons using the
|
|
// "re-entrant" vertices that where identified while calculating intersection
|
|
// points. These will be used (later) to mark cut-mesh patches as either interior
|
|
// or exterior w.r.t the source-mesh.
|
|
//
|
|
// Note that these polygons will be the new "child polygons" which are new as a result of
|
|
// the intersections.
|
|
|
|
// An element here represents the index of an exterior cut-mesh polygon, and the index of
|
|
// halfedge which touches the source-mesh and points torward the interior (inside) of the src-mesh.
|
|
std::unordered_map<int /*m0 cs poly*/, int /*he idx*/> known_exterior_cm_polygons;
|
|
|
|
if (explicit_cutpaths_making_holes.size() > 0) { // atleast one cut-path makes a hole to be sealed later
|
|
|
|
// for each traced cut-mesh polygon
|
|
|
|
for (std::vector<int>::const_iterator cs_ipoly_iter = m0_cm_cutpath_adjacent_polygons.cbegin();
|
|
cs_ipoly_iter != m0_cm_cutpath_adjacent_polygons.cend();
|
|
++cs_ipoly_iter) {
|
|
// for (std::vector<traced_polygon_t>::const_iterator cs_poly_iter = traced_cs_polygons_iter_cbegin;
|
|
// cs_poly_iter != m0_polygons.cend();
|
|
// ++cs_poly_iter) {
|
|
|
|
// const traced_polygon_t& cs_poly = *cs_poly_iter;
|
|
// const int cs_poly_idx = (int)std::distance(m0_polygons.cbegin(), cs_poly_iter);
|
|
const int cs_poly_idx = *cs_ipoly_iter;
|
|
MCUT_ASSERT(cs_poly_idx < (int)m0_polygons.size());
|
|
|
|
if (known_exterior_cm_polygons.find(cs_poly_idx) != known_exterior_cm_polygons.cend()) {
|
|
continue; // we have already tagged the polygon as being exterior!
|
|
}
|
|
|
|
const traced_polygon_t& cs_poly = SAFE_ACCESS(m0_polygons, cs_poly_idx);
|
|
|
|
// for each halfedge of polygon
|
|
for (traced_polygon_t::const_iterator cs_poly_he_iter = cs_poly.cbegin();
|
|
cs_poly_he_iter != cs_poly.cend();
|
|
++cs_poly_he_iter) {
|
|
|
|
// we want to use class-1 ihalfedges : o-->x. This type of halfedge was the
|
|
// one used to calculate re-entrant vertices
|
|
const hd_t& cs_poly_he = *cs_poly_he_iter;
|
|
const vd_t cs_poly_he_src = m0.source(cs_poly_he);
|
|
const vd_t cs_poly_he_tgt = m0.target(cs_poly_he);
|
|
const bool tgt_is_ivertex = m0_is_intersection_point(cs_poly_he_tgt, ps_vtx_cnt);
|
|
const bool src_is_ivertex = m0_is_intersection_point(cs_poly_he_src, ps_vtx_cnt);
|
|
|
|
if (!tgt_is_ivertex) {
|
|
continue; // either class-0 or class-2
|
|
}
|
|
|
|
// check that the target vertex is along a cut-path making a hole
|
|
const int tgt_explicit_cutpath_sequence_idx = SAFE_ACCESS(m0_ivtx_to_cutpath_sequence, cs_poly_he_tgt);
|
|
bool cutpath_makes_a_hole = std::find(explicit_cutpaths_making_holes.cbegin(),
|
|
explicit_cutpaths_making_holes.cend(),
|
|
tgt_explicit_cutpath_sequence_idx)
|
|
!= explicit_cutpaths_making_holes.cend();
|
|
|
|
if (cutpath_makes_a_hole == false) {
|
|
// skip because the patch of the curent polygon will not be used
|
|
// for sealing/stitching holes. Thus, there is no need to tag the
|
|
// polygon as being either interior or exterior. That is, its
|
|
// adjacent cutpath does not make a hole!
|
|
continue;
|
|
}
|
|
|
|
// get the intersection info which was calculated earlier (src-mesh normal vector )
|
|
// NOTE: this is exactly the same numerical calculation that was computed previously.
|
|
// const std::map<vd_t, vec3>::const_iterator cs_nonborder_reentrant_ivertices_find_iter = cm_nonborder_reentrant_ivtx_list.find(cs_poly_he_tgt);
|
|
// const bool tgt_is_nonborder_reentrant_vertex = cs_nonborder_reentrant_ivertices_find_iter != cm_nonborder_reentrant_ivtx_list.cend();
|
|
// std::vector<vd_t>::const_iterator border_reentrant_vertex_find_iter = std::find(cm_border_reentrant_ivtx_list.cbegin(), cm_border_reentrant_ivtx_list.cend(), cs_poly_he_tgt);
|
|
// const bool tgt_is_border_reentrant_vertex = border_reentrant_vertex_find_iter != cm_border_reentrant_ivtx_list.cend();
|
|
|
|
// MCUT_ASSERT(!(tgt_is_nonborder_reentrant_vertex && tgt_is_border_reentrant_vertex)); // a re-entrant vertex cannot be both a border and norborder
|
|
|
|
// if (!tgt_is_nonborder_reentrant_vertex && !tgt_is_border_reentrant_vertex) {
|
|
// continue; // cs_poly_he_tgt is an ivertex but it is not a nonborder re-entrant vertex ( was not saved as one)
|
|
// }
|
|
|
|
// o-->x : We want the intersection halfedges which point "into" the source-mesh, i.e. whose tgt is on
|
|
// the source-mesh face of tgt (found in the registry entry). This implies that the current
|
|
// cut-mesh halfedge must have an opposite direction w.r.t the normal of the src-mesh face.
|
|
const bool is_ox = (!src_is_ivertex && tgt_is_ivertex);
|
|
bool is_boundary_ih = false; // i.e. is and intersecting halfedge
|
|
|
|
MCUT_ASSERT((size_t)cs_poly_he_tgt - ps_vtx_cnt < m0_ivtx_to_intersection_registry_entry.size() /* m0_ivtx_to_intersection_registry_entry.find(cs_poly_he_tgt) != m0_ivtx_to_intersection_registry_entry.cend()*/);
|
|
const std::pair<ed_t, fd_t>& cs_poly_he_tgt_ipair = SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, (std::size_t)cs_poly_he_tgt - ps_vtx_cnt);
|
|
|
|
if (src_is_ivertex && tgt_is_ivertex) {
|
|
// const hd_t src_coincident_ps_halfedge = SAFE_ACCESS(m0_ivtx_to_ps_edge, m0.source(cs_poly_he));
|
|
// const hd_t tgt_ps_h = SAFE_ACCESS(m0_ivtx_to_ps_edge, m0.target(cs_poly_he));
|
|
// const ed_t src_ps_edge = SAFE_ACCESS(m0_ivtx_to_ps_edge, m0.source(cs_poly_he)); // ps.edge(src_coincident_ps_halfedge);
|
|
// const ed_t tgt_ps_edge = SAFE_ACCESS(m0_ivtx_to_ps_edge, m0.target(cs_poly_he)); // ps.edge(tgt_ps_h);
|
|
|
|
MCUT_ASSERT((size_t)cs_poly_he_src - ps_vtx_cnt < m0_ivtx_to_intersection_registry_entry.size() /* m0_ivtx_to_intersection_registry_entry.find(cs_poly_he_src) != m0_ivtx_to_intersection_registry_entry.cend()*/);
|
|
const std::pair<ed_t, fd_t>& cs_poly_he_src_ipair = SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, (std::size_t)cs_poly_he_src - ps_vtx_cnt);
|
|
|
|
is_boundary_ih = (cs_poly_he_src_ipair.first == cs_poly_he_tgt_ipair.first);
|
|
}
|
|
|
|
if (!(is_ox || is_boundary_ih)) {
|
|
continue; // is interior-ihalfedge
|
|
}
|
|
|
|
// if tgt is a border re-entrant vertex then the polygon is (by definition) on the border
|
|
// given the properties of a border re-entrant vertex. Basically, this is due to the
|
|
// type of halfedge that we are looking for and the fact that border re-entrant vertices
|
|
// occur only on the border of the cut-mesh.
|
|
// bool is_border_polygon = tgt_is_border_reentrant_vertex;
|
|
|
|
// if (!is_border_polygon && tgt_is_nonborder_reentrant_vertex) {
|
|
// Re-calculate the geometry operation as as we did before we calculated the
|
|
// tgt-ivertex (i.e. with scalar product) using the halfedge's src and tgt
|
|
// coordinates and the normal of the face which was intersected to produce
|
|
// the tgt vertex.
|
|
// const vec3& polygon_normal = cs_nonborder_reentrant_ivertices_find_iter->second;
|
|
// MCUT_ASSERT(m0_ivtx_to_tested_polygon_normal.find(cs_poly_he_tgt) != m0_ivtx_to_tested_polygon_normal.cend());
|
|
|
|
// get the registry entry edge
|
|
|
|
// const std::vector<fd_t> set_edge0_v0_registry = ps_get_ivtx_registry_entry_faces(ps, set_edge0_v0_ipair);
|
|
// const ed_t& registry_entry_edge = cs_poly_he_tgt_ipair.first; // SAFE_ACCESS(m0_ivtx_to_ps_edge, cs_poly_he_tgt);
|
|
// get registry entry faces
|
|
|
|
// const std::vector<fd_t>& registry_entry_faces = SAFE_ACCESS(m0_ivtx_to_ps_faces, cs_poly_he_tgt);
|
|
const std::vector<fd_t> registry_entry_faces = ps_get_ivtx_registry_entry_faces(ps, cs_poly_he_tgt_ipair);
|
|
|
|
// get the registry-entry face which is not incident to edge
|
|
const vd_t registry_entry_edge_v0 = ps.vertex(cs_poly_he_tgt_ipair.first, 0);
|
|
std::vector<fd_t>::const_iterator tested_face; // which was intersected by "registry_entry_edge" to get "cs_poly_he_tgt"
|
|
bool registry_entry_edge_is_from_cutmesh = ps_is_cutmesh_vertex(registry_entry_edge_v0, sm_vtx_cnt);
|
|
if (registry_entry_edge_is_from_cutmesh) {
|
|
// ... then intersected face was from the source mesh
|
|
tested_face = std::find_if(registry_entry_faces.cbegin(), registry_entry_faces.cend(),
|
|
[&](const fd_t& f) {
|
|
bool is_sm_face = !ps_is_cutmesh_face(f, sm_face_count) && f != hmesh_t::null_face();
|
|
return is_sm_face;
|
|
});
|
|
} else {
|
|
// ... then intersected face was from the cut mesh
|
|
tested_face = std::find_if(registry_entry_faces.cbegin(), registry_entry_faces.cend(),
|
|
[&](const fd_t& f) {
|
|
bool is_cm_face = ps_is_cutmesh_face(f, sm_face_count) && f != hmesh_t::null_face();
|
|
return is_cm_face;
|
|
});
|
|
}
|
|
|
|
MCUT_ASSERT(tested_face != registry_entry_faces.cend()); // "registry_entry_faces" must have at least one face from cm and at least one from sm
|
|
MCUT_ASSERT(ps_tested_face_to_plane_normal.find(*tested_face) != ps_tested_face_to_plane_normal.cend());
|
|
|
|
// get normal of face
|
|
const vec3& polygon_normal = SAFE_ACCESS(ps_tested_face_to_plane_normal, *tested_face); // SAFE_ACCESS(m0_ivtx_to_tested_polygon_normal, cs_poly_he_tgt);
|
|
// const vec3& polygon_normal = geometric_data.first; // source-mesh face normal
|
|
// const double& orig_scalar_prod = geometric_data.second; // the dot product result we computed earlier
|
|
|
|
// MCUT_ASSERT(sign(orig_scalar_prod) == NEGATIVE);
|
|
|
|
// calculate the vector represented by the current halfedge
|
|
const vec3 cs_poly_he_vector = m0.vertex(cs_poly_he_tgt) - m0.vertex(cs_poly_he_src);
|
|
// calculate dot product with the src-mesh normal
|
|
const double scalar_prod = dot_product(polygon_normal, cs_poly_he_vector);
|
|
// the original ps-halfedge was "incoming" (pointing inwards) and gave a
|
|
// negative scalar-product with the src-mesh face normal.
|
|
// check that it is the same
|
|
// Note: we want the same sign (i.e. cs_poly_he_vector has negative scalar-product)
|
|
// because we want the class-1 ihalfedge which is exterior but points inside the src-mesh
|
|
// is_border_polygon = (sign(scalar_prod) == NEGATIVE);
|
|
//}
|
|
|
|
if (sign(scalar_prod) == NEGATIVE) { // the current halfedge passed the sign test
|
|
MCUT_ASSERT(known_exterior_cm_polygons.find(cs_poly_idx) == known_exterior_cm_polygons.cend());
|
|
known_exterior_cm_polygons[cs_poly_idx] = (int)std::distance(cs_poly.cbegin(), cs_poly_he_iter);
|
|
break; // done, we now know "cs_poly_idx" as an exterior polygon
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// cm_nonborder_reentrant_ivtx_list.clear(); // free
|
|
m0_ivtx_to_cutpath_sequence.clear(); // free
|
|
|
|
TIMESTACK_POP();
|
|
|
|
TIMESTACK_PUSH("Find source mesh polygon above and below cm");
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Find the source-mesh polygons (next to cutpath) which are above and below
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
// We are searching through all of the traced source-mesh polygons to find those
|
|
// which are adjacent to the cut path. We then identify them as being either
|
|
// "above" or "below" the cut-mesh which we do using the source-mesh re-entrant
|
|
// vertices.
|
|
//
|
|
|
|
std::vector<int> sm_polygons_below_cs;
|
|
std::vector<int> sm_polygons_above_cs;
|
|
|
|
// for each traced source-mesh polygon along cutpath
|
|
for (std::vector<int>::const_iterator sm_ipoly_iter = m0_sm_cutpath_adjacent_polygons.cbegin();
|
|
sm_ipoly_iter != m0_sm_cutpath_adjacent_polygons.cend();
|
|
++sm_ipoly_iter) {
|
|
// for (std::vector<traced_polygon_t>::const_iterator sm_poly_iter = m0_polygons.cbegin();
|
|
// sm_poly_iter != traced_sm_polygons_iter_end;
|
|
// ++sm_poly_iter) {
|
|
// const traced_polygon_t& sm_poly = *sm_poly_iter;
|
|
// const int sm_poly_idx = (int)std::distance(m0_polygons.cbegin(), sm_poly_iter);
|
|
const int sm_poly_idx = *sm_ipoly_iter;
|
|
MCUT_ASSERT(sm_poly_idx < (int)m0_polygons.size());
|
|
const traced_polygon_t& sm_poly = SAFE_ACCESS(m0_polygons, sm_poly_idx);
|
|
|
|
// for each halfedge of polygon
|
|
for (traced_polygon_t::const_iterator sm_poly_he_iter = sm_poly.cbegin();
|
|
sm_poly_he_iter != sm_poly.cend();
|
|
++sm_poly_he_iter) {
|
|
|
|
const hd_t& sm_poly_he = *sm_poly_he_iter; // we want class-1 ihalfedges : o-->x
|
|
const vd_t sm_poly_he_src = m0.source(sm_poly_he);
|
|
const vd_t sm_poly_he_tgt = m0.target(sm_poly_he);
|
|
const bool tgt_is_ivertex = m0_is_intersection_point(sm_poly_he_tgt, ps_vtx_cnt);
|
|
const bool src_is_ivertex = m0_is_intersection_point(sm_poly_he_src, ps_vtx_cnt);
|
|
|
|
if (!tgt_is_ivertex) {
|
|
continue; // either class-0 (o-->o) or class-2 (x-->o)
|
|
}
|
|
|
|
// const std::map<vd_t, vec3>::const_iterator sm_nonborder_reentrant_ivertices_find_iter = sm_nonborder_reentrant_ivtx_list.find(sm_poly_he_tgt);
|
|
// const bool tgt_is_sm_nonborder_reentrant_vertex = sm_nonborder_reentrant_ivertices_find_iter != sm_nonborder_reentrant_ivtx_list.cend();
|
|
|
|
// NOTE: we do not need source-mesh border re-entrant vertices because they are not useful for the
|
|
// determining whether traced source-mesh polygons are either "above" or "below" the cut-mesh. The notion
|
|
// of above or below is defined only for source-mesh fragements w.r.t. the cut-mesh. The is because
|
|
// we are only interested in partitioning the source-mesh and not the cut-mesh
|
|
//
|
|
|
|
// if (!tgt_is_sm_nonborder_reentrant_vertex) {
|
|
// continue; // cs_poly_he_tgt is an ivertex but it is not a regular re-entrant vertex
|
|
// }
|
|
|
|
// o-->x : We want the intersection halfedges which point into the cut-mesh and whose tgt lays on the cut-mesh face of tgt
|
|
// (they have an opposite direction wrt the face normal)
|
|
const bool is_ox = (!src_is_ivertex && tgt_is_ivertex);
|
|
|
|
const bool is_boundary_halfedge = m0_is_polygon_boundary_halfedge(sm_poly_he, m0_num_cutpath_halfedges);
|
|
bool is_boundary_ih = is_boundary_halfedge;
|
|
|
|
// const hd_t src_coincident_ps_halfedge = SAFE_ACCESS(m0_ivtx_to_ps_edge, sm_poly_he_src);
|
|
// const hd_t tgt_ps_h = SAFE_ACCESS(m0_ivtx_to_ps_edge, sm_poly_he_tgt);
|
|
|
|
// const ed_t tgt_ps_edge = SAFE_ACCESS(m0_ivtx_to_ps_edge, sm_poly_he_tgt); //ps.edge(tgt_ps_h);
|
|
MCUT_ASSERT((size_t)sm_poly_he_tgt - ps_vtx_cnt < m0_ivtx_to_intersection_registry_entry.size() /*m0_ivtx_to_intersection_registry_entry.find(sm_poly_he_tgt) != m0_ivtx_to_intersection_registry_entry.cend()*/);
|
|
const std::pair<ed_t, fd_t>& sm_poly_he_tgt_ipair = SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, (std::size_t)sm_poly_he_tgt - ps_vtx_cnt);
|
|
const ed_t& tgt_ps_edge = sm_poly_he_tgt_ipair.first;
|
|
#if 0
|
|
if (src_is_ivertex && tgt_is_ivertex)
|
|
{
|
|
MCUT_ASSERT((size_t)sm_poly_he_src - ps_vtx_cnt < m0_ivtx_to_intersection_registry_entry.size() /*m0_ivtx_to_intersection_registry_entry.find(sm_poly_he_src) != m0_ivtx_to_intersection_registry_entry.cend()*/);
|
|
const std::pair<ed_t, fd_t> &sm_poly_he_src_ipair = SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, sm_poly_he_src - ps_vtx_cnt);
|
|
const ed_t &src_ps_edge = sm_poly_he_src_ipair.first; // SAFE_ACCESS(m0_ivtx_to_ps_edge, sm_poly_he_src); //ps.edge(src_coincident_ps_halfedge);
|
|
|
|
is_boundary_ih = (src_ps_edge == tgt_ps_edge);
|
|
}
|
|
#endif
|
|
if (!(is_ox || is_boundary_ih)) {
|
|
continue;
|
|
}
|
|
|
|
// Re-calculate the exact same geometry operation as the time we calculated
|
|
// the tgt-ivertex (scalar product using the halfedge's src and tgt coordinates
|
|
// and and the normal of the cut-mesh face that was intersected to produce
|
|
// the tgt vertex).
|
|
// MCUT_ASSERT(m0_ivtx_to_tested_polygon_normal.find(sm_poly_he_tgt) != m0_ivtx_to_tested_polygon_normal.cend());
|
|
// get the registry entry edge
|
|
|
|
const ed_t& registry_entry_edge = tgt_ps_edge; // SAFE_ACCESS(m0_ivtx_to_ps_edge, sm_poly_he_tgt);
|
|
// get registry entry faces
|
|
const std::vector<fd_t> registry_entry_faces = ps_get_ivtx_registry_entry_faces(ps, sm_poly_he_tgt_ipair); // SAFE_ACCESS(m0_ivtx_to_ps_faces, sm_poly_he_tgt);
|
|
// get the registry-entry face which is not incident to edge
|
|
const vd_t registry_entry_edge_v0 = ps.vertex(registry_entry_edge, 0);
|
|
std::vector<fd_t>::const_iterator tested_face; // which was intersected by "registry_entry_edge" to get "cs_poly_he_tgt"
|
|
bool registry_entry_edge_is_from_cutmesh = ps_is_cutmesh_vertex(registry_entry_edge_v0, sm_vtx_cnt);
|
|
if (registry_entry_edge_is_from_cutmesh) {
|
|
// ... then intersected face was from the source mesh
|
|
tested_face = std::find_if(registry_entry_faces.cbegin(), registry_entry_faces.cend(),
|
|
[&](const fd_t& f) {
|
|
bool is_sm_face = !ps_is_cutmesh_face(f, sm_face_count) && f != hmesh_t::null_face();
|
|
return is_sm_face;
|
|
});
|
|
} else {
|
|
// ... then intersected face was from the cut mesh
|
|
tested_face = std::find_if(registry_entry_faces.cbegin(), registry_entry_faces.cend(),
|
|
[&](const fd_t& f) {
|
|
bool is_cm_face = ps_is_cutmesh_face(f, sm_face_count) && f != hmesh_t::null_face();
|
|
return is_cm_face;
|
|
});
|
|
}
|
|
|
|
MCUT_ASSERT(tested_face != registry_entry_faces.cend()); // "registry_entry_faces" must have at least one face from cm and at least one from sm
|
|
MCUT_ASSERT(ps_tested_face_to_plane_normal.find(*tested_face) != ps_tested_face_to_plane_normal.cend());
|
|
|
|
// get normal of face
|
|
const vec3& polygon_normal = SAFE_ACCESS(ps_tested_face_to_plane_normal, *tested_face);
|
|
// const vec3& polygon_normal = SAFE_ACCESS(m0_ivtx_to_tested_polygon_normal, sm_poly_he_tgt);
|
|
// const vec3& polygon_normal = geometric_data.first;
|
|
// const double& orig_scalar_prod = geometric_data.second;
|
|
|
|
// MCUT_ASSERT(sign(orig_scalar_prod) == NEGATIVE);
|
|
|
|
const vec3 sm_poly_he_vector = m0.vertex(sm_poly_he_tgt) - m0.vertex(sm_poly_he_src);
|
|
const double scalar_prod = dot_product(polygon_normal, sm_poly_he_vector);
|
|
|
|
// Again, the notion of exterior is denoted by a negative dot-product.
|
|
// Original ps-halfedge was "incoming" and gave a negative scalar-product
|
|
// with the cut-mesh face normal.
|
|
//
|
|
// We want the same sign (i.e. cs_poly_he_vector has negative scalar-product) because we want
|
|
// the class-1 ihalfedge which is exterior but points "inside" the cut-mesh (i.e. torward
|
|
// the negative side)
|
|
if (sign(scalar_prod) == NEGATIVE) {
|
|
|
|
// At this point, we have found our class-1 (or class 3, x-->x) source-mesh halfedge
|
|
// from which we can infer whether the current polygon is "above" (outside) or
|
|
// "below" (inside) the cut-mesh.
|
|
// Also, by using the traced halfedge connectivity, we can determine the adjacent polygon
|
|
// which shares an edge with the curent polygon. This shared edge is the edge of the
|
|
// "next" halfedge (of the current halfedge) and it is always an interior edge.
|
|
|
|
// Check if the current polygon is already found to be "above" the cut-mesh
|
|
const bool cur_poly_already_marked_as_above = std::find(sm_polygons_above_cs.cbegin(), sm_polygons_above_cs.cend(), sm_poly_idx) != sm_polygons_above_cs.cend();
|
|
|
|
if (!cur_poly_already_marked_as_above) {
|
|
sm_polygons_above_cs.push_back(sm_poly_idx);
|
|
}
|
|
|
|
// Here we can conviniently find and save the the neighbouring polygon that is on the
|
|
// other side i.e. "below" the cut-mesh. This is made possible because we can easily
|
|
// search through the halfedge connectivity.
|
|
|
|
// index of current halfedge in the current polygon
|
|
const int sm_poly_he_idx = (int)std::distance(sm_poly.cbegin(), sm_poly_he_iter);
|
|
// index of the "next" halfedge in the current polygon
|
|
const int sm_poly_next_he_idx = wrap_integer(sm_poly_he_idx + 1, 0, (int)sm_poly.size() - 1);
|
|
// the handle of the next halfedge in the current polygon
|
|
const hd_t& sm_poly_next_he = SAFE_ACCESS(sm_poly, sm_poly_next_he_idx);
|
|
// now we query the handle of the opposite-halfedge of the next-halfedge.
|
|
// This is facilitated by the incidence information that is maintained inside
|
|
// our halfedge data structure "m0" which stores our vertices (including intersection
|
|
// points) and edges that we calculated in earlier stages of the pipeline).
|
|
const hd_t opp_of_sm_poly_next_he = m0.opposite(sm_poly_next_he);
|
|
// using our halfedge-to-traced-polygon map, we then get the polygon index of the
|
|
// opposite-halfedge
|
|
const std::vector<int>& coincident_polys = SAFE_ACCESS(m0_h_to_ply, opp_of_sm_poly_next_he); // coincident polygons (one cs and one sm)
|
|
const std::vector<int>::const_iterator find_iter = std::find_if(
|
|
coincident_polys.cbegin(), coincident_polys.cend(),
|
|
[&](const int& e) { return (e < traced_sm_polygon_count); });
|
|
|
|
// must always exist since "opp_of_sm_poly_next_he" is an interior ihalfedge
|
|
MCUT_ASSERT(find_iter != coincident_polys.cend());
|
|
|
|
// we have found the other source-mesh polygon which is "below" (inside) the cut-mesh
|
|
const int coincident_sm_poly_idx = *find_iter;
|
|
const bool neigh_poly_already_marked_as_below = std::find(sm_polygons_below_cs.cbegin(), sm_polygons_below_cs.cend(), sm_poly_idx) != sm_polygons_below_cs.cend();
|
|
|
|
if (!neigh_poly_already_marked_as_below) {
|
|
sm_polygons_below_cs.push_back(coincident_sm_poly_idx);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// sort that we can do binary search over it
|
|
std::sort(sm_polygons_below_cs.begin(), sm_polygons_below_cs.end());
|
|
std::sort(sm_polygons_above_cs.begin(), sm_polygons_above_cs.end());
|
|
|
|
TIMESTACK_POP();
|
|
|
|
// sm_nonborder_reentrant_ivtx_list.clear();
|
|
|
|
// Here, we check for the unique case in which we could not find any traced source-mesh
|
|
// polygons along the cut path which could be identified as either "above" (outside)
|
|
// or "below" (inside).
|
|
// Such a situation is rare and happens when the source-mesh has one face where the
|
|
// intersection with the cut-mesh is a partial cut
|
|
|
|
// if (sm_polygons_above_cs.empty() && sm_polygons_below_cs.empty()) {
|
|
// MCUT_ASSERT(sm_face_count == 1);
|
|
// sm_polygons_above_cs.push_back(0); // sm polygons are stored first theirfore sm polygon will ccse first (see "ps" definition)
|
|
// sm_polygons_below_cs.push_back(0);
|
|
// }
|
|
|
|
TIMESTACK_PUSH("Map source mesh ihalfedges to bool");
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Map source-mesh intersection halfedges to a boolean value
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
// Here we will map every source-mesh halfedge connected to an intersection point to a boolean.
|
|
// This boolean value indicates if the halfedge has been `transformed`. The notion
|
|
// of "transformation" is used to indicate whether a halfedge has been "processed"
|
|
// to assign it to a distinct fragment connected component of the source-mesh.
|
|
//
|
|
// We call a halfedge connected to at-least one intersection point note an "intersection
|
|
// halfedge"
|
|
//
|
|
|
|
std::unordered_map<
|
|
hd_t, // intersection halfedge which is used for tracing
|
|
bool // flag for indicating if halfedge has been transformed
|
|
>
|
|
m0_sm_ihe_to_flag;
|
|
|
|
// for (edge_array_iterator_t edge_iter = m0.edges_begin(); edge_iter != m0.edges_end(); ++edge_iter) {
|
|
for (std::unordered_map<vd_t, std::vector<hd_t>>::const_iterator ivtx_iter = ivtx_to_incoming_hlist.cbegin(); ivtx_iter != ivtx_to_incoming_hlist.cend(); ++ivtx_iter) {
|
|
for (std::vector<hd_t>::const_iterator halfedge_iter = ivtx_iter->second.cbegin(); halfedge_iter != ivtx_iter->second.cend(); ++halfedge_iter) {
|
|
// const ed_t& edge = (*edge_iter);
|
|
const ed_t& edge = m0.edge(*halfedge_iter);
|
|
const vd_t v0 = m0.vertex(edge, 0);
|
|
const vd_t v1 = m0.vertex(edge, 1);
|
|
|
|
const bool v0_is_ivtx = m0_is_intersection_point(v0, ps_vtx_cnt);
|
|
const bool v1_is_ivtx = m0_is_intersection_point(v1, ps_vtx_cnt);
|
|
|
|
if (!v0_is_ivtx && !v1_is_ivtx) { // o-->o
|
|
// we only want halfedges with an intersection point
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// check if current edge is a cut-mesh edge
|
|
//
|
|
|
|
if (v0_is_ivtx && !v1_is_ivtx) { // x-->o
|
|
|
|
// get the polygon-soup version of tgt descriptor
|
|
// std::map<vd_t, vd_t>::const_iterator m0_to_ps_vtx_find_v1_iter = std::find_if(
|
|
// m0_to_ps_vtx.cbegin(), m0_to_ps_vtx.cend(),
|
|
// [&](const std::pair<vd_t, vd_t> &e) { return e.first == v1; });
|
|
|
|
MCUT_ASSERT((int)v1 < (int)m0_to_ps_vtx.size() /*m0_to_ps_vtx_find_v1_iter != m0_to_ps_vtx.cend()*/);
|
|
|
|
const vd_t& ps_v1 = SAFE_ACCESS(m0_to_ps_vtx, v1); // m0_to_ps_vtx_find_v1_iter->second;
|
|
|
|
if (ps_is_cutmesh_vertex(ps_v1, sm_vtx_cnt)) { // is it a cut-mesh vertex..?
|
|
// we want only source-mesh edges
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (!v0_is_ivtx && v1_is_ivtx) { // o-->x
|
|
// std::map<vd_t, vd_t>::const_iterator m0_to_ps_vtx_find_v0_iter = std::find_if(
|
|
// m0_to_ps_vtx.cbegin(), m0_to_ps_vtx.cend(),
|
|
// [&](const std::pair<vd_t, vd_t> &e) { return e.first == v0; });
|
|
|
|
MCUT_ASSERT((size_t)v0 < m0_to_ps_vtx.size()); // m0_to_ps_vtx_find_v0_iter != m0_to_ps_vtx.cend());
|
|
|
|
const vd_t& ps_v0 = SAFE_ACCESS(m0_to_ps_vtx, v0); // m0_to_ps_vtx_find_v0_iter->second;
|
|
|
|
if (ps_is_cutmesh_vertex(ps_v0, sm_vtx_cnt)) {
|
|
continue; // is a cut-mesh edge
|
|
}
|
|
}
|
|
|
|
// TODO: we also need to check for cut-mesh edges of the form x-->x [but only the polygon boundary type]
|
|
// At the moment, "m0_sm_ihe_to_flag" will also include those cut-mesh halfedges!
|
|
//
|
|
// ** I'm not convinced that this is a problem
|
|
|
|
#if 0
|
|
bool is_ambiguious_boundary_edge_case = v0_is_ivtx && v1_is_ivtx;
|
|
|
|
if (is_ambiguious_boundary_edge_case) { // exterior edge with two intersection vertices (ambigious case arising from concave polyhedron cut)
|
|
|
|
const hd_t v0_coincident_ps_halfedge = SAFE_ACCESS(m0_ivtx_to_ps_edge, v0);
|
|
const hd_t v1_coincident_ps_halfedge = SAFE_ACCESS(m0_ivtx_to_ps_edge, v1);
|
|
const ed_t v0_ps_edge = ps.edge(v0_coincident_ps_halfedge);
|
|
const ed_t v1_ps_edge = ps.edge(v1_coincident_ps_halfedge);
|
|
bool is_valid_ambiguious_boundary_edge = (v0_ps_edge == v1_ps_edge); // see also above when gathering exterior incident edges
|
|
if (is_valid_ambiguious_boundary_edge && !cm_is_watertight) // x-->x where o-->x-->x-->o
|
|
{
|
|
// Exterior ihalfedges (and hence their respective halfedges) are not transformed.
|
|
// Only interior ihalfedges need to be transformed to create incisions that allow openings of the sm via transformations.
|
|
// NOTE: when cs is watertight we still include polygon-exterior interior-ihalfedge because they a needed to "bite" a chuck out of the cs (see example 19)
|
|
continue;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// save the halfegdes of the current edge (if they are used to trace a polygon)
|
|
//
|
|
|
|
const hd_t h0 = m0.halfedge(edge, 0);
|
|
|
|
if (SAFE_ACCESS(m0_h_to_ply, h0).size() > 0 /*m0_h_to_ply.find(h0) != m0_h_to_ply.end()*/ && m0_sm_ihe_to_flag.find(h0) == m0_sm_ihe_to_flag.cend()) { // check if used to trace polygon
|
|
MCUT_ASSERT(m0_sm_ihe_to_flag.count(h0) == 0);
|
|
m0_sm_ihe_to_flag[h0] = false;
|
|
// std::pair<std::map<hd_t, bool>::const_iterator, bool> pair0 = m0_sm_ihe_to_flag.insert(std::make_pair(h0, false));
|
|
// MCUT_ASSERT(pair0.second == true);
|
|
MCUT_ASSERT(m0_sm_ihe_to_flag.count(h0) == 1);
|
|
}
|
|
|
|
const hd_t h1 = m0.halfedge(edge, 1);
|
|
|
|
if (SAFE_ACCESS(m0_h_to_ply, h1).size() > 0 /*m0_h_to_ply.find(h1) != m0_h_to_ply.end()*/ && m0_sm_ihe_to_flag.find(h1) == m0_sm_ihe_to_flag.cend()) { // check id used to trace polygon
|
|
MCUT_ASSERT(m0_sm_ihe_to_flag.count(h1) == 0);
|
|
m0_sm_ihe_to_flag[h1] = false;
|
|
// std::pair<std::map<hd_t, bool>::const_iterator, bool> pair1 = m0_sm_ihe_to_flag.insert(std::make_pair(h1, false));
|
|
// MCUT_ASSERT(pair1.second == true);
|
|
MCUT_ASSERT(m0_sm_ihe_to_flag.count(h1) == 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
TIMESTACK_POP();
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// create the second auxilliary halfedge data structure ("m1")
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
TIMESTACK_PUSH("Create m1");
|
|
//
|
|
// At this point, we create another auxilliary halfedge data structure called "m1".
|
|
// It will store the vertices and edges like "m0" but will also include the
|
|
// duplicate copy of (most/all) intersection points, as well as some new edges. The new
|
|
// edges are created to partition/separate the source-mesh as we process intersection
|
|
// halfedges by assigning them the correct copy of dupicated intersection
|
|
// points. Thus along the cut path, we will create new connectivity that
|
|
// allows us to partition the source-mesh along this path.
|
|
//
|
|
|
|
// store's the (unsealed) connected components (fragments of the source-mesh)
|
|
hmesh_t m1;
|
|
m1.reserve_for_additional_elements(m0.number_of_vertices() + m0.number_of_vertices() * 0.25);
|
|
// copy vertices from m0 t0 m1 (and save mapping to avoid assumptions).
|
|
// This map DOES NOT include patch intersection points because they are new
|
|
// i.e. we keep only the points represent original vertices in the source-mesh
|
|
// and cut-mesh (used as data for client tex coord mapping usage)
|
|
std::vector<vd_t> m0_to_m1_vtx(m0.number_of_vertices());
|
|
std::vector<vd_t> m1_to_m0_ovtx(m0.number_of_vertices());
|
|
for (vertex_array_iterator_t v = m0.vertices_begin(); v != m0.vertices_end(); ++v) {
|
|
const vd_t m1_vd = m1.add_vertex(m0.vertex(*v));
|
|
MCUT_ASSERT(m1_vd != hmesh_t::null_vertex());
|
|
|
|
MCUT_ASSERT((size_t)(*v) < m0_to_m1_vtx.size() /*m0_to_m1_vtx.count(*v) == 0*/ && (size_t)m1_vd < m1_to_m0_ovtx.size() /* m1_to_m0_ovtx.count(m1_vd) == 0*/);
|
|
// std::pair<std::map<vd_t, vd_t>::const_iterator, bool> pair = m0_to_m1_vtx.insert(std::make_pair(*v, m1_vd));
|
|
// MCUT_ASSERT(pair.second == true);
|
|
m0_to_m1_vtx[*v] = m1_vd;
|
|
m1_to_m0_ovtx[m1_vd] = *v;
|
|
// MCUT_ASSERT(m0_to_m1_vtx.count(*v) == 1);
|
|
}
|
|
|
|
MCUT_ASSERT(m1.number_of_vertices() == m0.number_of_vertices());
|
|
|
|
TIMESTACK_POP();
|
|
|
|
TIMESTACK_PUSH("Map m0 to m1 halfedges");
|
|
|
|
// copy m0 edges and halfedges [which are not intersection-halfedges] and
|
|
// build a mapping between m0 and m1. This mapping is needed because as we
|
|
// begin to transform halfedges incident to the cut-path, some of their opposites
|
|
// will become invalidated. This is because for each interior edge we will
|
|
// essentially create a new edge.
|
|
// We must also relate halfedges (in "m1") to their opposites explicitly (essentially
|
|
// copying the information already stored in "m0"). Because this information will be
|
|
// lost after duplicating intersection points and transforming all halfedges
|
|
// along the cut-path.
|
|
|
|
std::unordered_map<hd_t, hd_t> m0_to_m1_he;
|
|
m0_to_m1_he.reserve(m0.number_of_edges() * 2);
|
|
|
|
for (edge_array_iterator_t e = m0.edges_begin(); e != m0.edges_end(); ++e) {
|
|
const ed_t& m0_edge = (*e);
|
|
const vd_t m0_v0 = m0.vertex(m0_edge, 0);
|
|
const vd_t m0_v1 = m0.vertex(m0_edge, 1);
|
|
// const bool m0_v0_is_ivtx = m0_is_intersection_point(m0_v0, ps_vtx_cnt);
|
|
// const bool m0_v1_is_ivtx = m0_is_intersection_point(m0_v1, ps_vtx_cnt);
|
|
|
|
if (!(m0_is_intersection_point(m0_v0, ps_vtx_cnt) || m0_is_intersection_point(m0_v1, ps_vtx_cnt))) { // not coincident to an intersection vertex (i.e. is class-0 edge)
|
|
|
|
const vd_t m1_v0 = SAFE_ACCESS(m0_to_m1_vtx, m0_v0);
|
|
const vd_t m1_v1 = SAFE_ACCESS(m0_to_m1_vtx, m0_v1);
|
|
const hd_t m1_halfedge = m1.add_edge(m1_v0, m1_v1); // add m1
|
|
const hd_t m0_h0 = m0.halfedge(m0_edge, 0);
|
|
const vd_t m0_h0_src = m0.source(m0_h0);
|
|
const hd_t m0_h1 = m0.halfedge(m0_edge, 1);
|
|
const vd_t m1_halfedge_src = m1.source(m1_halfedge);
|
|
const vd_t m1_halfedge_tgt = m1.target(m1_halfedge);
|
|
|
|
if (SAFE_ACCESS(m0_to_m1_vtx, m0_h0_src) == m1_halfedge_src) { // i.e. "is the m0_h0 equivalent to m1_halfedge?"
|
|
m0_to_m1_he.insert(std::make_pair(m0_h0, m1_halfedge));
|
|
m0_to_m1_he.insert(std::make_pair(m0_h1, m1.opposite(m1_halfedge)));
|
|
} else {
|
|
m0_to_m1_he.insert(std::make_pair(m0_h1, m1_halfedge));
|
|
m0_to_m1_he.insert(std::make_pair(m0_h0, m1.opposite(m1_halfedge)));
|
|
}
|
|
}
|
|
}
|
|
|
|
TIMESTACK_POP();
|
|
|
|
TIMESTACK_PUSH("m0 source mesh set next");
|
|
//
|
|
// For each src-mesh halfedge we store "next-halfedge" state for quick-lookup in "m0".
|
|
// We store this information in "m0" because it allows for a more expedient state-lookup during
|
|
// connected-component re-assignment.
|
|
//
|
|
// Note: this is only made possible because 1) from this point onwards "m0" will not be modified
|
|
// at all, and 2) we can safely assume that its okay to store "next-halfedge" state without `technically`
|
|
// violating 2-manifold rules since this information is only for the src-mesh polygons.
|
|
//
|
|
// We will ultimately use this saved state to extract the intersection-halfedges which are coincident
|
|
// to intersection-vertices during connected-component re-assignment.
|
|
//
|
|
|
|
// for each source-mesh polygon
|
|
for (std::vector<traced_polygon_t>::const_iterator traced_sm_polygon_iter = m0_polygons.cbegin();
|
|
traced_sm_polygon_iter != m0_traced_sm_polygons_iter_cend;
|
|
++traced_sm_polygon_iter) {
|
|
const traced_polygon_t& traced_sm_polygon = *traced_sm_polygon_iter;
|
|
|
|
// for each halfedge of polygon
|
|
for (traced_polygon_t::const_iterator traced_sm_polygon_halfedge_iter = traced_sm_polygon.cbegin();
|
|
traced_sm_polygon_halfedge_iter != traced_sm_polygon.cend();
|
|
++traced_sm_polygon_halfedge_iter) {
|
|
const int i = (int)std::distance(traced_sm_polygon.cbegin(), traced_sm_polygon_halfedge_iter);
|
|
|
|
const hd_t& cur = SAFE_ACCESS(traced_sm_polygon, i);
|
|
const hd_t& next = SAFE_ACCESS(traced_sm_polygon, ((size_t)i + 1) % traced_sm_polygon.size());
|
|
m0.set_next(cur, next); // update state
|
|
}
|
|
}
|
|
|
|
TIMESTACK_POP();
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// source-mesh partitioning
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
TIMESTACK_PUSH("Source mesh partitioning"); // &&&&&
|
|
|
|
//
|
|
// Here we partition the traced source-mesh polygons into disjoint connected components
|
|
// by circulating around each intersection point and creating a copy for
|
|
// each distinct connected component seen. In the case of a complete (through) cut,
|
|
// all intersection points are duplicated. However, in the case of a partial
|
|
// cut, we only duplicate the intersection points which are along the cut path
|
|
// but exclude those which are the terminal vertices of a sequence.
|
|
// A sequence here is an ordered list of edges passing through intersection
|
|
// points (along the cut path). In the case of a partial cut, a sequence
|
|
// does not form a loop.
|
|
//
|
|
|
|
/*
|
|
Goal:
|
|
Assign the correct intersection point instance (descriptor) to each intersection halfedge.
|
|
|
|
For reference there following are the types of halfedges we have
|
|
|
|
class : instance : definition (what will happen)
|
|
|
|
0 : o-->o : Exterior/Boundary (nothing, we already copied this type of edge into m1)
|
|
1 : o-->x : Exterior/Boundary (tgt may be assigned duplicate instance of descriptor)
|
|
2 : x-->o : Exterior/Boundary (src may be assigned duplicate instance of descriptor)
|
|
3 : x-->x : Interior OR Exterior/Boundary (tgt and src may be assigned duplicate instance of descriptor)
|
|
|
|
o - original-vertex
|
|
x - intersection-vertex/point
|
|
*/
|
|
|
|
// This data structure will map the descriptors of intersection-halfedges in "m0"
|
|
// to their descriptor in "m1". Thus, some halfedges (in "m0") will be mapped to
|
|
// new halfedges which are not in "m0" but will be added into "m1".
|
|
std::unordered_map<
|
|
hd_t, // "m0" halfedge
|
|
hd_t // "m1" version
|
|
>
|
|
m0_to_m1_ihe;
|
|
|
|
// lamda checks the intersection halfedge that has not been transformed/processed already
|
|
std::function<bool(const std::pair<hd_t, bool>&)> check_if_halfedge_is_transformed = [&](const std::pair<hd_t, bool>& e) {
|
|
const hd_t& m0_ihe = e.first;
|
|
const vd_t m0_ihe_src_vertex = m0.source(m0_ihe);
|
|
const bool src_is_ivertex = m0_is_intersection_point(m0_ihe_src_vertex, ps_vtx_cnt);
|
|
|
|
if (src_is_ivertex) {
|
|
return false; // has to be original vertex
|
|
}
|
|
|
|
const bool is_transformed = e.second;
|
|
|
|
if (is_transformed) {
|
|
return false; // cannot have been transformed already.
|
|
}
|
|
|
|
const vd_t& m0_ihe_tgt_vertex = m0.target(m0_ihe);
|
|
const bool tgt_is_ivertex = m0_is_intersection_point(m0_ihe_tgt_vertex, ps_vtx_cnt);
|
|
|
|
if (tgt_is_ivertex) { // tgt is an intersection point
|
|
|
|
// is the current halfedge used to traced a polygon (i.e. those we stored in "m0")
|
|
const bool is_incident_to_traced_polygon = SAFE_ACCESS(m0_h_to_ply, m0_ihe).size() > 0 /*m0_h_to_ply.find(m0_ihe) != m0_h_to_ply.end()*/;
|
|
|
|
if (is_incident_to_traced_polygon) {
|
|
//
|
|
// We now need to make sure that the preceeding (prevous) halfedge of the
|
|
// current (in its polygon) is class0 or class2
|
|
//
|
|
|
|
// find coincident polygon
|
|
const std::vector<int>& incident_polys = SAFE_ACCESS(m0_h_to_ply, m0_ihe);
|
|
|
|
MCUT_ASSERT(incident_polys.size() == 1); // class-1 halfedges are incident to exactly one polygon
|
|
|
|
const int incident_poly_idx = incident_polys.front();
|
|
const traced_polygon_t& incident_poly = SAFE_ACCESS(m0_polygons, incident_poly_idx);
|
|
// find the reference to the current halfedde (in the traced polygon)
|
|
traced_polygon_t::const_iterator he_find_iter = std::find(incident_poly.cbegin(), incident_poly.cend(), m0_ihe);
|
|
|
|
MCUT_ASSERT(he_find_iter != incident_poly.cend()); // if its incident to a polygon then that polygon must have it!
|
|
|
|
// halfedge index in polygon
|
|
const int he_index = (int)std::distance(incident_poly.cbegin(), he_find_iter);
|
|
// index of previous halfedge in polygon
|
|
const int preceeding_he_idx = wrap_integer(he_index - 1, 0, (int)incident_poly.size() - 1);
|
|
const hd_t& preceeding_he = SAFE_ACCESS(incident_poly, preceeding_he_idx);
|
|
const vd_t preceeding_he_src = m0.source(preceeding_he);
|
|
const vd_t preceeding_he_tgt = m0.target(preceeding_he);
|
|
const bool preceeding_he_src_is_ivertex = m0_is_intersection_point(preceeding_he_src, ps_vtx_cnt);
|
|
const bool preceeding_he_tgt_is_ivertex = m0_is_intersection_point(preceeding_he_tgt, ps_vtx_cnt);
|
|
// classify preceeding halfedge
|
|
const bool preceeding_he_is_class0 = !preceeding_he_src_is_ivertex && !preceeding_he_tgt_is_ivertex; // o-->o
|
|
const bool preceeding_he_is_class2 = preceeding_he_src_is_ivertex && !preceeding_he_tgt_is_ivertex; // x-->o
|
|
// count the original vertices that are contained in the polygon the current halfedge.
|
|
// we need this check to detect a special edge case.
|
|
const int overtices_in_poly = (int)std::count_if(
|
|
incident_poly.cbegin(), incident_poly.cend(),
|
|
[&](const hd_t& e) { return !m0_is_intersection_point(m0.target(e), ps_vtx_cnt); });
|
|
|
|
return (preceeding_he_is_class0 || (preceeding_he_is_class2 && overtices_in_poly == 1));
|
|
}
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
// our routine will start from an untransformed class-1 intersection-halfedge. We do this because it makes
|
|
// transformation process easier for us by reducing the number of steps.
|
|
std::unordered_map<hd_t, bool>::iterator m0_1st_sm_ihe_fiter = std::find_if( // for each src-mesh intersection halfedge
|
|
m0_sm_ihe_to_flag.begin(),
|
|
m0_sm_ihe_to_flag.end(),
|
|
check_if_halfedge_is_transformed);
|
|
|
|
// Here we have queue of intersection halfedges which will be used to begin a transformation walk/traversal
|
|
// around the polygon of each contained halfedge. For each polygon along the cut path there will ever be at
|
|
// most one of its halfedges in this queue.
|
|
// To assign a polygon along the cut-path to the correct connected component, we will traverse a subset (or full set)
|
|
// of it halfedges in order to assign the correct instance of each referenced intersection point to the traversed
|
|
// halfedge (s).
|
|
std::vector<hd_t> m0_ox_hlist;
|
|
|
|
// used to specifically prevent duplicate ox and xo halfedges
|
|
// Map-key=an intersection-vertex in m1;
|
|
// Map-value=list of (vertex,halfedge) pairs.
|
|
// The first elem in a pair is a vertex connected to [Map-key].
|
|
// The second element in a pair is the halfedge connecting [Map-key] and the first element in the pair
|
|
//
|
|
// NOTE: [Map-key] is always the src vertex of the halfedge which is the second element in a pair of [Map-value]
|
|
std::unordered_map<
|
|
vd_t, // "m1" intersection point
|
|
std::vector< // list of (vertex,halfedge) pairs.
|
|
std::pair<
|
|
vd_t, // vertex connected to "m1" intersection point (i.e. the key of this map)
|
|
hd_t // the halfedge connecting the vertices
|
|
>>>
|
|
m1_ivtx_to_h;
|
|
|
|
// At this point we also introduce the notion of a so-called "strongly connected border set" (SCBS).
|
|
// An SCBS is set of adjacent source-mesh polygons along the partitioned cut-path ("partitioned" implies that the
|
|
// source-mesh polygons along the cut-path are no longer "topologically connected").
|
|
// int strongly_connected_sm_boundary_seq_iter_id = -1;
|
|
|
|
// for each strongly-connected set of sm-boundary sequences.
|
|
// one iteration pertains to a transformation of a set of sm-boundary sequences which all belong to the same connected ccsponent.
|
|
// sets of sm-boundary sequences which belong to the same (sm) connected component may be produced different iterations.
|
|
|
|
do {
|
|
//
|
|
|
|
MCUT_ASSERT((m0_1st_sm_ihe_fiter != m0_sm_ihe_to_flag.end())); // their must be at least one halfedge from which we can start walking!
|
|
|
|
m0_ox_hlist.push_back(m0_1st_sm_ihe_fiter->first); // add to queue
|
|
|
|
// The following do-while loop will transform/process the halfedges which belong
|
|
// to exactly one SCBS
|
|
do {
|
|
|
|
hd_t m0_cur_h = hmesh_t::null_halfedge();
|
|
// get first intersection halfedge which determine's the first polygon of the current SCBS.
|
|
// Note that the current SCBS is determined implicitely from this initial intersection halfedge.
|
|
const hd_t seq_init_ihe = m0_ox_hlist.back();
|
|
|
|
m0_ox_hlist.pop_back(); // remove "seq_init_ihe" from queue
|
|
hd_t m0_nxt_h = seq_init_ihe;
|
|
|
|
// The following do-while loop will transform/process the halfedges which belong
|
|
// to [a part] of the current SCBS (i.e. a swept surface of polygons next to the
|
|
// partitioned cut-path).
|
|
do { // process ih sequence starting from "seq_init_ihe"
|
|
|
|
m0_cur_h = m0_nxt_h; // current
|
|
|
|
m0_nxt_h = m0.next(m0_cur_h); // next
|
|
|
|
const vd_t m0_cur_h_src = m0.source(m0_cur_h);
|
|
const vd_t m0_cur_h_tgt = m0.target(m0_cur_h);
|
|
const bool m0_cur_h_src_is_ivtx = m0_is_intersection_point(m0_cur_h_src, ps_vtx_cnt);
|
|
const bool m0_cur_h_tgt_is_ivtx = m0_is_intersection_point(m0_cur_h_tgt, ps_vtx_cnt);
|
|
const bool m0_cur_h_is_ox = !m0_cur_h_src_is_ivtx && m0_cur_h_tgt_is_ivtx; // o-->x
|
|
|
|
MCUT_ASSERT((size_t)m0_cur_h_src < m0_to_m1_vtx.size() /* m0_to_m1_vtx.find(m0_cur_h_src) != m0_to_m1_vtx.cend()*/);
|
|
|
|
vd_t m1_cur_h_src = SAFE_ACCESS(m0_to_m1_vtx, m0_cur_h_src); // from m0 to m1 descriptor
|
|
|
|
MCUT_ASSERT((size_t)m0_cur_h_tgt < m0_to_m1_vtx.size() /*m0_to_m1_vtx.find(m0_cur_h_tgt) != m0_to_m1_vtx.cend()*/);
|
|
|
|
vd_t m1_cur_h_tgt = SAFE_ACCESS(m0_to_m1_vtx, m0_cur_h_tgt); // from m0 to m1 descriptor
|
|
|
|
// o-->x OR x-->x
|
|
if (m0_cur_h_tgt_is_ivtx) { // tgt vertex of current halfedge is an intersection point
|
|
|
|
//
|
|
// check if the next halfedge has been processed/transformed
|
|
//
|
|
|
|
// can we find the m1 version of the next halfedge
|
|
std::unordered_map<hd_t, hd_t>::const_iterator m1_nxt_h_fiter = m0_to_m1_ihe.find(m0_nxt_h);
|
|
const bool nxt_is_processed = m1_nxt_h_fiter != m0_to_m1_ihe.cend();
|
|
|
|
if (nxt_is_processed) {
|
|
// Since the next halfedge has been processed, we can simply set
|
|
// target vertex instance of the current ("m1") halfedge to the source of the
|
|
// next halfedge
|
|
m1_cur_h_tgt = m1.source(m1_nxt_h_fiter->second);
|
|
} else {
|
|
// otherwise, we need to determined the correct instance of the tgt
|
|
// vertex to be used (see paper for details)
|
|
m1_cur_h_tgt = resolve_intersection_point_descriptor(ps, m0, m1, m0_cur_h, m0_cur_h_tgt, m1_cur_h_tgt, m0_cur_h_is_ox,
|
|
m0_h_to_ply, ivtx_to_incoming_hlist, m0_sm_ihe_to_flag, m0_ivtx_to_intersection_registry_entry, m0_to_m1_ihe, m0_to_ps_vtx, ps_vtx_cnt, sm_vtx_cnt, sm_face_count, m0_num_cutpath_halfedges);
|
|
}
|
|
}
|
|
|
|
// x-->o OR x-->x
|
|
if (m0_cur_h_src_is_ivtx) { // src vertex of current halfedge is an intersection point
|
|
if (m0_cur_h == seq_init_ihe) // is it the first halfedge of the current SCBS?
|
|
{
|
|
// get the opposite halfedge
|
|
const hd_t opp = m0.opposite(m0_cur_h); // NOTE: m0_cur_h_src == target(opp)
|
|
// we need to determined the correct instance of the src vertex to be used (see paper for details)
|
|
m1_cur_h_src = resolve_intersection_point_descriptor(ps, m0, m1, opp, m0_cur_h_src, m1_cur_h_src, m0_cur_h_is_ox,
|
|
m0_h_to_ply, ivtx_to_incoming_hlist, m0_sm_ihe_to_flag, m0_ivtx_to_intersection_registry_entry, m0_to_m1_ihe, m0_to_ps_vtx, ps_vtx_cnt, sm_vtx_cnt, sm_face_count, m0_num_cutpath_halfedges);
|
|
} else { // current halfedge is not the first halfedge of the current SCBS
|
|
|
|
// get the previous halfedge
|
|
const hd_t m0_prv_h = m0.prev(m0_cur_h);
|
|
|
|
// The previous halfedge must have been transformed since the current halfedge
|
|
// is not the first halfedge of the current SCBS. TODO: explain further for why
|
|
// this is true when halfedge is x-->x (i.e. exterior, and from scoop cut)
|
|
MCUT_ASSERT(m0_to_m1_ihe.find(m0_prv_h) != m0_to_m1_ihe.cend());
|
|
|
|
// get transformed instance of previous halfedge ("m1" version )
|
|
const hd_t m1_prv_h = SAFE_ACCESS(m0_to_m1_ihe, m0_prv_h);
|
|
// Since the previous halfedge has been processed, we can simply set
|
|
// src vertex instance of the current ("m1") halfedge to the source of the
|
|
// next halfedge
|
|
const vd_t m1_prv_h_tgt = m1.target(m1_prv_h);
|
|
m1_cur_h_src = m1_prv_h_tgt;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now that we have resolved the correct descriptor instance to use for the src and/or tgt vertex,
|
|
// we will create an edge in halfedge data structure ("m1") connecting "m1_cur_h_src" and
|
|
// "m1_cur_h_tgt" if this edge does not already exist.
|
|
//
|
|
|
|
// bool m1_cur_h_exists = false;
|
|
// const bool m0_cur_h_is_xx = m0_cur_h_src_is_ivtx && m0_cur_h_tgt_is_ivtx;
|
|
// std::map<vd_t, std::vector<std::pair<vd_t, hd_t> > >::iterator fiter = m1_ivtx_to_h.end();
|
|
|
|
const bool is_boundary_halfedge = m0_is_polygon_boundary_halfedge(m0_cur_h, m0_num_cutpath_halfedges);
|
|
bool m0_cur_h_is_exterior = is_boundary_halfedge;
|
|
|
|
#if 0
|
|
if (m0_cur_h_is_xx)
|
|
{
|
|
//const hd_t m0_cur_h_src_ps_h = SAFE_ACCESS(m0_ivtx_to_ps_edge, m0_cur_h_src);
|
|
//const hd_t m0_cur_h_tgt_ps_h = SAFE_ACCESS(m0_ivtx_to_ps_edge, m0_cur_h_tgt);
|
|
MCUT_ASSERT((size_t)m0_cur_h_src - ps_vtx_cnt < m0_ivtx_to_intersection_registry_entry.size() /*m0_ivtx_to_intersection_registry_entry.find(m0_cur_h_src) != m0_ivtx_to_intersection_registry_entry.cend()*/);
|
|
const std::pair<ed_t, fd_t> &m0_cur_h_src_ipair = SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, m0_cur_h_src - ps_vtx_cnt);
|
|
const ed_t m0_cur_h_src_ps_e = m0_cur_h_src_ipair.first; // SAFE_ACCESS(m0_ivtx_to_ps_edge, m0_cur_h_src); //ps.edge(m0_cur_h_src_ps_h);
|
|
|
|
MCUT_ASSERT((size_t)m0_cur_h_tgt - ps_vtx_cnt < m0_ivtx_to_intersection_registry_entry.size() /*m0_ivtx_to_intersection_registry_entry.find(m0_cur_h_tgt) != m0_ivtx_to_intersection_registry_entry.cend()*/);
|
|
const std::pair<ed_t, fd_t> &m0_cur_h_tgt_ipair = SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, m0_cur_h_tgt - ps_vtx_cnt);
|
|
const ed_t m0_cur_h_tgt_ps_e = m0_cur_h_tgt_ipair.first; // SAFE_ACCESS(m0_ivtx_to_ps_edge, m0_cur_h_tgt); // ps.edge(m0_cur_h_tgt_ps_h);
|
|
m0_cur_h_is_exterior = (m0_cur_h_src_ps_e == m0_cur_h_tgt_ps_e);
|
|
}
|
|
#endif
|
|
// get the opposite of the current halfedge ("m0")
|
|
const hd_t opp = m0.opposite(m0_cur_h);
|
|
// check if this opposite halfedge was used to traced a polygon
|
|
const bool opp_used_for_tracing = SAFE_ACCESS(m0_h_to_ply, opp).size() > 0 /*m0_h_to_ply.find(opp) != m0_h_to_ply.end()*/;
|
|
|
|
// if 1) the current halfedge is an interior halfedge (x-->x), OR
|
|
// 2) the current halfedge is an exterior halfedge AND it has not been processed
|
|
if (!m0_cur_h_is_exterior || (m0_cur_h_is_exterior && m0_to_m1_ihe.find(m0_cur_h) == m0_to_m1_ihe.cend())) {
|
|
|
|
// create processed version (i.e. "m1" version) of "m0_cur_h"
|
|
hd_t m1_cur_h = m1.add_edge(m1_cur_h_src, m1_cur_h_tgt);
|
|
|
|
MCUT_ASSERT(m1_cur_h != hmesh_t::null_halfedge());
|
|
|
|
//
|
|
// here, we update the list containing the vertices and halfedge that are connected
|
|
// to the "m1_cur_h_src" and "m1_cur_h_tgt" i.e. adjacency information
|
|
//
|
|
|
|
if (m0_cur_h_is_exterior) { // is the current halfedge an exterior halfedge..?
|
|
|
|
// find entry in the adjancency vector
|
|
|
|
// src
|
|
std::unordered_map<vd_t, std::vector<std::pair<vd_t, hd_t>>>::iterator fiter = m1_ivtx_to_h.find(m1_cur_h_src);
|
|
|
|
if (fiter != m1_ivtx_to_h.cend()) // check src's entry exists
|
|
{
|
|
MCUT_ASSERT(std::find_if(fiter->second.cbegin(), fiter->second.cend(), [&](const std::pair<vd_t, hd_t>& p) { return p.first == m1_cur_h_tgt; }) == fiter->second.cend());
|
|
fiter->second.emplace_back(m1_cur_h_tgt, m1_cur_h); // record connection and save connecting halfedge
|
|
} else {
|
|
std::pair<std::unordered_map<vd_t, std::vector<std::pair<vd_t, hd_t>>>::iterator, bool> p = m1_ivtx_to_h.emplace(m1_cur_h_src, std::vector<std::pair<vd_t, hd_t>>());
|
|
MCUT_ASSERT(p.second == true);
|
|
fiter = p.first;
|
|
fiter->second.emplace_back(m1_cur_h_tgt, m1_cur_h);
|
|
}
|
|
|
|
// repeat for tgt
|
|
|
|
fiter = m1_ivtx_to_h.find(m1_cur_h_tgt); // check if the tgt's entry exists and update it
|
|
|
|
if (fiter != m1_ivtx_to_h.cend()) // check tgt's entry exists
|
|
{
|
|
MCUT_ASSERT(std::find_if(fiter->second.cbegin(), fiter->second.cend(), [&](const std::pair<vd_t, hd_t>& p) { return p.first == m1_cur_h_src; }) == fiter->second.cend());
|
|
fiter->second.emplace_back(m1_cur_h_src, m1.opposite(m1_cur_h)); // record that it is connected to src (by the opp he of m1_cur_h)
|
|
} else {
|
|
std::pair<std::unordered_map<vd_t, std::vector<std::pair<vd_t, hd_t>>>::iterator, bool> p = m1_ivtx_to_h.emplace(m1_cur_h_tgt, std::vector<std::pair<vd_t, hd_t>>());
|
|
MCUT_ASSERT(p.second == true);
|
|
fiter = p.first;
|
|
fiter->second.emplace_back(m1_cur_h_src, m1.opposite(m1_cur_h)); // record that it is connected to src (by the opp he of m1_cur_h)
|
|
}
|
|
}
|
|
|
|
// map m0 to m1 version of current halfedge
|
|
m0_to_m1_ihe.emplace(m0_cur_h, m1_cur_h);
|
|
|
|
// if 1) the current halfedge is an exterior halfedge, AND
|
|
// 2) the opposite of the current halfedge was used to trace a polygon
|
|
if (m0_cur_h_is_exterior && opp_used_for_tracing) {
|
|
// Thanks of the halfedge data structure (each edge has 2 halfedges),
|
|
// we also have the m1 version/copy of opposite halfedge.
|
|
// NOTE however, the opposite halfedge it is still not "processed", and
|
|
// we will do so only when we traverse/walk it!
|
|
m0_to_m1_ihe.emplace(opp, m1.opposite(m1_cur_h));
|
|
}
|
|
|
|
// NOTE: keep in mind that two opposite halfedges which are interior halfedges
|
|
// will belong to separate connected components after all processing is complete.
|
|
// (exterior halfedges on the other hand will share the same connected component
|
|
// as their opposites).
|
|
} else {
|
|
|
|
//
|
|
// here, we have an exterior halfedge whose "m1" version has already been created.
|
|
//
|
|
MCUT_ASSERT(m0_to_m1_ihe.find(m0_cur_h) != m0_to_m1_ihe.cend());
|
|
const hd_t m1_cur_h = SAFE_ACCESS(m0_to_m1_ihe, m0_cur_h);
|
|
}
|
|
|
|
//
|
|
// update queue of ox halfedges which will be the initial halfedges of
|
|
// (potentially parts of) SCBS's to be processed
|
|
//
|
|
|
|
// x-->o
|
|
const bool m0_cur_h_is_xo = m0_cur_h_src_is_ivtx && !m0_cur_h_tgt_is_ivtx;
|
|
|
|
// if 1) curreent halfedge is x-->o AND 2) it's opposites has been using to trace a polygon, AND
|
|
// 3) this opposite has not already been processed
|
|
if (m0_cur_h_is_xo && opp_used_for_tracing && !SAFE_ACCESS(m0_sm_ihe_to_flag, opp)) {
|
|
|
|
// get the next halfedge
|
|
const hd_t nxt = m0.next(m0_cur_h);
|
|
// is the next halfedge an intersection halfedge "o-->x"
|
|
const bool nxt_is_ih = m0_is_intersection_point(m0.target(nxt), ps_vtx_cnt); // check if is last halfedge
|
|
|
|
if ((nxt_is_ih && nxt == seq_init_ihe) || !nxt_is_ih) {
|
|
// here we add the next SCBS's first halfedge from which SCBS processing will begin.
|
|
m0_ox_hlist.push_back(opp);
|
|
}
|
|
}
|
|
|
|
MCUT_ASSERT(m0_sm_ihe_to_flag.find(m0_cur_h) != m0_sm_ihe_to_flag.cend());
|
|
|
|
SAFE_ACCESS(m0_sm_ihe_to_flag, m0_cur_h) = true; // mark as "processed"
|
|
|
|
} while (
|
|
// "next" is ihalfedge
|
|
(m0_is_intersection_point(m0.source(m0_nxt_h), ps_vtx_cnt) || m0_is_intersection_point(m0.target(m0_nxt_h), ps_vtx_cnt)) &&
|
|
// "next" is not transformed. For case when ihalfedge-sequence forms a loop.
|
|
SAFE_ACCESS(m0_sm_ihe_to_flag, m0_nxt_h) == false); // TODO: I think this last condition is the same as "m0_nxt_h" == "seq_init_ihe" (try it bcz using m0_sm_ihe_to_flag will be slower)
|
|
|
|
} while (!m0_ox_hlist.empty());
|
|
|
|
//
|
|
// find next class1 halfedge which has not been transformed ( possibly in the same connected ccsponent )
|
|
//
|
|
|
|
m0_1st_sm_ihe_fiter = std::find_if( // find o-->x halfedge
|
|
m0_sm_ihe_to_flag.begin(),
|
|
m0_sm_ihe_to_flag.end(),
|
|
check_if_halfedge_is_transformed);
|
|
|
|
// True only if there exists a src-mesh ps-edge which has [at least] two intersection points
|
|
// This means that the source-mesh has a scoop cut (see example 19)
|
|
const bool class1_ihalfedge_found = (m0_1st_sm_ihe_fiter != m0_sm_ihe_to_flag.end());
|
|
|
|
if (!class1_ihalfedge_found) { // The above search failed to find an untransformed class-1 halfedge.
|
|
|
|
//
|
|
// So now we instead try to search an untransformed polygon-boundary interior-ihalfedge (x-->x).
|
|
//
|
|
|
|
m0_1st_sm_ihe_fiter = std::find_if( // for each intersection halfedge
|
|
m0_sm_ihe_to_flag.begin(), m0_sm_ihe_to_flag.end(),
|
|
[&](const std::pair<hd_t, bool>& e) {
|
|
const bool is_transformed = e.second; // has it already been transformed..?
|
|
|
|
if (is_transformed) {
|
|
return false; // we want only the transformed intersection halfedges
|
|
}
|
|
|
|
const hd_t& m0_ihe = e.first;
|
|
|
|
const vd_t m0_ihe_src_vertex = m0.source(m0_ihe);
|
|
const bool src_is_ivertex = m0_is_intersection_point(m0_ihe_src_vertex, ps_vtx_cnt);
|
|
const vd_t m0_ihe_tgt_vertex = m0.target(m0_ihe);
|
|
const bool tgt_is_ivertex = m0_is_intersection_point(m0_ihe_tgt_vertex, ps_vtx_cnt);
|
|
|
|
if (!(src_is_ivertex && tgt_is_ivertex)) {
|
|
return false; // we want only class-3 intersection halfedges (x-->x)
|
|
}
|
|
|
|
//
|
|
// checf is halfedge is really an exterior one (ambigious case arising from concave polyhedron cut)
|
|
//
|
|
// const hd_t v0_coincident_ps_halfedge = SAFE_ACCESS(m0_ivtx_to_ps_edge, m0_ihe_src_vertex);
|
|
// const hd_t v1_coincident_ps_halfedge = SAFE_ACCESS(m0_ivtx_to_ps_edge, m0_ihe_tgt_vertex);
|
|
#if 0
|
|
MCUT_ASSERT((size_t)m0_ihe_src_vertex - ps_vtx_cnt < m0_ivtx_to_intersection_registry_entry.size() /*m0_ivtx_to_intersection_registry_entry.find(m0_ihe_src_vertex) != m0_ivtx_to_intersection_registry_entry.cend()*/);
|
|
const std::pair<ed_t, fd_t> &m0_ihe_src_vertex_ipair = SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, m0_ihe_src_vertex - ps_vtx_cnt);
|
|
const ed_t v0_ps_edge = m0_ihe_src_vertex_ipair.first; // SAFE_ACCESS(m0_ivtx_to_ps_edge, m0_ihe_src_vertex); //ps.edge(v0_coincident_ps_halfedge);
|
|
|
|
MCUT_ASSERT((size_t)m0_ihe_tgt_vertex - ps_vtx_cnt < m0_ivtx_to_intersection_registry_entry.size() /*m0_ivtx_to_intersection_registry_entry.find(m0_ihe_tgt_vertex) != m0_ivtx_to_intersection_registry_entry.cend()*/);
|
|
const std::pair<ed_t, fd_t> &m0_ihe_tgt_vertex_ipair = SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, m0_ihe_tgt_vertex - ps_vtx_cnt);
|
|
const ed_t v1_ps_edge = m0_ihe_tgt_vertex_ipair.first; // SAFE_ACCESS(m0_ivtx_to_ps_edge, m0_ihe_tgt_vertex); // ps.edge(v1_coincident_ps_halfedge);
|
|
|
|
const bool is_poly_exterior_interior_ihalfedge = (v0_ps_edge == v1_ps_edge);
|
|
#endif
|
|
const bool is_boundary_halfedge = m0_is_polygon_boundary_halfedge(m0_ihe, m0_num_cutpath_halfedges);
|
|
|
|
if (is_boundary_halfedge /*is_poly_exterior_interior_ihalfedge*/) { // we want only polygon-exterior interior ihalfedges
|
|
|
|
// get the traced polygon which uses the current halfedge
|
|
const std::vector<std::vector<int>>::const_iterator coincident_poly_find_iter = m0_h_to_ply.cbegin() + m0_ihe; /* m0_h_to_ply.find(m0_ihe);*/
|
|
|
|
MCUT_ASSERT(coincident_poly_find_iter != m0_h_to_ply.end());
|
|
MCUT_ASSERT(coincident_poly_find_iter->size() == 1); // polygon-exterior interior-ihalfedges are incident to exactly one polygon
|
|
|
|
const bool is_used_to_trace_src_mesh_polygon = (coincident_poly_find_iter->front() < traced_sm_polygon_count);
|
|
|
|
return (is_used_to_trace_src_mesh_polygon);
|
|
} else {
|
|
return false;
|
|
}
|
|
});
|
|
}
|
|
|
|
// loop while there exists a "non-transformed" exterior intersection-halfedge
|
|
// from which we can start building a SCBS
|
|
} while (m0_1st_sm_ihe_fiter != m0_sm_ihe_to_flag.end());
|
|
|
|
#if 0
|
|
// dump
|
|
|
|
|
|
for (std::map<hd_t, hd_t>::const_iterator i = m0_to_m1_ihe.cbegin(); i != m0_to_m1_ihe.cend(); ++i) {
|
|
|
|
}
|
|
#endif
|
|
|
|
TIMESTACK_POP(); // &&&&&
|
|
|
|
// m0_to_ps_vtx.clear(); // free
|
|
ivtx_to_incoming_hlist.clear(); // free
|
|
m0_sm_ihe_to_flag.clear(); // free
|
|
// m0_to_m1_vtx.clear(); // free
|
|
m0_ox_hlist.clear(); // free
|
|
m1_ivtx_to_h.clear(); // free
|
|
|
|
//
|
|
// NOTE: at this stage, we have calculated all the vertices, edges, halfedges and meta-data
|
|
// which describes the connectivity of the partitioned (topologically split) source-mesh along
|
|
// the cut-path.
|
|
//
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Save the number of vertices in "m1" after source-mesh partitioning
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
// saving the number of vertices here will allow us to infer exactly which vertices
|
|
// lie on the seam.
|
|
|
|
const int m1_num_vertices_after_srcmesh_partitioning = m1.number_of_vertices();
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Update the traced polygons to represent the partitioned src-mesh
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
TIMESTACK_PUSH("Update traced polygons");
|
|
|
|
//
|
|
// We are basically re-tracing the polygons that we traced earlier (in "m0").
|
|
// These retraced polygon are stored in "m1". The re-traced polygons which
|
|
// where next to the cut-path will now reside (i.e. reference vertices and
|
|
// halfedges) in the correct connected component to separate the source mesh.
|
|
//
|
|
|
|
// the updated polygons (with the partitioning)
|
|
std::vector<traced_polygon_t> m1_polygons;
|
|
m1_polygons.resize(traced_sm_polygon_count); // resize to match
|
|
|
|
// NOTE: this map contains polygons which are traced on the source mesh only.
|
|
// We do this because cut-mesh polygon will have two version each, where
|
|
// a version corresponds to those that are stitched on the exterior and those interior.
|
|
// We will duplicate the "m1" mesh later into two copies.
|
|
std::unordered_map<int, int> m0_to_m1_face; // std::map<int, int> m0_to_m1_face;
|
|
std::unordered_map<int, int> m1_to_m0_face;
|
|
|
|
// for each traced polygon (in "m0")
|
|
for (std::vector<traced_polygon_t>::const_iterator m0_traced_sm_polygon_iter = m0_polygons.cbegin();
|
|
m0_traced_sm_polygon_iter != m0_traced_sm_polygons_iter_cend;
|
|
++m0_traced_sm_polygon_iter) {
|
|
const traced_polygon_t& m0_sm_polygon = *m0_traced_sm_polygon_iter; // m0 version (unpartitioned)
|
|
// get index of polygon
|
|
const int polygon_index = (int)std::distance(m0_polygons.cbegin(), m0_traced_sm_polygon_iter);
|
|
|
|
MCUT_ASSERT(polygon_index < (int)m1_polygons.size()); // sanity check
|
|
|
|
traced_polygon_t& m1_sm_polygon = SAFE_ACCESS(m1_polygons, polygon_index); // m1 version (partitioned)
|
|
m1_sm_polygon.resize(m0_sm_polygon.size()); // resize to match
|
|
|
|
// for each halfedge of current polygon
|
|
for (traced_polygon_t::const_iterator m0_traced_sm_polygon_halfedge_iter = m0_sm_polygon.cbegin();
|
|
m0_traced_sm_polygon_halfedge_iter != m0_sm_polygon.cend();
|
|
++m0_traced_sm_polygon_halfedge_iter) {
|
|
|
|
const hd_t& m0_he = *m0_traced_sm_polygon_halfedge_iter;
|
|
const bool m0_he_src_is_ivertex = m0_is_intersection_point(m0.source(m0_he), ps_vtx_cnt);
|
|
const bool m0_he_tgt_is_ivertex = m0_is_intersection_point(m0.target(m0_he), ps_vtx_cnt);
|
|
// is the halfedge connected to an intersection point...?
|
|
const bool is_ihalfedge = m0_he_src_is_ivertex || m0_he_tgt_is_ivertex;
|
|
|
|
hd_t m1_he = hmesh_t::null_halfedge();
|
|
|
|
if (is_ihalfedge) { // its an intersection halfedge
|
|
MCUT_ASSERT(m0_to_m1_ihe.find(m0_he) != m0_to_m1_ihe.cend()); // must have been walked/traversed
|
|
|
|
m1_he = SAFE_ACCESS(m0_to_m1_ihe, m0_he); // m1 version
|
|
} else {
|
|
MCUT_ASSERT(m0_to_m1_he.find(m0_he) != m0_to_m1_he.cend());
|
|
|
|
m1_he = SAFE_ACCESS(m0_to_m1_he, m0_he); // m1 version
|
|
}
|
|
|
|
// get halfedge index in polygon
|
|
const int halfedge_index = (int)std::distance(m0_sm_polygon.cbegin(), m0_traced_sm_polygon_halfedge_iter);
|
|
|
|
MCUT_ASSERT(halfedge_index < (int)m1_sm_polygon.size()); // array was resized with the same capacity as m0 polygon
|
|
|
|
SAFE_ACCESS(m1_sm_polygon, halfedge_index) = m1_he;
|
|
}
|
|
|
|
m0_to_m1_face[polygon_index] = polygon_index; // one to one mapping because we are only dealing with source-mesh traced polygons
|
|
m1_to_m0_face[polygon_index] = polygon_index;
|
|
}
|
|
|
|
m0_to_m1_he.clear();
|
|
|
|
TIMESTACK_POP();
|
|
|
|
//
|
|
// NOTE: at this stage "m1_polygons" (and "m0_to_m1...") contains only source-mesh polygons.
|
|
//
|
|
|
|
TIMESTACK_PUSH("Mark seam edges");
|
|
// extract the seam vertices
|
|
// NOTE: the size of this vector include only ps vertices, intersection points, and
|
|
// the duplicates of intersection points (for separating the source mesh).
|
|
// new vertices that will later be created (by duplicate ps cut-mesh vertices during stitching)
|
|
// are not included
|
|
std::vector<bool> m1_vertex_to_seam_flag;
|
|
mark_seam_vertices(m1_vertex_to_seam_flag, m1, ps_vtx_cnt, m1_num_vertices_after_srcmesh_partitioning);
|
|
|
|
TIMESTACK_POP();
|
|
|
|
MCUT_ASSERT(!m1_vertex_to_seam_flag.empty());
|
|
|
|
if (input.keep_unsealed_fragments || input.keep_fragments_partially_cut) {
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Extract the partitioned connected components for output
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
std::map<std::size_t, std::vector<std::pair<std::shared_ptr<hmesh_t>, connected_component_info_t>>> unsealed_connected_components;
|
|
|
|
extract_connected_components(
|
|
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
*input.scheduler,
|
|
#endif
|
|
unsealed_connected_components,
|
|
m1,
|
|
0,
|
|
m1_polygons,
|
|
sm_polygons_below_cs,
|
|
sm_polygons_above_cs,
|
|
m1_vertex_to_seam_flag,
|
|
m1_to_m0_ovtx,
|
|
std::unordered_map<vd_t, vd_t>(), // ... because data is only available during "m1" stitching stage (later), and its not needed here
|
|
m1_to_m0_face,
|
|
m0_to_ps_vtx,
|
|
m0_to_ps_face,
|
|
ps_to_sm_vtx,
|
|
ps_to_sm_face,
|
|
ps_to_cm_vtx,
|
|
ps_to_cm_face,
|
|
sm_vtx_cnt,
|
|
sm_face_count,
|
|
input.populate_vertex_maps,
|
|
input.populate_face_maps,
|
|
input.keep_fragments_below_cutmesh,
|
|
input.keep_fragments_above_cutmesh,
|
|
input.keep_fragments_partially_cut);
|
|
|
|
// for each connected component (i.e. mesh)
|
|
for (std::map<std::size_t, std::vector<std::pair<std::shared_ptr<hmesh_t>, connected_component_info_t>>>::iterator cc_iter = unsealed_connected_components.begin();
|
|
cc_iter != unsealed_connected_components.end();
|
|
++cc_iter) {
|
|
|
|
const int cc_id = static_cast<int>(cc_iter->first);
|
|
std::vector<std::pair<std::shared_ptr<hmesh_t>, connected_component_info_t>>& mesh_data = cc_iter->second;
|
|
|
|
// there will only be one element of the mesh since "unsealed_connected_components"
|
|
// is empty before calling "extract_connected_components"
|
|
MCUT_ASSERT(mesh_data.size() == 1);
|
|
if (input.verbose) {
|
|
dump_mesh(mesh_data.front().first.get()[0], ("fragment.unsealed." + std::to_string(cc_id) + "." + to_string(mesh_data.front().second.location)).c_str());
|
|
}
|
|
std::pair<std::shared_ptr<hmesh_t>, connected_component_info_t>& md = mesh_data.front();
|
|
std::shared_ptr<output_mesh_info_t> omi = std::shared_ptr<output_mesh_info_t>(new output_mesh_info_t);
|
|
omi->mesh = md.first;
|
|
omi->seam_vertices = std::move(md.second.seam_vertices);
|
|
omi->data_maps = std::move(md.second.data_maps);
|
|
output.unsealed_cc[md.second.location].emplace_back((omi));
|
|
}
|
|
|
|
unsealed_connected_components.clear();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Check if the pipeline needs to terminate at this point
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
// Note that we do not ever continue to fill holes if the cut-mesh cuts
|
|
// the source-mesh multiple times where most cuts are complete but there is at-least one partial.
|
|
// So long as there is a partial cut and the input mesh is not water tight, we wont patch.
|
|
// This is because patching becomes complex as we then need to account for skipping the task of stitching patches which are incident to hole-bounding-sequences which are not loops.
|
|
// Maybe future work..?
|
|
const bool proceed_to_fill_holes = explicit_cutpaths_making_holes.size() == m0_cutpath_sequences.size();
|
|
|
|
//
|
|
// The pipeline stops here if there are no holes to fill.
|
|
//
|
|
// NOTE: non-watertight meshes with a complete cut by a single cut-surface polygon cannot be "sealed"
|
|
// since no edges of the cut-mesh polygon will be intersected.
|
|
//
|
|
|
|
if (proceed_to_fill_holes == false) {
|
|
printf("[mcut-kernel]: detected a configuration that does not permit filling holes. input-mesh verification advised.\n");
|
|
return; // done
|
|
}
|
|
|
|
if (false == (input.keep_fragments_below_cutmesh || //
|
|
input.keep_fragments_above_cutmesh || //
|
|
input.keep_fragments_sealed_inside || //
|
|
input.keep_fragments_sealed_outside || input.keep_fragments_sealed_inside_exhaustive || //
|
|
input.keep_fragments_sealed_outside_exhaustive || //
|
|
input.keep_inside_patches || //
|
|
input.keep_outside_patches)) {
|
|
// if the user simply wants [unsealed] fragments that may be [partially cut], then we should not have to proceed further.
|
|
return;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// The remainder of the pipeline performs hole-filling if there exists
|
|
// atleast one from a circular/linear cut-path which creates a hole in the
|
|
// source-mesh.
|
|
// Specifically, we will seal each connected component by
|
|
// identifying and then "stitching" patches of cut-mesh polygons to the
|
|
// source-mesh connected components
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
// Please refer to: `docs/notes.md`, section "Hole Filling"
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Gather a primary intersection-halfedge for each patch (i.e. cut-path)
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
// Here, we gather one interior intersection-halfedge (in "m0") for each patch,
|
|
// (this halfedge is used to trace a cut-mesh polygon). We will use these
|
|
// halfedges as starting points to iteratively build patches. Building a patch
|
|
// is analogous to labelling each cut-mesh polygon with a patch id. We use halfedges along
|
|
// cut-paths since they mark/represent the border/boundary between patches.
|
|
//
|
|
// Thus, in the following std::vector, each element is a pair of a 1) cut-mesh polygon index
|
|
// and 2) the index of a halfedge (on a cut-path) in that polygon ("m0" version).
|
|
//
|
|
|
|
TIMESTACK_PUSH("Find primary halfedges for patch identification");
|
|
|
|
std::vector<std::pair<int, int>> patch_discovery_seeds;
|
|
|
|
// for each cutpath that makes a hole
|
|
for (std::vector<int>::const_iterator ecpmh_iter = explicit_cutpaths_making_holes.cbegin();
|
|
ecpmh_iter != explicit_cutpaths_making_holes.cend();
|
|
++ecpmh_iter) {
|
|
|
|
const int ecpmh_idx = *ecpmh_iter; // index of cutpath
|
|
MCUT_ASSERT(ecpmh_idx < (int)m0_cutpath_sequences.size());
|
|
const std::vector<ed_t>& m0_explicit_cutpath_sequence = SAFE_ACCESS(m0_cutpath_sequences, ecpmh_idx);
|
|
|
|
// pick any edge (we choose the first one)
|
|
const ed_t& edge = m0_explicit_cutpath_sequence.front();
|
|
MCUT_ASSERT(edge != hmesh_t::null_edge());
|
|
|
|
for (int i = 0; i < 2; ++i) { // for each halfedge of current edge
|
|
hd_t h = m0.halfedge(edge, i);
|
|
MCUT_ASSERT(h != hmesh_t::null_halfedge());
|
|
|
|
/*
|
|
1. get the cut-mesh polygon using the halfedge
|
|
2. save polygon and halfedge index
|
|
*/
|
|
|
|
MCUT_ASSERT(SAFE_ACCESS(m0_h_to_ply, h).size() > 0 /*m0_h_to_ply.find(h) != m0_h_to_ply.cend()*/);
|
|
const std::vector<int>& h_polygons = SAFE_ACCESS(m0_h_to_ply, h);
|
|
|
|
// get the cut-mesh polygon using h0
|
|
std::vector<int>::const_iterator h_cutmesh_polygon_find_iter = std::find_if(
|
|
h_polygons.cbegin(),
|
|
h_polygons.cend(),
|
|
[&](const int& e) {
|
|
return e >= traced_sm_polygon_count; // match cutmesh polygon!
|
|
});
|
|
MCUT_ASSERT(h_cutmesh_polygon_find_iter != h_polygons.cend()); // cut path halfedge are always used by 2 polygons (for tracing)
|
|
const int h_polygon_idx = *h_cutmesh_polygon_find_iter;
|
|
// the actual polygon
|
|
MCUT_ASSERT(h_polygon_idx < (int)m0_polygons.size());
|
|
|
|
const std::vector<hd_t>& h_polygon = SAFE_ACCESS(m0_polygons, h_polygon_idx);
|
|
// find the index of h0 in the polygon
|
|
std::vector<hd_t>::const_iterator h_polygon_find_iter = std::find_if(
|
|
h_polygon.cbegin(), h_polygon.cend(),
|
|
[&](const hd_t& e) {
|
|
return e == h;
|
|
});
|
|
|
|
MCUT_ASSERT(h_polygon_find_iter != h_polygon.cend());
|
|
|
|
const int h_idx = (int)std::distance(h_polygon.cbegin(), h_polygon_find_iter);
|
|
|
|
patch_discovery_seeds.emplace_back(h_polygon_idx, h_idx); // save
|
|
}
|
|
}
|
|
|
|
std::vector<std::pair<int, int>> primary_interior_ihalfedge_pool = patch_discovery_seeds; // copy because it gets modify
|
|
|
|
TIMESTACK_POP();
|
|
|
|
m0_cutpath_sequences.clear(); // free, no longer needed.
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Find graph(s) and build patches
|
|
///////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
TIMESTACK_PUSH("Build patch graphs"); // &&&&&
|
|
|
|
// Note that the built patches in this stage will have the same winding
|
|
// order (e.g. counter-clock-wise ) as the input cut-mesh. The patches with
|
|
// reversed winding order will be created later (once all patches are
|
|
// identified).
|
|
//
|
|
// There is one graph which arises from the intersection of the
|
|
// source-mesh with the cut-mesh. This graph is planar
|
|
// and there is exactly one node with e.g. color "A" and the rest are "B".
|
|
// When visualised, it looks like a star, and this graph topology
|
|
// is bipartite. We call this a "strongly-connected set" (SCS) because there
|
|
// is a path from one graph node to another.
|
|
//
|
|
//
|
|
std::map<
|
|
int, // patch index
|
|
std::vector<int> // patch polygon-indices
|
|
>
|
|
patches;
|
|
|
|
std::unordered_map<
|
|
int, // traced cut-mesh polygon index
|
|
int // patch index
|
|
>
|
|
m0_cm_poly_to_patch_idx;
|
|
m0_cm_poly_to_patch_idx.reserve(cs_face_count);
|
|
|
|
// This map stores an interior intersection-halfedge for each patch. This
|
|
// halfedge also represents the traced cut-mesh polygon from which we will
|
|
// start stitching/glueing the correspnding patch to a fragment connected
|
|
// component of the source mesh.
|
|
// This connected component will be one which has the same winding as the
|
|
// patch
|
|
std::map<
|
|
int, // patch index
|
|
int // interior intersection-halfedge from which patch-stitching into a connected component will start from
|
|
>
|
|
patch_to_seed_interior_ihalfedge_idx;
|
|
|
|
std::map<
|
|
int, // patch index
|
|
int> // index of patch-polygon that will be stitched first (from "patch_to_seed_interior_ihalfedge_idx[patch index]")
|
|
patch_to_seed_poly_idx;
|
|
|
|
// Interior/inside patches must be stitched into separate connected component
|
|
// as exterior/outside patches so we create two versions of "m1" for that.
|
|
//
|
|
std::map<
|
|
char, // color value (representing the notion of "interior"/"exterior")
|
|
hmesh_t // the mesh (copy of "m1") to which corresponding patch(es) will be stitched
|
|
>
|
|
color_to_m1; // = { { 'A' /*e.g. "red"*/, m1 }, { 'B' /*e.g. "blue"*/, m1 } };
|
|
color_to_m1.insert(std::make_pair('A' /*e.g. "red"*/, m1));
|
|
color_to_m1.insert(std::make_pair('B' /*e.g. "red"*/, std::move(m1))); // "m1" becomes NULL after this
|
|
|
|
// m1.reset(); // clear data
|
|
|
|
// Patch (node) colors
|
|
// NOTE: we use the letters 'A' and 'B' just to ensure lexicographical order
|
|
// when we iterate over color_to_patch
|
|
//
|
|
std::map<
|
|
char, // color value
|
|
std::vector<int> // list of patches of that color
|
|
>
|
|
color_to_patch = { { 'A', std::vector<int>() }, { 'B', std::vector<int>() } };
|
|
|
|
// We also tag each patch, identifying whether is it a floating patch or not.
|
|
// All patches have an entry, including the reversed patches that are created
|
|
// later.
|
|
//
|
|
// std::map<
|
|
// int, // patch id
|
|
// bool // flag to indicate of patch is a floating patch.
|
|
// >
|
|
// patch_to_floating_flag;
|
|
|
|
// tracks how many cut-mesh polygons have been stitched. Used only for naming damped meshes
|
|
int global_cm_poly_stitch_counter = 0;
|
|
|
|
// keeps track of the total number of default-winding-order (e.g. CCW) patches which has been identified
|
|
// NOTE: not all will be CCW if we have floating patches (in this case winding could be flipped)
|
|
int total_ccw_patch_count = 0;
|
|
std::vector<bool> patch_poly_enqueued(m0_polygons.size(), false);
|
|
std::queue<int> flood_fill_queue; // for building patch using BFS
|
|
|
|
do {
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Associate cut-mesh polygons with patches of the graph
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
int graph_cur_patch_idx = (int)patches.size();
|
|
|
|
// counter to keep track of the number of patches discovered for
|
|
// the current SCS
|
|
|
|
//
|
|
// Here, we will pick an interior intersection halfedge (and its polygon) from
|
|
// which we can identify the [first patch] of the graph
|
|
std::tuple<int, int, int> graph_interior_ihalfedge_pool;
|
|
std::get<0>(graph_interior_ihalfedge_pool) = -1;
|
|
|
|
// find the [first] interior intersection-halfedge of a cut-mesh polygon
|
|
// which has not already been associated with a patch
|
|
while (!primary_interior_ihalfedge_pool.empty()) { // while seeds are not found
|
|
|
|
// pull an interior intersection-halfedge from the queue
|
|
std::vector<std::pair<int, int>>::iterator primary_interior_ihalfedge_pool_citer = primary_interior_ihalfedge_pool.end() - 1; // last element
|
|
// halfedge polygon index
|
|
const int potential_seed_poly_idx = primary_interior_ihalfedge_pool_citer->first;
|
|
// halfedge index in polygon
|
|
const int potential_seed_poly_he_idx = primary_interior_ihalfedge_pool_citer->second;
|
|
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
const bool poly_patch_is_known = parallel_find_in_map_by_key(
|
|
*input.scheduler,
|
|
m0_cm_poly_to_patch_idx.cbegin(),
|
|
m0_cm_poly_to_patch_idx.cend(), potential_seed_poly_idx)
|
|
!= m0_cm_poly_to_patch_idx.cend();
|
|
#else
|
|
// check if the polygon has already been associated with a patch
|
|
const bool poly_patch_is_known = m0_cm_poly_to_patch_idx.find(potential_seed_poly_idx) != m0_cm_poly_to_patch_idx.cend();
|
|
#endif
|
|
primary_interior_ihalfedge_pool_citer = primary_interior_ihalfedge_pool.erase(primary_interior_ihalfedge_pool_citer);
|
|
|
|
if (!poly_patch_is_known) {
|
|
// we can use the halfedge as a seed from which to starting point to build [a] patch
|
|
std::get<0>(graph_interior_ihalfedge_pool) = graph_cur_patch_idx;
|
|
std::get<1>(graph_interior_ihalfedge_pool) = potential_seed_poly_idx;
|
|
std::get<2>(graph_interior_ihalfedge_pool) = potential_seed_poly_he_idx;
|
|
break; // start patch discovery with the current seed
|
|
}
|
|
}
|
|
|
|
if (std::get<0>(graph_interior_ihalfedge_pool) == -1) {
|
|
break; // done
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// build the patch by flood-fill (BFS)
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
MCUT_ASSERT(patch_to_seed_interior_ihalfedge_idx.count(graph_cur_patch_idx) == 0);
|
|
patch_to_seed_interior_ihalfedge_idx[graph_cur_patch_idx] = std::get<2>(graph_interior_ihalfedge_pool);
|
|
|
|
MCUT_ASSERT(patch_to_seed_poly_idx.count(graph_cur_patch_idx) == 0);
|
|
patch_to_seed_poly_idx[graph_cur_patch_idx] = std::get<1>(graph_interior_ihalfedge_pool);
|
|
|
|
std::vector<int>& patch = patches[graph_cur_patch_idx]; // patch_insertion.first->second; // polygons of patch
|
|
patch.reserve(cs_face_count);
|
|
|
|
flood_fill_queue.push(std::get<1>(graph_interior_ihalfedge_pool)); // first polygon
|
|
patch_poly_enqueued[std::get<1>(graph_interior_ihalfedge_pool)] = true;
|
|
|
|
do { // each interation adds a polygon to the patch
|
|
|
|
// get the polygon at the front of the queue
|
|
const int graph_patch_poly_idx = flood_fill_queue.front();
|
|
flood_fill_queue.pop(); // graph_patch_poly_idx
|
|
|
|
// add polygon to patch
|
|
patch.push_back(graph_patch_poly_idx);
|
|
|
|
// relate polygon to patch
|
|
MCUT_ASSERT(m0_cm_poly_to_patch_idx.count(graph_patch_poly_idx) == 0);
|
|
m0_cm_poly_to_patch_idx[graph_patch_poly_idx] = graph_cur_patch_idx;
|
|
// std::pair<std::map<int, int>::const_iterator, bool> pair = m0_cm_poly_to_patch_idx.insert(std::make_pair(graph_patch_poly_idx, graph_cur_patch_idx)); // signifies that polygon has been associated with a patch
|
|
// MCUT_ASSERT(pair.second == true);
|
|
MCUT_ASSERT(m0_cm_poly_to_patch_idx.count(graph_patch_poly_idx) == 1);
|
|
|
|
//
|
|
// find adjacent polygons which share class 0,1,2 (o-->o, o-->x, x-->o) halfedges, and
|
|
// the class 3 (x-->x) halfedges which are [exterior/boundary] intersection-halfedges.
|
|
//
|
|
|
|
// the current polygon
|
|
const traced_polygon_t& graph_patch_poly = SAFE_ACCESS(m0_polygons, graph_patch_poly_idx);
|
|
|
|
// for each halfedge of the current polygon
|
|
for (traced_polygon_t::const_iterator poly_he_iter = graph_patch_poly.cbegin();
|
|
poly_he_iter != graph_patch_poly.cend();
|
|
++poly_he_iter) {
|
|
|
|
#if 0
|
|
const vd_t src_vertex = m0.source(*poly_he_iter);
|
|
const vd_t tgt_vertex = m0.target(*poly_he_iter);
|
|
bool is_ambiguious_boundary_edge_case = m0_is_intersection_point(src_vertex, ps_vtx_cnt) && m0_is_intersection_point(tgt_vertex, ps_vtx_cnt);
|
|
bool is_valid_ambiguious_boundary_edge = false;
|
|
|
|
if (is_ambiguious_boundary_edge_case)
|
|
{ // exterior edge with two intersection vertices (ambigious case arising from concave polyhedron cut)
|
|
|
|
//const hd_t src_coincident_ps_halfedge = SAFE_ACCESS(m0_ivtx_to_ps_edge, src_vertex);
|
|
//const hd_t tgt_ps_h = SAFE_ACCESS(m0_ivtx_to_ps_edge, tgt_vertex);
|
|
MCUT_ASSERT((size_t)src_vertex - ps_vtx_cnt < m0_ivtx_to_intersection_registry_entry.size() /*m0_ivtx_to_intersection_registry_entry.find(src_vertex) != m0_ivtx_to_intersection_registry_entry.cend()*/);
|
|
const std::pair<ed_t, fd_t> &src_vertex_ipair = SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, src_vertex - ps_vtx_cnt);
|
|
const ed_t src_ps_edge = src_vertex_ipair.first; // SAFE_ACCESS(m0_ivtx_to_ps_edge, src_vertex); //ps.edge(src_coincident_ps_halfedge);
|
|
|
|
MCUT_ASSERT((size_t)tgt_vertex - ps_vtx_cnt < m0_ivtx_to_intersection_registry_entry.size() /*m0_ivtx_to_intersection_registry_entry.find(tgt_vertex) != m0_ivtx_to_intersection_registry_entry.cend()*/);
|
|
const std::pair<ed_t, fd_t> &tgt_vertex_ipair = SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, tgt_vertex - ps_vtx_cnt);
|
|
const ed_t tgt_ps_edge = tgt_vertex_ipair.first; // SAFE_ACCESS(m0_ivtx_to_ps_edge, tgt_vertex); //ps.edge(tgt_ps_h);
|
|
|
|
is_valid_ambiguious_boundary_edge = (src_ps_edge == tgt_ps_edge);
|
|
}
|
|
#endif
|
|
const bool is_boundary_halfedge = m0_is_polygon_boundary_halfedge((*poly_he_iter), m0_num_cutpath_halfedges);
|
|
|
|
// "is the halfdge not along the cut-path"
|
|
if (is_boundary_halfedge /*!is_ambiguious_boundary_edge_case || is_valid_ambiguious_boundary_edge*/) {
|
|
// get the opposite halfedge which is used to trace the adjacent polygon
|
|
const hd_t poly_he_opp = m0.opposite(*poly_he_iter);
|
|
// get the coincident polygons (if any)
|
|
std::vector<std::vector<int>>::const_iterator find_iter = m0_h_to_ply.cbegin() + poly_he_opp; // m0_h_to_ply.find(poly_he_opp);
|
|
|
|
// check if "poly_he_opp" is used to trace a polygon i.e its not a border halfedge
|
|
if (find_iter->size() > 0 /*find_iter != m0_h_to_ply.cend()*/) {
|
|
|
|
MCUT_ASSERT(find_iter->size() == 1); // only used to trace a CCW cut-mesh polygon
|
|
|
|
// get the polygon which is traced with "poly_he_opp" i.e. the adjacent polygon we are looking for!
|
|
const int incident_poly = find_iter->front();
|
|
|
|
// mustbe cut-mesh polygon since we are only dealing with such polygons when building patches
|
|
MCUT_ASSERT(incident_poly >= traced_sm_polygon_count);
|
|
|
|
// if (!poly_already_in_patch)
|
|
{
|
|
// std::unordered_map<int, bool>::const_iterator qmap_fiter = patch_poly_enqueued.find(incident_poly);
|
|
const bool poly_already_queued = patch_poly_enqueued[incident_poly]; // qmap_fiter != patch_poly_enqueued.cend(); //std::find(flood_fill_queue.crbegin(), flood_fill_queue.crend(), incident_poly) != flood_fill_queue.crend();
|
|
if (!poly_already_queued) {
|
|
flood_fill_queue.push(incident_poly); // add adjacent polygon to bfs-queue
|
|
patch_poly_enqueued[incident_poly] = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} while (!flood_fill_queue.empty()); // while there are more adjacent polygons for building current patch
|
|
|
|
MCUT_ASSERT(!patch.empty()); // there has to be at least one polygon
|
|
|
|
// NOTE: at this stage, all patches/nodes of the current graph have been coloured i.e. we have bipartite graph of the patches (except if there is only one patch i.e. a floating patch).
|
|
} while (!primary_interior_ihalfedge_pool.empty()); // while there are more interior ihalfedges coincident to polygons which have not been associated with a patch (hence, there are remaining graphs of patches to be stitched)
|
|
|
|
patch_poly_enqueued.clear();
|
|
|
|
// NOTE: At this stage, we have identified all patches of the current graph
|
|
|
|
MCUT_ASSERT(patches.size() >= 1);
|
|
|
|
total_ccw_patch_count = (int)patches.size();
|
|
|
|
std::unordered_map<
|
|
int, // patch index
|
|
std::vector<int> // adjacent patches (i.e. sharing a cut-path)
|
|
>
|
|
graph_patch_to_adj_list;
|
|
|
|
MCUT_ASSERT(patch_discovery_seeds.size() % 2 == 0);
|
|
|
|
// To understand this loop see how "patch_discovery_seeds" is define above.
|
|
// We are effetcively using the seed polygon for each patch, to determing adjacency.
|
|
// "patch_discovery_seeds" is populated with two polygons are any one time,
|
|
// where these polygons shared a cutpath edge, and hence belong to the boundaries
|
|
// of their respective patches
|
|
for (int i = 0; i < (int)patch_discovery_seeds.size(); i += 2) {
|
|
const std::pair<int, int>& cur = patch_discovery_seeds[i];
|
|
const int cur_cm_traced_poly = cur.first;
|
|
const int cur_patch_idx = SAFE_ACCESS(m0_cm_poly_to_patch_idx, cur_cm_traced_poly);
|
|
|
|
const std::pair<int, int>& nxt = patch_discovery_seeds[(std::size_t)i + 1];
|
|
const int nxt_cm_traced_poly = nxt.first;
|
|
const int nxt_patch_idx = SAFE_ACCESS(m0_cm_poly_to_patch_idx, nxt_cm_traced_poly);
|
|
|
|
graph_patch_to_adj_list[cur_patch_idx].push_back(nxt_patch_idx);
|
|
graph_patch_to_adj_list[nxt_patch_idx].push_back(cur_patch_idx);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Identify which patches are interior and which are exterior (coloring)
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
// We will now sort the patches into two sets - interior and exterior.
|
|
// We do this by building an adjacency matrix and its square (^2) to produce
|
|
// a bipartite graph via coloring. The adjacency matrix represents the
|
|
// adjacency between patches (sharing a cut path).
|
|
//
|
|
|
|
matrix_t<> scs_adj_matrix((int)patches.size(), (int)patches.size()); // square
|
|
|
|
for (std::unordered_map<int, std::vector<int>>::const_iterator patch_iter = graph_patch_to_adj_list.cbegin();
|
|
patch_iter != graph_patch_to_adj_list.cend();
|
|
++patch_iter) {
|
|
|
|
const int row_id = patch_iter->first; // same as patch index
|
|
|
|
for (std::vector<int>::const_iterator adj_patch_iter = patch_iter->second.cbegin();
|
|
adj_patch_iter != patch_iter->second.cend();
|
|
++adj_patch_iter) {
|
|
|
|
const int col_id = *adj_patch_iter;
|
|
|
|
if (row_id == col_id) {
|
|
// our adjacency matrix is no self referent because patches
|
|
// do not connect to themselves!
|
|
continue;
|
|
}
|
|
|
|
scs_adj_matrix(row_id, col_id) = 1; // mark adjacent
|
|
}
|
|
}
|
|
|
|
const matrix_t<> scs_adj_matrix_sqrd = scs_adj_matrix * scs_adj_matrix;
|
|
|
|
// Here we do graph coloring using BFS
|
|
// NOTE: coloring is used to mark patches as either interior or exterior.
|
|
// Be aware that since we work only with the topology (connectivity), the
|
|
// notion color itself will not tell us whether a patch is interior or
|
|
// exterior. The coloring simply tells us that a patch belongs to one
|
|
// group or the other. One exception is when we have a floating-patch
|
|
// in which case it is possible to infer that the patch is interior.
|
|
// This is because floating patches are always defined by interior
|
|
// intersection-halfedges.
|
|
|
|
std::deque<int> graph_patch_coloring_queue;
|
|
// start coloring with the first patch
|
|
graph_patch_coloring_queue.push_back(0);
|
|
// "red" chosen arbitrarilly
|
|
std::vector<int>& red_nodes = SAFE_ACCESS(color_to_patch, 'A');
|
|
|
|
do { // color the current node/patch of the red set
|
|
const int graph_cur_colored_patch_idx = graph_patch_coloring_queue.front();
|
|
red_nodes.push_back(graph_cur_colored_patch_idx);
|
|
|
|
const int row_id = graph_cur_colored_patch_idx; // NOTE: no need to account for the fact that the number of patches accumulates since graph_1st_patch_idx == 0
|
|
|
|
// find adjacent patch using A^2 and push adj patch onto queue (if not already colored)
|
|
for (int col_id = 0; col_id < scs_adj_matrix_sqrd.cols(); ++col_id) {
|
|
|
|
if (row_id == col_id) {
|
|
continue; // we dont care about two-walks from a node back to itself
|
|
}
|
|
|
|
const unsigned int entry = scs_adj_matrix_sqrd(row_id, col_id);
|
|
|
|
if (entry > 0) // two-walk exists
|
|
{
|
|
const int graph_next_colored_patch_idx = col_id;
|
|
|
|
if ( // not already colored
|
|
std::find(red_nodes.cbegin(), red_nodes.cend(), graph_next_colored_patch_idx) == red_nodes.cend() &&
|
|
// not in queue
|
|
std::find(graph_patch_coloring_queue.cbegin(), graph_patch_coloring_queue.cend(), graph_next_colored_patch_idx) == graph_patch_coloring_queue.cend()) {
|
|
graph_patch_coloring_queue.push_back(graph_next_colored_patch_idx);
|
|
}
|
|
}
|
|
}
|
|
|
|
graph_patch_coloring_queue.pop_front(); // rm graph_cur_colored_patch_idx
|
|
|
|
} while (!graph_patch_coloring_queue.empty());
|
|
|
|
// color the remaining uncolored nodes
|
|
std::vector<int>& blue_nodes = SAFE_ACCESS(color_to_patch, 'B'); // i.e. blue patches
|
|
|
|
for (std::unordered_map<int, std::vector<int>>::const_iterator patch_iter = graph_patch_to_adj_list.cbegin();
|
|
patch_iter != graph_patch_to_adj_list.cend();
|
|
++patch_iter) {
|
|
|
|
const bool is_red = std::find(red_nodes.cbegin(), red_nodes.cend(), patch_iter->first) != red_nodes.cend();
|
|
|
|
if (!is_red) {
|
|
blue_nodes.push_back(patch_iter->first);
|
|
}
|
|
}
|
|
|
|
TIMESTACK_POP(); // &&&&&
|
|
|
|
// NOTE: at this stage, all strongly-connected-sets have been identified and colored (i.e via coloring, all nodes/patches have been associated with a side : interior or exterior)
|
|
|
|
MCUT_ASSERT(!patches.empty());
|
|
|
|
primary_interior_ihalfedge_pool.clear();
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Find the cut-mesh vertices that must not be duplicated
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
TIMESTACK_PUSH("Find non-duplicated cut-mesh vertices");
|
|
// In the case of a partial cut, the o-vertices of the cut-mesh are not duplicated
|
|
// e.g. those which reside interior to the sm
|
|
std::vector<vd_t> sm_interior_cs_border_vertices;
|
|
|
|
if (partial_cut_detected) {
|
|
|
|
//
|
|
// Here we save the cut-mesh border vertices (non-intersection points) which
|
|
// are on the interior (inside) of the src-mesh.
|
|
// These are needed for calculating properly-sealed connected components which
|
|
// have been partially cut. We use this informaion determine which vertices of
|
|
// the cut-mesh to not duplicate while allowing for the openings in the sealed
|
|
// the connected components.
|
|
//
|
|
MCUT_ASSERT(!cm_is_watertight);
|
|
|
|
/*
|
|
1. do while there exists a re-entrant vertex which has not been used to find an interior cut-mesh border vertices
|
|
a. get the ihalfedge whose source is <1> and it is used for tracing
|
|
b. do while target of next halfedge along border is not an intersection point
|
|
c. save target of current halfedge as one which we will not duplicate
|
|
d. go to next halfedge along border
|
|
*/
|
|
|
|
// populate the queue with all cut-mesh tip re-entrant vertices
|
|
std::deque<vd_t> reentrant_ivertex_queue(cm_border_reentrant_ivtx_list.cbegin(), cm_border_reentrant_ivtx_list.cend());
|
|
|
|
do {
|
|
|
|
// pull any re-entrant vertex from queue
|
|
const vd_t current_reentrant_ivertex = reentrant_ivertex_queue.front();
|
|
|
|
hd_t current_cs_border_he = hmesh_t::null_halfedge();
|
|
hd_t next_cs_border_he = hmesh_t::null_halfedge();
|
|
int current_cs_border_he_idx = -1;
|
|
int next_cs_border_he_idx = -1;
|
|
|
|
//
|
|
// find polygon, and its halfedge whose src vertex is the current re-entrant vertex
|
|
// "current_reentrant_ivertex", and the target is not an intersection point
|
|
// keep in mind that we are looking for the non-intersection points that lie
|
|
// inside the src-mesh - so that we dont duplicate them.
|
|
//
|
|
std::vector<traced_polygon_t>::const_iterator next_cs_border_he_poly_find_iter = std::find_if(
|
|
m0_polygons.cbegin() + traced_sm_polygon_count, // offset to start of traced cut-mesh polygons
|
|
m0_polygons.cend(),
|
|
[&](const traced_polygon_t& cs_poly) {
|
|
// for each halfedge of cut-mesh polygon
|
|
for (traced_polygon_t::const_iterator cs_poly_he_iter = cs_poly.cbegin();
|
|
cs_poly_he_iter != cs_poly.cend();
|
|
++cs_poly_he_iter) {
|
|
|
|
// check if the target is an intersection point
|
|
const vd_t tgt = m0.target(*cs_poly_he_iter);
|
|
const bool tgt_is_ivertex = m0_is_intersection_point(tgt, ps_vtx_cnt);
|
|
|
|
if (tgt_is_ivertex) { //..-->x
|
|
continue;
|
|
}
|
|
|
|
// is the halfedge on the border of the cut-mesh i.e. its opposite halfedge is not
|
|
// used to trace a polygon
|
|
const bool is_on_cs_border = SAFE_ACCESS(m0_h_to_ply, m0.opposite(*cs_poly_he_iter)).size() == 0; // m0_h_to_ply.find(m0.opposite(*cs_poly_he_iter)) == m0_h_to_ply.cend(); // opposite is used to traced a polygon
|
|
|
|
if (is_on_cs_border) {
|
|
|
|
// check if the src vertex of the current halfedge is a re-entrant vertex
|
|
// we search through the queue because it contains the tip re-entrant vertices
|
|
// that have not yet been visited (valid set).
|
|
// Note that this implies that src is also an intersection point
|
|
const vd_t src = m0.source(*cs_poly_he_iter);
|
|
const bool src_is_reentrant = std::find(reentrant_ivertex_queue.cbegin(), reentrant_ivertex_queue.cend(), src) != reentrant_ivertex_queue.cend();
|
|
|
|
if (src_is_reentrant) {
|
|
|
|
// we have found that first halfedge from which the remaining one(s)
|
|
// inside the src-mesh can be found
|
|
next_cs_border_he = *cs_poly_he_iter;
|
|
next_cs_border_he_idx = (int)std::distance(cs_poly.cbegin(), cs_poly_he_iter);
|
|
break;
|
|
} else {
|
|
continue;
|
|
}
|
|
} else {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
return (next_cs_border_he != hmesh_t::null_halfedge());
|
|
});
|
|
|
|
reentrant_ivertex_queue.pop_front(); // rm current_reentrant_ivertex
|
|
|
|
// we could not find a halfedge whose src vertex is the current re-entrant vertex
|
|
if (next_cs_border_he_poly_find_iter == m0_polygons.cend()) {
|
|
// happens when a single cut-mesh partially cuts the src-mesh whereby
|
|
// a single edge passes through 2 or more src-mesh faces e.g.
|
|
// tet vs triangle partial cut
|
|
continue;
|
|
}
|
|
|
|
// a reference to the polygon which is traced with the halfedge we found
|
|
std::vector<traced_polygon_t>::const_iterator current_cs_border_he_poly_find_iter = m0_polygons.cend();
|
|
|
|
//
|
|
// we will now walk along the border of the cut-mesh saving all non
|
|
// intersection points which must not be duplicated later
|
|
//
|
|
while (next_cs_border_he != hmesh_t::null_halfedge()) {
|
|
|
|
// current border halfedge
|
|
current_cs_border_he = next_cs_border_he;
|
|
// polygon of current border halfedge
|
|
current_cs_border_he_poly_find_iter = next_cs_border_he_poly_find_iter;
|
|
// index of current border halfedge
|
|
current_cs_border_he_idx = next_cs_border_he_idx;
|
|
|
|
// reset
|
|
next_cs_border_he = hmesh_t::null_halfedge();
|
|
next_cs_border_he_idx = -1;
|
|
next_cs_border_he_poly_find_iter = m0_polygons.cend();
|
|
|
|
// save the non-intersection point on the border
|
|
const vd_t current_cs_border_he_tgt = m0.target(current_cs_border_he);
|
|
sm_interior_cs_border_vertices.push_back(current_cs_border_he_tgt);
|
|
|
|
if (m0_is_intersection_point(current_cs_border_he_tgt, ps_vtx_cnt)) {
|
|
break; // done (finished walking along cut-mesh interior border)
|
|
}
|
|
|
|
//
|
|
// find next halfedge along the border
|
|
//
|
|
|
|
// const int& current_cs_border_he_poly_idx = SAFE_ACCESS(m0_h_to_ply, current_cs_border_he).front(); // NOTE: class-2 or class-1 ihalfedges are incident to only one polygon
|
|
|
|
// the current polygon
|
|
// const traced_polygon_t& current_cs_border_he_poly = *current_cs_border_he_poly_find_iter;
|
|
// get reference to the current border halfedge in the polygon
|
|
// const traced_polygon_t::const_iterator current_cs_border_he_find_iter = std::find(
|
|
// current_cs_border_he_poly.cbegin(), current_cs_border_he_poly.cend(), current_cs_border_he);
|
|
|
|
// halfedge must exist in the polygon because it is used for tracing
|
|
// MCUT_ASSERT(current_cs_border_he_find_iter != current_cs_border_he_poly.cend());
|
|
|
|
// const int current_cs_border_he_idx = std::distance(current_cs_border_he_poly.cbegin(), current_cs_border_he_find_iter);
|
|
|
|
// Here we now find the next border halfedge
|
|
// -----------------------------------------
|
|
|
|
// We do this by circulating around "current_cs_border_he_tgt" to find the next border halfedge
|
|
// starting from the next after the current halfedge (around the vertex)
|
|
std::vector<traced_polygon_t>::const_iterator next_he_poly_iter = current_cs_border_he_poly_find_iter;
|
|
std::vector<traced_polygon_t>::const_iterator cur_he_poly_iter = m0_polygons.cend();
|
|
int next_he_idx = wrap_integer(current_cs_border_he_idx + 1, 0, (int)next_he_poly_iter->size() - 1);
|
|
int cur_he_idx = -1;
|
|
hd_t next_he = next_he_poly_iter->at(next_he_idx);
|
|
hd_t cur_he = hmesh_t::null_halfedge();
|
|
|
|
do {
|
|
cur_he = next_he;
|
|
MCUT_ASSERT(cur_he != hmesh_t::null_halfedge());
|
|
next_he = hmesh_t::null_halfedge();
|
|
|
|
cur_he_idx = next_he_idx;
|
|
MCUT_ASSERT(cur_he_idx != -1);
|
|
next_he_idx = -1;
|
|
|
|
cur_he_poly_iter = next_he_poly_iter;
|
|
MCUT_ASSERT(cur_he_poly_iter != m0_polygons.cend());
|
|
next_he_poly_iter = m0_polygons.cend();
|
|
|
|
// the next halfedge descriptor itself
|
|
// const hd_t& cur_he = SAFE_ACCESS(current_cs_border_he_poly, cur_he_idx); // in the polygon of current_cs_border_he
|
|
|
|
// get the opposite of the next halfedge in order to enter the neighbouring
|
|
// polygon which has a border halfedge
|
|
const hd_t opp_of_cur_he = m0.opposite(cur_he);
|
|
|
|
bool opp_of_cur_he_is_border = SAFE_ACCESS(m0_h_to_ply, opp_of_cur_he).size() == 0; // m0_h_to_ply.find(opp_of_cur_he) == m0_h_to_ply.cend(); // opposite is used to traced a polygon
|
|
|
|
if (opp_of_cur_he_is_border) { // found!
|
|
next_cs_border_he = cur_he;
|
|
next_cs_border_he_idx = cur_he_idx;
|
|
next_cs_border_he_poly_find_iter = cur_he_poly_iter;
|
|
} else {
|
|
|
|
// get index of this neighouring polygon
|
|
MCUT_ASSERT(SAFE_ACCESS(m0_h_to_ply, opp_of_cur_he).size() > 0 /*m0_h_to_ply.find(opp_of_cur_he) != m0_h_to_ply.cend()*/);
|
|
const int& opp_of_cur_he_poly_idx = SAFE_ACCESS(m0_h_to_ply, opp_of_cur_he).front(); // NOTE: class-2 or class-1 ihalfedges are incident to only one polygon
|
|
// reference to the neighbour/adjacent polygon
|
|
std::vector<traced_polygon_t>::const_iterator opp_of_cur_he_poly_iter = m0_polygons.cbegin() + (opp_of_cur_he_poly_idx);
|
|
|
|
MCUT_ASSERT(opp_of_cur_he_poly_iter != m0_polygons.cend());
|
|
|
|
// get the neighbouring polygon itself
|
|
const traced_polygon_t& opp_of_cur_he_poly = *opp_of_cur_he_poly_iter;
|
|
// get the reference to the next of opposite of current halfedge
|
|
const traced_polygon_t::const_iterator opp_of_cur_he_find_iter = std::find(
|
|
opp_of_cur_he_poly.cbegin(), opp_of_cur_he_poly.cend(), opp_of_cur_he);
|
|
|
|
MCUT_ASSERT(opp_of_cur_he_find_iter != opp_of_cur_he_poly.cend());
|
|
|
|
// index of "next of opposite of current halfedge" in the neighbour polygon
|
|
const int opp_of_cur_he_idx = (int)std::distance(opp_of_cur_he_poly.cbegin(), opp_of_cur_he_find_iter);
|
|
// get the next halfedge, which will be on the border
|
|
const int next_of_opp_of_cur_he_idx = wrap_integer(opp_of_cur_he_idx + 1, 0, (int)opp_of_cur_he_poly.size() - 1);
|
|
|
|
MCUT_ASSERT(next_of_opp_of_cur_he_idx < (int)opp_of_cur_he_poly.size());
|
|
const hd_t& next_of_opp_of_cur_he = SAFE_ACCESS(opp_of_cur_he_poly, next_of_opp_of_cur_he_idx); // in the polygon of opp_of_next_he_poly
|
|
const hd_t opp_of_next_of_opp_of_cur_he = m0.opposite(next_of_opp_of_cur_he);
|
|
bool opp_of_next_of_opp_of_cur_he_is_border = SAFE_ACCESS(m0_h_to_ply, opp_of_next_of_opp_of_cur_he).size() == 0; // m0_h_to_ply.find(opp_of_next_of_opp_of_cur_he) == m0_h_to_ply.cend(); // opposite is used to traced a polygon
|
|
|
|
// bool opp_of_next_of_opp_of_cur_he_is_border = m0_h_to_ply.find(opp_of_next_of_opp_of_cur_he) == m0_h_to_ply.cend(); // opposite is used to traced a polygon
|
|
|
|
if (opp_of_next_of_opp_of_cur_he_is_border) { // found!
|
|
next_cs_border_he = next_of_opp_of_cur_he;
|
|
next_cs_border_he_idx = next_of_opp_of_cur_he_idx;
|
|
next_cs_border_he_poly_find_iter = opp_of_cur_he_poly_iter;
|
|
} // else if (opp_of_next_of_opp_of_cur_he_is_border) {
|
|
|
|
// this is an edge-case:
|
|
// simple partial-cut intersection where an edge (of the cut-mesh) intersects two faces of the src-mesh
|
|
|
|
// get the target of the halfedge along the border of the adjacent polygon
|
|
// const vd_t next_of_opp_of_cur_he_tgt = m0.target(next_of_opp_of_cur_he);
|
|
|
|
// MCUT_ASSERT(m0_is_intersection_point(next_of_opp_of_cur_he_tgt, ps_vtx_cnt));
|
|
|
|
// break;
|
|
//}
|
|
else {
|
|
|
|
const int& poly_idx = SAFE_ACCESS(m0_h_to_ply, opp_of_next_of_opp_of_cur_he).front(); // NOTE: class-2 or class-1 ihalfedges are incident to only one polygon
|
|
//
|
|
next_he_poly_iter = m0_polygons.cbegin() + (poly_idx);
|
|
MCUT_ASSERT(next_he_poly_iter != m0_polygons.cend());
|
|
const traced_polygon_t& poly = *next_he_poly_iter;
|
|
const traced_polygon_t::const_iterator he_find_iter = std::find(poly.cbegin(), poly.cend(), opp_of_next_of_opp_of_cur_he);
|
|
MCUT_ASSERT(he_find_iter != poly.cend());
|
|
const int idx = (int)std::distance(poly.cbegin(), he_find_iter);
|
|
// .. need to start from next-after, otherwise we end up in an infinite loop!
|
|
// see top of current do-while loop: i.e. this -> "opp_of_cur_he = m0.opposite(cur_he);" would
|
|
// bring us back into the current polygon
|
|
next_he_idx = wrap_integer(idx + 1, 0, (int)poly.size() - 1);
|
|
MCUT_ASSERT(next_he_idx < (int)poly.size());
|
|
// set next
|
|
// --------
|
|
next_he = poly[next_he_idx];
|
|
}
|
|
}
|
|
|
|
// while the next border halfedge has not been found OR
|
|
// if the next border he which is found is not actually equal to the current he
|
|
} while (next_cs_border_he == hmesh_t::null_halfedge());
|
|
} // while (...)
|
|
|
|
} while (!reentrant_ivertex_queue.empty());
|
|
} // if (partial_cut_detected) {
|
|
|
|
cm_border_reentrant_ivtx_list.clear(); // free
|
|
|
|
TIMESTACK_POP();
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Infer patch location (inside/outside) based on graph coloring
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
// Here we will now explicitly association location information to the graph
|
|
// color. Thus for each color 'A', or 'B' we find out if it means "interior" or
|
|
// "exterior" - in the geometric sense. Bare in mind that till this point the
|
|
// notion graph node color was only used to classify patches into two sets.
|
|
// The intuition behind this classification was that "there is two types" of
|
|
// cut-mesh patches, 1) those which are used to seal the interior of the source-mesh
|
|
// and 2) those used to seal the exterior". So here, we find out whether 'A' means
|
|
// "interior" or "exterior", and vice versa for 'B'.
|
|
//
|
|
// DETAIL: Relying on topological alone to infer patch location is insufficient to
|
|
// determine whether a patch lies inside or outside the source-mesh (with the exception
|
|
// of floating patches). There is ambiguity which prevents us from knowing exactly
|
|
// what location each color 'A' or 'B' pertains to.
|
|
//
|
|
|
|
TIMESTACK_PUSH("Infer patch color to location");
|
|
|
|
std::map<char, cm_patch_location_t> patch_color_label_to_location;
|
|
|
|
// if no exterior cut-mesh polygons where found using re-entrant vertices
|
|
if (known_exterior_cm_polygons.empty()) {
|
|
//
|
|
// entering this scope means that we have a floating patch.
|
|
// Failing to find any exterior patch(es) occurs only due the fact the
|
|
// the cut-mesh intersectioned the src-mesh, but no edge in the cut-mesh
|
|
// intersected a face of the source-mesh
|
|
//
|
|
// MCUT_ASSERT(patches.size() == 1);
|
|
|
|
// What we will do then is assign cut-mesh polygons a value of
|
|
// cm_patch_location_t::INSIDE since floating patches
|
|
// are always interior
|
|
|
|
const int patch_idx = patches.cbegin()->first;
|
|
|
|
MCUT_ASSERT(patches.cbegin()->second.size() == 1); // should only be one polygon due to fact of having floating patch
|
|
|
|
// find the colored entry containing the patch
|
|
std::map<char, std::vector<int>>::const_iterator color_to_ccw_patches_find_iter = std::find_if(
|
|
color_to_patch.cbegin(),
|
|
color_to_patch.cend(),
|
|
[&](const std::pair<char, std::vector<int>>& e) {
|
|
return std::find(e.second.cbegin(), e.second.cend(), patch_idx) != e.second.cend();
|
|
});
|
|
|
|
// all patches must be associated with a patch by this point
|
|
MCUT_ASSERT(color_to_ccw_patches_find_iter != color_to_patch.cend());
|
|
|
|
//
|
|
// We have successively inferred that the color label ('A' or 'B')
|
|
// associated with the floating patch corresponds to "interior"
|
|
//
|
|
const char color_label = color_to_ccw_patches_find_iter->first;
|
|
patch_color_label_to_location.insert(std::make_pair(color_label, cm_patch_location_t::INSIDE));
|
|
|
|
// So, given that we know what the other color label is, we can infer
|
|
// the location associaed with the remaining color
|
|
patch_color_label_to_location.insert(std::make_pair(color_label == 'A' ? 'B' : 'A', cm_patch_location_t::OUTSIDE));
|
|
} else {
|
|
//
|
|
// Here, we know the at least one polygon which lies on the exterior of the
|
|
// source-mesh.
|
|
// So lets find the patch which contains any such polygon and label this patch
|
|
// as being "exterior".
|
|
//
|
|
|
|
#if 1
|
|
// std::vector<std::pair<int /*poly*/, int /*he idx*/>> known_exterior_cm_polygons;
|
|
const std::unordered_map<int /*poly*/, int /*he idx*/>::const_iterator known_exterior_cm_polygon = known_exterior_cm_polygons.cbegin();
|
|
MCUT_ASSERT(m0_cm_poly_to_patch_idx.find(known_exterior_cm_polygon->first) != m0_cm_poly_to_patch_idx.cend());
|
|
|
|
// get the patch containing the polygon
|
|
const int patch_idx = SAFE_ACCESS(m0_cm_poly_to_patch_idx, known_exterior_cm_polygon->first);
|
|
|
|
// get the color of the patch
|
|
std::map<char, std::vector<int>>::const_iterator color_to_ccw_patches_find_iter = std::find_if(
|
|
color_to_patch.cbegin(),
|
|
color_to_patch.cend(),
|
|
[&](const std::pair<char, std::vector<int>>& e) {
|
|
return std::find(e.second.cbegin(), e.second.cend(), patch_idx) != e.second.cend();
|
|
});
|
|
|
|
MCUT_ASSERT(color_to_ccw_patches_find_iter != color_to_patch.cend());
|
|
|
|
// thus, the color of the patch means it is an "exterior" patch because it contains an exterior
|
|
// polygon
|
|
const char color_label = color_to_ccw_patches_find_iter->first;
|
|
patch_color_label_to_location.insert(std::make_pair(color_label, cm_patch_location_t::OUTSIDE));
|
|
|
|
// infer the opposite color label's meaning
|
|
patch_color_label_to_location.insert(std::make_pair(color_label == 'A' ? 'B' : 'A', cm_patch_location_t::INSIDE));
|
|
#else
|
|
// for each cut-mesh polygon
|
|
for (std::map<int, int>::const_iterator cs_poly_to_patch_idx_iter = m0_cm_poly_to_patch_idx.cbegin();
|
|
cs_poly_to_patch_idx_iter != m0_cm_poly_to_patch_idx.cend();
|
|
++cs_poly_to_patch_idx_iter) {
|
|
|
|
// get index of polygon
|
|
const int cs_poly_idx = cs_poly_to_patch_idx_iter->first;
|
|
|
|
// check if polygon is an exterior cut-mesh polygon
|
|
const std::vector<std::pair<int, int>>::const_iterator known_exterior_cs_polygons_find_iter = std::find_if(
|
|
known_exterior_cm_polygons.cbegin(),
|
|
known_exterior_cm_polygons.cend(),
|
|
[&](const std::pair<int, int>& e) { return e.first == cs_poly_idx; });
|
|
const bool poly_is_known_exterior_cs_polygon = (known_exterior_cs_polygons_find_iter != known_exterior_cm_polygons.cend());
|
|
|
|
if (poly_is_known_exterior_cs_polygon) {
|
|
// get the patch containing the polygon
|
|
const int patch_idx = cs_poly_to_patch_idx_iter->second;
|
|
|
|
// get the color of the patch
|
|
std::map<char, std::vector<int>>::const_iterator color_to_ccw_patches_find_iter = std::find_if(
|
|
color_to_patch.cbegin(),
|
|
color_to_patch.cend(),
|
|
[&](const std::pair<char, std::vector<int>>& e) {
|
|
return std::find(e.second.cbegin(), e.second.cend(), patch_idx) != e.second.cend();
|
|
});
|
|
|
|
MCUT_ASSERT(color_to_ccw_patches_find_iter != color_to_patch.cend());
|
|
|
|
// thus, the color of the patch means it is an "exterior" patch because it contains an exterior
|
|
// polygon
|
|
const char color_label = color_to_ccw_patches_find_iter->first;
|
|
patch_color_label_to_location.insert(std::make_pair(color_label, cm_patch_location_t::OUTSIDE));
|
|
|
|
// infer the opposite color label's meaning
|
|
patch_color_label_to_location.insert(std::make_pair(color_label == 'A' ? 'B' : 'A', cm_patch_location_t::INSIDE));
|
|
|
|
break; // done (only need to find the first exterior polygon for use to know everything else)
|
|
}
|
|
}
|
|
|
|
#endif
|
|
}
|
|
MCUT_ASSERT(!patch_color_label_to_location.empty());
|
|
|
|
known_exterior_cm_polygons.clear();
|
|
// m0_cm_poly_to_patch_idx.clear();
|
|
#if 0
|
|
// dump
|
|
|
|
|
|
for (std::map<char, std::vector<int>>::const_iterator color_to_ccw_patches_iter = color_to_patch.cbegin(); color_to_ccw_patches_iter != color_to_patch.cend(); ++color_to_ccw_patches_iter)
|
|
{
|
|
const char color_label = color_to_ccw_patches_iter->first;
|
|
//const cm_patch_location_t color_label_dye = SAFE_ACCESS(patch_color_label_to_location, color_label);
|
|
|
|
|
|
}
|
|
|
|
#endif
|
|
TIMESTACK_POP();
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Create reverse patches
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
TIMESTACK_PUSH("Create reversed patches");
|
|
|
|
const int traced_polygon_count = (int)m0_polygons.size(); // does not include the reversed cut-mesh polygons
|
|
|
|
// note: reversed patches are called "cw" patches (for assumed clockwise based on input meshes)
|
|
|
|
std::map<
|
|
char, // color tag
|
|
std::vector<int> // reversed patch index
|
|
>
|
|
color_to_cw_patch;
|
|
|
|
std::map<
|
|
int, // patch index
|
|
int // opposite patch index
|
|
>
|
|
patch_to_opposite;
|
|
|
|
// for each color
|
|
for (std::map<char, std::vector<int>>::const_iterator color_to_ccw_patches_iter = color_to_patch.cbegin();
|
|
color_to_ccw_patches_iter != color_to_patch.cend();
|
|
++color_to_ccw_patches_iter) {
|
|
|
|
// const char color_id = color_to_ccw_patches_iter->first;
|
|
|
|
//
|
|
|
|
// add entry
|
|
MCUT_ASSERT(color_to_cw_patch.count(color_to_ccw_patches_iter->first) == 0);
|
|
color_to_cw_patch[color_to_ccw_patches_iter->first] = std::vector<int>();
|
|
// std::pair<std::map<char, std::vector<int>>::iterator, bool> color_to_cw_patch_insertion = color_to_cw_patch.insert(std::make_pair(color_to_ccw_patches_iter->first, std::vector<int>()));
|
|
// MCUT_ASSERT(color_to_cw_patch_insertion.second == true);
|
|
MCUT_ASSERT(color_to_cw_patch.count(color_to_ccw_patches_iter->first) == 1);
|
|
|
|
// list of reversed patches with current color
|
|
std::vector<int>& cw_patch_color = SAFE_ACCESS(color_to_cw_patch, color_to_ccw_patches_iter->first);
|
|
|
|
// for each patch with current color
|
|
for (std::vector<int>::const_iterator patch_iter = color_to_ccw_patches_iter->second.cbegin();
|
|
patch_iter != color_to_ccw_patches_iter->second.cend();
|
|
++patch_iter) {
|
|
|
|
const int patch_idx = *patch_iter;
|
|
|
|
const std::vector<int>& patch = SAFE_ACCESS(patches, patch_idx);
|
|
|
|
//
|
|
// create reversed patch
|
|
//
|
|
|
|
const int cw_patch_idx = (int)patches.size(); // index of reversed version
|
|
|
|
// relate patch to opposite
|
|
patch_to_opposite[patch_idx] = cw_patch_idx;
|
|
patch_to_opposite[cw_patch_idx] = patch_idx;
|
|
|
|
MCUT_ASSERT(patches.count(cw_patch_idx) == 0);
|
|
patches[cw_patch_idx] = std::vector<int>();
|
|
// std::pair<std::map<int, std::vector<int>>::iterator, bool> patch_insertion = patches.insert(std::make_pair(cw_patch_idx, std::vector<int>()));
|
|
// MCUT_ASSERT(patch_insertion.second == true);
|
|
MCUT_ASSERT(patches.count(cw_patch_idx) == 1);
|
|
|
|
std::vector<int>& cw_patch = SAFE_ACCESS(patches, cw_patch_idx);
|
|
|
|
// add to list of patches with current color
|
|
cw_patch_color.push_back(cw_patch_idx);
|
|
|
|
/*
|
|
for each polygon in patch
|
|
if is floating-patch polygon
|
|
find the opposite polygon (which already exists)
|
|
add opposite polygon into patch
|
|
else
|
|
create reversed version and update data structures
|
|
*/
|
|
|
|
// number of polygons in the ccw patch
|
|
const int initial_patch_size = (int)patch.size();
|
|
|
|
// for each polygon in the ccw patch
|
|
for (int ccw_patch_iter = 0; ccw_patch_iter < initial_patch_size; ++ccw_patch_iter) {
|
|
|
|
// get the polygon index
|
|
const int ccw_patch_poly_idx = SAFE_ACCESS(patch, ccw_patch_iter);
|
|
|
|
// all polygon are stored in the same array so we can use that to deduce
|
|
// index of new reversed polygon
|
|
int cw_poly_idx = (int)m0_polygons.size();
|
|
|
|
// get the normal polygon
|
|
const traced_polygon_t& patch_poly = SAFE_ACCESS(m0_polygons, ccw_patch_poly_idx);
|
|
#if 0
|
|
const bool is_floating_patch = SAFE_ACCESS(patch_to_floating_flag, patch_idx);
|
|
|
|
if (is_floating_patch)
|
|
{
|
|
//
|
|
// the reversed polygon of the current normal polygon already exists.
|
|
// So we can just use that. Note: this is because the polygon tracing
|
|
// that we did earlier (in "m0") always traces two versions of a polygon
|
|
// whose halfedges are all interior intersection-halfedges (x-->x)
|
|
//
|
|
|
|
// find opposite polygon
|
|
const hd_t &coincident_halfedge = patch_poly.front(); // can be any
|
|
const hd_t coincident_halfedge_opp = m0.opposite(coincident_halfedge);
|
|
const std::vector<int> &coincident_polys = SAFE_ACCESS(m0_h_to_ply, coincident_halfedge_opp);
|
|
// find coincident cut-mesh polygon
|
|
const std::vector<int>::const_iterator coincident_polys_find_iter = std::find_if(
|
|
coincident_polys.cbegin(),
|
|
coincident_polys.cend(),
|
|
[&](const int &e)
|
|
{ return e >= traced_sm_polygon_count && e < traced_polygon_count; });
|
|
|
|
MCUT_ASSERT(coincident_polys_find_iter != coincident_polys.cend());
|
|
|
|
const int patch_poly_opp = *coincident_polys_find_iter;
|
|
cw_poly_idx = patch_poly_opp; // found opposite polygon
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
|
|
//
|
|
// the current ccw polygon does not form a floating patch,
|
|
// so we calculate the reversed version by retracing the
|
|
// connectivity in reverse order
|
|
//
|
|
|
|
traced_polygon_t cw_poly;
|
|
cw_poly.reserve(patch_poly.size());
|
|
traced_polygon_t tmp;
|
|
tmp.reserve(patch_poly.size());
|
|
|
|
// for each halfedge of the ccw polygon
|
|
for (traced_polygon_t::const_iterator patch_poly_he_iter = patch_poly.cbegin();
|
|
patch_poly_he_iter != patch_poly.cend();
|
|
++patch_poly_he_iter) {
|
|
|
|
// get halfedge descriptor
|
|
const hd_t& patch_poly_he = *patch_poly_he_iter;
|
|
// get the opposite halfedge
|
|
const hd_t patch_poly_he_opp = m0.opposite(patch_poly_he);
|
|
// add into list defining reversed polygon
|
|
tmp.push_back(patch_poly_he_opp);
|
|
// check if another cut-mesh polygon is traced with this opposite halfedge.
|
|
std::vector<std::vector<int>>::iterator find_iter = m0_h_to_ply.begin() + patch_poly_he_opp; // m0_h_to_ply.find(patch_poly_he_opp);
|
|
#if 0
|
|
if (find_iter == m0_h_to_ply.end())
|
|
{ // "patch_poly_he_opp" not used to trace any polygon
|
|
//
|
|
// we only enter this scope of the halfedge "patch_poly_he_opp" is a
|
|
// border halfedge which is not used to trace a cut-mesh polygon.
|
|
|
|
// add entry for the halfedge
|
|
std::pair<std::map<hd_t, std::vector<int>>::iterator, bool> m0_he_to_poly_idx_insertion = m0_h_to_ply.insert(std::make_pair(patch_poly_he_opp, std::vector<int>()));
|
|
|
|
MCUT_ASSERT(m0_he_to_poly_idx_insertion.second == true);
|
|
|
|
find_iter = m0_he_to_poly_idx_insertion.first;
|
|
}
|
|
#endif
|
|
// associate "patch_poly_he_opp" with the new reversed polygon
|
|
find_iter->push_back(cw_poly_idx);
|
|
}
|
|
|
|
MCUT_ASSERT(tmp.size() == patch_poly.size());
|
|
|
|
// reverse the order to ensure correct winding, last halfedge for goes to beginning, and so on...
|
|
for (int h = 0; h < (int)tmp.size(); ++h) {
|
|
const int index = (int)tmp.size() - 1 - h;
|
|
cw_poly.push_back(SAFE_ACCESS(tmp, index));
|
|
}
|
|
|
|
//{
|
|
// for (traced_polygon_t::const_iterator cw_poly_he_iter = cw_poly.cbegin(); cw_poly_he_iter != cw_poly.cend(); ++cw_poly_he_iter) {
|
|
/// }
|
|
//}
|
|
|
|
MCUT_ASSERT(m0.source(cw_poly.front()) == m0.target(cw_poly.back())); // must form loop
|
|
|
|
m0_polygons.push_back(cw_poly); // save the new polygon!
|
|
}
|
|
|
|
// the new polygon's index as being part of the patch
|
|
cw_patch.push_back(cw_poly_idx);
|
|
|
|
// map the reversed polygon to its patch
|
|
MCUT_ASSERT(m0_cm_poly_to_patch_idx.count(cw_poly_idx) == 0);
|
|
m0_cm_poly_to_patch_idx[cw_poly_idx] = cw_patch_idx;
|
|
|
|
// map the reversed polygon to the same ps-face as its ccw counterpart!
|
|
if (m0_to_ps_face.count(cw_poly_idx) == 0) {
|
|
// must not be a floating patch because such ccw and cw patch polygons are created and mapped during "m0" tracing stage
|
|
// MCUT_ASSERT(patch_to_floating_flag.count(cw_poly_idx) == false);
|
|
MCUT_ASSERT(m0_to_ps_face.count(ccw_patch_poly_idx) == 1);
|
|
// both polygon will have originated from the same ps-face!
|
|
m0_to_ps_face[cw_poly_idx] = SAFE_ACCESS(m0_to_ps_face, ccw_patch_poly_idx);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TIMESTACK_POP();
|
|
|
|
// number of reversed cut-mesh polygons
|
|
// const int cw_cs_poly_count = ((int)m0_polygons.size() - traced_polygon_count);
|
|
|
|
//
|
|
|
|
// NOTE: at this stage, all patch polygons (ccw/normal) also have an opposite (cw/reversed)
|
|
|
|
// merge the opposite color_to_patch data structure
|
|
|
|
TIMESTACK_PUSH("merge opposite color to path data structures");
|
|
|
|
// for each color
|
|
for (std::map<char, std::vector<int>>::const_iterator color_to_cw_patch_iter = color_to_cw_patch.cbegin();
|
|
color_to_cw_patch_iter != color_to_cw_patch.cend();
|
|
++color_to_cw_patch_iter) {
|
|
|
|
const char color_value = color_to_cw_patch_iter->first;
|
|
|
|
// get reversed patches
|
|
const std::vector<int>& colored_cw_patches = color_to_cw_patch_iter->second;
|
|
// get normal patches
|
|
std::vector<int>& colored_patches = SAFE_ACCESS(color_to_patch, color_value);
|
|
// merge
|
|
colored_patches.insert(colored_patches.end(), colored_cw_patches.cbegin(), colored_cw_patches.cend()); // merge
|
|
|
|
// dump
|
|
if (input.verbose) {
|
|
|
|
for (std::vector<int>::const_iterator colored_patch_iter = colored_patches.cbegin();
|
|
colored_patch_iter != colored_patches.cend();
|
|
++colored_patch_iter) {
|
|
|
|
const int patch_idx = *colored_patch_iter;
|
|
const std::vector<int>& patch = SAFE_ACCESS(patches, patch_idx);
|
|
// const int is_ccw = (int)(std::distance(colored_patches.cbegin(), colored_patch_iter) < (int)(patch.size() / 2));
|
|
|
|
//
|
|
|
|
for (std::vector<int>::const_iterator patch_poly_iter = patch.cbegin(); patch_poly_iter != patch.cend(); ++patch_poly_iter) {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TIMESTACK_POP();
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// save the patches into the output
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
TIMESTACK_PUSH("Save patches");
|
|
|
|
// for each color
|
|
for (std::map<char, std::vector<int>>::const_iterator color_to_patches_iter = color_to_patch.cbegin();
|
|
color_to_patches_iter != color_to_patch.cend();
|
|
++color_to_patches_iter) {
|
|
|
|
const char color_id = color_to_patches_iter->first;
|
|
|
|
// for each patch with current color
|
|
for (std::vector<int>::const_iterator patch_iter = color_to_patches_iter->second.cbegin();
|
|
patch_iter != color_to_patches_iter->second.cend();
|
|
++patch_iter) {
|
|
|
|
const int cur_patch_idx = *patch_iter;
|
|
|
|
const cm_patch_location_t& patch_location = SAFE_ACCESS(patch_color_label_to_location, color_id);
|
|
if ((patch_location == cm_patch_location_t::INSIDE && !input.keep_inside_patches) || //
|
|
(patch_location == cm_patch_location_t::OUTSIDE && !input.keep_outside_patches)) {
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// create mesh for patch
|
|
//
|
|
std::shared_ptr<hmesh_t> patch_mesh = std::shared_ptr<hmesh_t>(new hmesh_t);
|
|
patch_mesh->reserve_for_additional_elements(cs_face_count);
|
|
|
|
std::unordered_map<
|
|
vd_t, // vertex descriptor in "m0"
|
|
vd_t // vertex descriptor in "patch_mesh"
|
|
>
|
|
m0_to_patch_mesh_vertex;
|
|
|
|
m0_to_patch_mesh_vertex.reserve(m0.number_of_vertices());
|
|
|
|
// NOTE: ccw/normal patches are created before their reversed counterparts (hence the modulo Operator trick)
|
|
|
|
// is the a normal patch
|
|
const bool is_ccw_patch = ((cur_patch_idx % total_ccw_patch_count) == cur_patch_idx);
|
|
const cm_patch_winding_order_t patch_descriptor = is_ccw_patch ? cm_patch_winding_order_t::DEFAULT : cm_patch_winding_order_t::REVERSE;
|
|
const std::string cs_patch_descriptor_str = to_string(patch_descriptor);
|
|
|
|
// get the patch's polygons
|
|
const std::vector<int>& patch = SAFE_ACCESS(patches, cur_patch_idx);
|
|
|
|
//
|
|
// add vertices into patch mesh
|
|
//
|
|
|
|
std::vector<vd_t> seam_vertices; // vertices along cutpath
|
|
|
|
std::unordered_map<vd_t, vd_t> patch_to_m0_vertex;
|
|
patch_to_m0_vertex.reserve(cs_face_count);
|
|
|
|
// for each polygon in the patch
|
|
for (std::vector<int>::const_iterator patch_poly_iter = patch.cbegin(); patch_poly_iter != patch.cend(); ++patch_poly_iter) {
|
|
|
|
const int& patch_poly_idx = *patch_poly_iter;
|
|
const traced_polygon_t& patch_poly = SAFE_ACCESS(m0_polygons, patch_poly_idx);
|
|
|
|
// for each halfedge of polygon
|
|
for (traced_polygon_t::const_iterator patch_poly_he_iter = patch_poly.cbegin();
|
|
patch_poly_he_iter != patch_poly.cend();
|
|
++patch_poly_he_iter) {
|
|
|
|
const vd_t m0_vertex = m0.target(*patch_poly_he_iter);
|
|
const bool vertex_already_mapped = m0_to_patch_mesh_vertex.find(m0_vertex) != m0_to_patch_mesh_vertex.cend();
|
|
|
|
if (!vertex_already_mapped) {
|
|
// map from "m0" to "patch_mesh" descriptor
|
|
const vd_t& patch_mesh_vertex = patch_mesh->add_vertex(m0.vertex(m0_vertex));
|
|
|
|
MCUT_ASSERT(patch_mesh_vertex != hmesh_t::null_halfedge());
|
|
|
|
m0_to_patch_mesh_vertex.insert(std::make_pair(m0_vertex, patch_mesh_vertex));
|
|
patch_to_m0_vertex.insert(std::make_pair(patch_mesh_vertex, m0_vertex));
|
|
|
|
// mark if is seam vertex
|
|
if (m0_is_intersection_point(m0_vertex, ps_vtx_cnt)) {
|
|
seam_vertices.push_back(patch_mesh_vertex); // seam vertices are intersection points
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
MCUT_ASSERT(!seam_vertices.empty());
|
|
|
|
//
|
|
// add faces into patch mesh
|
|
//
|
|
|
|
std::unordered_map<fd_t, int> patch_to_m0_face;
|
|
patch_to_m0_face.reserve(patch.size());
|
|
|
|
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
auto fn_remap_face_vertices = [&](std::vector<int>::const_iterator block_start_, std::vector<int>::const_iterator block_end_) {
|
|
std::vector<std::pair<int, std::vector<vd_t>>> result;
|
|
result.resize(std::distance(block_start_, block_end_));
|
|
|
|
uint32_t counter = 0;
|
|
// for each polygon
|
|
for (std::vector<int>::const_iterator patch_poly_iter = block_start_; patch_poly_iter != block_end_; ++patch_poly_iter) {
|
|
std::vector<vd_t>& remapped_poly_vertices = result[counter].second; // redefined face using "patch_mesh" descriptors
|
|
const int& patch_poly_idx = *patch_poly_iter;
|
|
result[counter].first = patch_poly_idx;
|
|
const traced_polygon_t& patch_poly = SAFE_ACCESS(m0_polygons, patch_poly_idx);
|
|
|
|
remapped_poly_vertices.reserve(patch_poly.size());
|
|
|
|
// for each halfedge
|
|
for (traced_polygon_t::const_iterator patch_poly_he_iter = patch_poly.cbegin();
|
|
patch_poly_he_iter != patch_poly.cend();
|
|
++patch_poly_he_iter) {
|
|
const vd_t m0_vertex = m0.target(*patch_poly_he_iter);
|
|
const vd_t patch_mesh_vertex = SAFE_ACCESS(m0_to_patch_mesh_vertex, m0_vertex);
|
|
remapped_poly_vertices.push_back(patch_mesh_vertex);
|
|
}
|
|
|
|
counter++;
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
std::vector<std::future<std::vector<std::pair<int, std::vector<vd_t>>>>> futures;
|
|
std::vector<std::pair<int, std::vector<vd_t>>> partial_res;
|
|
|
|
parallel_for(
|
|
*input.scheduler,
|
|
patch.cbegin(),
|
|
patch.cend(),
|
|
fn_remap_face_vertices,
|
|
partial_res, // output computed by master thread
|
|
futures);
|
|
|
|
auto add_face_and_save_mapping = [&](const std::pair<int, std::vector<vd_t>>& remapped_poly_info) {
|
|
const std::vector<vd_t>& remapped_poly_vertices = remapped_poly_info.second;
|
|
const int patch_poly_idx = remapped_poly_info.first;
|
|
|
|
const fd_t f = patch_mesh->add_face(remapped_poly_vertices);
|
|
|
|
MCUT_ASSERT(f != hmesh_t::null_face());
|
|
|
|
patch_to_m0_face.insert(std::make_pair(f, patch_poly_idx));
|
|
};
|
|
|
|
// to maintain face insertion order, we add according to the scheduling
|
|
|
|
for (int i = 0; i < (int)futures.size(); ++i) {
|
|
std::future<std::vector<std::pair<int, std::vector<vd_t>>>>& f = futures[i];
|
|
MCUT_ASSERT(f.valid());
|
|
std::vector<std::pair<int, std::vector<vd_t>>> future_result = f.get(); // "get()" is a blocking function
|
|
|
|
// for each polygon
|
|
for (uint32_t j = 0; j < (uint32_t)future_result.size(); ++j) {
|
|
const std::pair<int, std::vector<vd_t>>& remapped_poly_info = future_result[j];
|
|
add_face_and_save_mapping(remapped_poly_info);
|
|
}
|
|
}
|
|
|
|
// for each polygon
|
|
for (uint32_t j = 0; j < (uint32_t)partial_res.size(); ++j) {
|
|
const std::pair<int, std::vector<vd_t>>& remapped_poly_info = partial_res[j];
|
|
add_face_and_save_mapping(remapped_poly_info);
|
|
}
|
|
|
|
#else // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
std::vector<vd_t> remapped_poly_vertices; // redefined face using "patch_mesh" descriptors
|
|
// for each polygon
|
|
for (std::vector<int>::const_iterator patch_poly_iter = patch.cbegin(); patch_poly_iter != patch.cend(); ++patch_poly_iter) {
|
|
|
|
const int& patch_poly_idx = *patch_poly_iter;
|
|
const traced_polygon_t& patch_poly = SAFE_ACCESS(m0_polygons, patch_poly_idx);
|
|
|
|
remapped_poly_vertices.clear();
|
|
remapped_poly_vertices.reserve(patch_poly.size());
|
|
|
|
// for each halfedge
|
|
for (traced_polygon_t::const_iterator patch_poly_he_iter = patch_poly.cbegin();
|
|
patch_poly_he_iter != patch_poly.cend();
|
|
++patch_poly_he_iter) {
|
|
const vd_t m0_vertex = m0.target(*patch_poly_he_iter);
|
|
const vd_t patch_mesh_vertex = SAFE_ACCESS(m0_to_patch_mesh_vertex, m0_vertex);
|
|
remapped_poly_vertices.push_back(patch_mesh_vertex);
|
|
}
|
|
|
|
const fd_t f = patch_mesh->add_face(remapped_poly_vertices);
|
|
|
|
MCUT_ASSERT(f != hmesh_t::null_face());
|
|
|
|
patch_to_m0_face.insert(std::make_pair(f, patch_poly_idx));
|
|
}
|
|
|
|
#endif // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
|
|
if (input.verbose) {
|
|
dump_mesh(patch_mesh.get()[0], ("patch" + std::to_string(cur_patch_idx) + "." + to_string(patch_location) + "." + cs_patch_descriptor_str).c_str());
|
|
}
|
|
|
|
std::shared_ptr<output_mesh_info_t> omi = std::shared_ptr<output_mesh_info_t>(new output_mesh_info_t);
|
|
omi->mesh = patch_mesh;
|
|
omi->seam_vertices = std::move(seam_vertices);
|
|
|
|
if (input.populate_vertex_maps) {
|
|
// compute vertex mapping
|
|
// ----------------------
|
|
|
|
omi->data_maps.vertex_map.resize(patch_mesh->number_of_vertices());
|
|
|
|
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
{
|
|
auto fn_fill_vertex_map = [&](vertex_array_iterator_t block_start_, vertex_array_iterator_t block_end_) {
|
|
for (vertex_array_iterator_t v = block_start_; v != block_end_; ++v) {
|
|
MCUT_ASSERT(patch_to_m0_vertex.count(*v) == 1);
|
|
const vd_t as_m0_descr = SAFE_ACCESS(patch_to_m0_vertex, *v);
|
|
vd_t as_cm_descr = hmesh_t::null_vertex();
|
|
|
|
if ((int)as_m0_descr < (int)m0_to_ps_vtx.size() /*m0_to_ps_vtx.count(as_m0_descr) == 1*/) {
|
|
vd_t as_ps_descr = SAFE_ACCESS(patch_to_m0_vertex, *v);
|
|
|
|
MCUT_ASSERT((int)as_ps_descr < (int)ps_to_cm_vtx.size() /*ps_to_cm_vtx.count(as_ps_descr) == 1*/);
|
|
as_cm_descr = SAFE_ACCESS(ps_to_cm_vtx, as_ps_descr);
|
|
|
|
// add an offset which allows users to deduce which birth/origin mesh (source or cut mesh) a face (map value) belongs to.
|
|
as_cm_descr = static_cast<vd_t>(as_cm_descr + sm_vtx_cnt);
|
|
}
|
|
|
|
MCUT_ASSERT(SAFE_ACCESS(omi->data_maps.vertex_map, *v) == hmesh_t::null_vertex() /*omi->data_maps.vertex_map.count(*v) == 0*/);
|
|
omi->data_maps.vertex_map[*v] = as_cm_descr;
|
|
}
|
|
};
|
|
|
|
parallel_for(
|
|
*input.scheduler,
|
|
patch_mesh->vertices_begin(),
|
|
patch_mesh->vertices_end(),
|
|
fn_fill_vertex_map);
|
|
}
|
|
#else // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
for (vertex_array_iterator_t v = patch_mesh->vertices_begin(); v != patch_mesh->vertices_end(); ++v) {
|
|
MCUT_ASSERT(patch_to_m0_vertex.count(*v) == 1);
|
|
const vd_t as_m0_descr = SAFE_ACCESS(patch_to_m0_vertex, *v);
|
|
vd_t as_cm_descr = hmesh_t::null_vertex();
|
|
|
|
if ((int)as_m0_descr < (int)m0_to_ps_vtx.size() /*m0_to_ps_vtx.count(as_m0_descr) == 1*/) {
|
|
vd_t as_ps_descr = SAFE_ACCESS(patch_to_m0_vertex, *v);
|
|
|
|
MCUT_ASSERT((int)as_ps_descr < (int)ps_to_cm_vtx.size() /*ps_to_cm_vtx.count(as_ps_descr) == 1*/);
|
|
as_cm_descr = SAFE_ACCESS(ps_to_cm_vtx, as_ps_descr);
|
|
|
|
// add an offset which allows users to deduce which birth/origin mesh (source or cut mesh) a face (map value) belongs to.
|
|
as_cm_descr = static_cast<vd_t>(as_cm_descr + sm_vtx_cnt);
|
|
}
|
|
|
|
MCUT_ASSERT(SAFE_ACCESS(omi->data_maps.vertex_map, *v) == hmesh_t::null_vertex() /*omi->data_maps.vertex_map.count(*v) == 0*/);
|
|
omi->data_maps.vertex_map[*v] = as_cm_descr;
|
|
}
|
|
#endif // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
}
|
|
|
|
if (input.populate_face_maps) {
|
|
// compute face mapping
|
|
// ----------------------
|
|
|
|
omi->data_maps.face_map.resize(patch_mesh->number_of_faces());
|
|
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
{
|
|
auto fn_fill_face_map = [&](face_array_iterator_t block_start_, face_array_iterator_t block_end_) {
|
|
for (face_array_iterator_t f = block_start_; f != block_end_; ++f) {
|
|
MCUT_ASSERT(patch_to_m0_face.count(*f) == 1);
|
|
const int as_m0_descr = SAFE_ACCESS(patch_to_m0_face, *f);
|
|
|
|
MCUT_ASSERT(m0_to_ps_face.count(as_m0_descr) == 1);
|
|
const fd_t as_ps_descr = SAFE_ACCESS(m0_to_ps_face, as_m0_descr);
|
|
|
|
MCUT_ASSERT((int)as_ps_descr < (int)ps_to_cm_face.size() /*ps_to_cm_face.count(as_ps_descr) == 1*/);
|
|
fd_t as_cm_descr = SAFE_ACCESS(ps_to_cm_face, as_ps_descr);
|
|
|
|
// add an offset which allows users to deduce which birth/origin mesh (source or cut mesh) a face (map value) belongs to.
|
|
as_cm_descr = static_cast<fd_t>(as_cm_descr + sm_face_count);
|
|
|
|
MCUT_ASSERT(SAFE_ACCESS(omi->data_maps.face_map, *f) == hmesh_t::null_face() /*omi->data_maps.face_map.count(*f) == 0*/);
|
|
omi->data_maps.face_map[*f] = as_cm_descr;
|
|
}
|
|
};
|
|
|
|
parallel_for(
|
|
*input.scheduler,
|
|
patch_mesh->faces_begin(),
|
|
patch_mesh->faces_end(),
|
|
fn_fill_face_map);
|
|
}
|
|
#else // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
for (face_array_iterator_t f = patch_mesh->faces_begin(); f != patch_mesh->faces_end(); ++f) {
|
|
MCUT_ASSERT(patch_to_m0_face.count(*f) == 1);
|
|
const int as_m0_descr = SAFE_ACCESS(patch_to_m0_face, *f);
|
|
|
|
MCUT_ASSERT(m0_to_ps_face.count(as_m0_descr) == 1);
|
|
const fd_t as_ps_descr = SAFE_ACCESS(m0_to_ps_face, as_m0_descr);
|
|
|
|
MCUT_ASSERT((int)as_ps_descr < (int)ps_to_cm_face.size() /*ps_to_cm_face.count(as_ps_descr) == 1*/);
|
|
fd_t as_cm_descr = SAFE_ACCESS(ps_to_cm_face, as_ps_descr);
|
|
|
|
// add an offset which allows users to deduce which birth/origin mesh (source or cut mesh) a face (map value) belongs to.
|
|
as_cm_descr = static_cast<fd_t>(as_cm_descr + sm_face_count);
|
|
|
|
MCUT_ASSERT(SAFE_ACCESS(omi->data_maps.face_map, *f) == hmesh_t::null_face() /*omi->data_maps.face_map.count(*f) == 0*/);
|
|
omi->data_maps.face_map[*f] = as_cm_descr;
|
|
}
|
|
#endif // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
}
|
|
|
|
if (patch_location == cm_patch_location_t::INSIDE) {
|
|
output.inside_patches[patch_descriptor].emplace_back((omi));
|
|
} else {
|
|
output.outside_patches[patch_descriptor].emplace_back((omi));
|
|
}
|
|
}
|
|
}
|
|
|
|
TIMESTACK_POP();
|
|
|
|
if (false == (input.keep_fragments_below_cutmesh || //
|
|
input.keep_fragments_above_cutmesh || //
|
|
input.keep_fragments_partially_cut || //
|
|
input.keep_fragments_sealed_inside || //
|
|
input.keep_fragments_sealed_outside || input.keep_fragments_sealed_inside_exhaustive || //
|
|
input.keep_fragments_sealed_outside_exhaustive)) {
|
|
// if the user simply wants [patches], then we should not have to proceed further.
|
|
return;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// calculate the reversed patch seeds
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
// Here, we will infer the seed [interior intersection-halfedges] and polygons
|
|
// for the newly create reversed polygons. We will also save information telling
|
|
// whether each reversed patch is a floating patch or not. We will use this
|
|
// information during stitching
|
|
//
|
|
|
|
TIMESTACK_PUSH("Create reversed-patch seed variables");
|
|
|
|
// for each color
|
|
for (std::map<char, std::vector<int>>::const_iterator color_to_cw_patch_iter = color_to_cw_patch.cbegin();
|
|
color_to_cw_patch_iter != color_to_cw_patch.cend();
|
|
++color_to_cw_patch_iter) {
|
|
|
|
// const char color_value = color_to_cw_patch_iter->first;
|
|
|
|
//
|
|
|
|
// get the reversed patch of the current color
|
|
const std::vector<int>& colored_cw_patches = color_to_cw_patch_iter->second;
|
|
|
|
// for each patch
|
|
for (std::vector<int>::const_iterator colored_cw_patch_iter = colored_cw_patches.cbegin();
|
|
colored_cw_patch_iter != colored_cw_patches.cend();
|
|
++colored_cw_patch_iter) {
|
|
|
|
const int cw_patch_idx = *colored_cw_patch_iter;
|
|
|
|
// get patch polygons
|
|
// const std::vector<int>& cw_patch = SAFE_ACCESS(patches, cw_patch_idx);
|
|
// get the opposite patch
|
|
const int ccw_patch_idx = SAFE_ACCESS(patch_to_opposite, cw_patch_idx); // opposite patch
|
|
|
|
//
|
|
// copy information from opposite (ccw/normal) patch
|
|
//
|
|
// std::pair<std::map<int, bool>::const_iterator, bool> patch_to_floating_flag_insertion = patch_to_floating_flag.insert(
|
|
// std::make_pair(cw_patch_idx, SAFE_ACCESS(patch_to_floating_flag, ccw_patch_idx)));
|
|
|
|
// MCUT_ASSERT(patch_to_floating_flag_insertion.second == true);
|
|
|
|
// was the opposite patch determined to be a floating patch
|
|
// const bool is_floating_patch = patch_to_floating_flag_insertion.first->second;
|
|
// get the index of seed interior intersection halfedge of the opposite ccw/normal patch
|
|
const int ccw_patch_seed_interior_ihalfedge_idx = SAFE_ACCESS(patch_to_seed_interior_ihalfedge_idx, ccw_patch_idx);
|
|
// get the index of seed polygon of the opposite ccw/normal patch
|
|
const int ccw_patch_seed_poly_idx = SAFE_ACCESS(patch_to_seed_poly_idx, ccw_patch_idx);
|
|
// get the seed polygon of the opposite ccw/normal patch
|
|
const traced_polygon_t& ccw_patch_seed_poly = SAFE_ACCESS(m0_polygons, ccw_patch_seed_poly_idx);
|
|
// get the seed interior intersection halfedge of the opposite ccw/normal patch
|
|
const hd_t& ccw_patch_seed_interior_ihalfedge = SAFE_ACCESS(ccw_patch_seed_poly, ccw_patch_seed_interior_ihalfedge_idx);
|
|
// opposite halfedge of the seed interior intersection halfedge of the opposite ccw/normal patch
|
|
const hd_t ccw_patch_seed_interior_ihalfedge_opp = m0.opposite(ccw_patch_seed_interior_ihalfedge);
|
|
|
|
// find the reversed polygon which uses "ccw_patch_seed_interior_ihalfedge_opp"
|
|
// this will be the seed polygon of the current reversed patch
|
|
const std::vector<int>& coincident_polys = SAFE_ACCESS(m0_h_to_ply, ccw_patch_seed_interior_ihalfedge_opp);
|
|
|
|
std::vector<int>::const_iterator find_iter = std::find_if(coincident_polys.cbegin(), coincident_polys.cend(),
|
|
[&](const int& e) {
|
|
#if 0
|
|
if (is_floating_patch)
|
|
{
|
|
return e >= traced_sm_polygon_count && e < traced_polygon_count; // interior ihalfedges of floating patches are already coincident to two polygons due to polygon tracing
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
return e >= traced_polygon_count;
|
|
}
|
|
});
|
|
|
|
MCUT_ASSERT(find_iter != coincident_polys.cend());
|
|
|
|
// the index of the seed polygon of the current reversed patch
|
|
const int cw_patch_seed_poly_idx = *find_iter;
|
|
|
|
// the patch must contain the polygon
|
|
MCUT_ASSERT(std::find(SAFE_ACCESS(patches, cw_patch_idx).cbegin(), SAFE_ACCESS(patches, cw_patch_idx).cend(), cw_patch_seed_poly_idx) != SAFE_ACCESS(patches, cw_patch_idx).cend());
|
|
|
|
const traced_polygon_t& cw_patch_seed_poly = SAFE_ACCESS(m0_polygons, cw_patch_seed_poly_idx);
|
|
traced_polygon_t::const_iterator he_find_iter = std::find(cw_patch_seed_poly.cbegin(), cw_patch_seed_poly.cend(), ccw_patch_seed_interior_ihalfedge_opp);
|
|
|
|
MCUT_ASSERT(he_find_iter != cw_patch_seed_poly.cend());
|
|
|
|
// the index of the interior intersection halfedge of the current reversed patch
|
|
const int opposite_patch_seed_interior_ihalfedge_idx = (int)std::distance(cw_patch_seed_poly.cbegin(), he_find_iter);
|
|
|
|
MCUT_ASSERT(patch_to_seed_interior_ihalfedge_idx.count(cw_patch_idx) == 0);
|
|
patch_to_seed_interior_ihalfedge_idx[cw_patch_idx] = opposite_patch_seed_interior_ihalfedge_idx;
|
|
// std::pair<std::map<int, int>::const_iterator, bool> seed_interior_ihalfedge_idx_insertion = patch_to_seed_interior_ihalfedge_idx.insert(std::make_pair(cw_patch_idx, opposite_patch_seed_interior_ihalfedge_idx));
|
|
// MCUT_ASSERT(seed_interior_ihalfedge_idx_insertion.second == true);
|
|
MCUT_ASSERT(patch_to_seed_interior_ihalfedge_idx.count(cw_patch_idx) == 1);
|
|
|
|
MCUT_ASSERT(patch_to_seed_poly_idx.count(cw_patch_idx) == 0);
|
|
patch_to_seed_poly_idx[cw_patch_idx] = cw_patch_seed_poly_idx;
|
|
// std::pair<std::map<int, int>::const_iterator, bool> seed_poly_idx_insertion = patch_to_seed_poly_idx.insert(std::make_pair(cw_patch_idx, cw_patch_seed_poly_idx));
|
|
// MCUT_ASSERT(seed_poly_idx_insertion.second == true);
|
|
MCUT_ASSERT(patch_to_seed_poly_idx.count(cw_patch_idx) == 1);
|
|
}
|
|
}
|
|
|
|
// patch_to_floating_flag.clear(); // free
|
|
color_to_cw_patch.clear(); // free
|
|
patch_to_opposite.clear(); // free
|
|
|
|
TIMESTACK_POP();
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Stitch cut-mesh patches into connected components (fragments) of the source-mesh
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
// We are now going to "fill the holes"
|
|
//
|
|
// For each color tag, we have a halfdge data structure (which is a copy of "m1").
|
|
// We do this to make sure that the exterior/outside patches will be stitched to
|
|
// separate copies of the connected components in "m1", compared to interior patches.
|
|
// This helps us to distinguish between stitching the interior of the source-mesh
|
|
// (hole-filling), and stitching the exterior (i.e. boolean merge operation
|
|
// if the cut-mesh is water-tight)
|
|
//
|
|
|
|
TIMESTACK_PUSH("Stitching"); // &&&&&
|
|
|
|
std::map<
|
|
char, // color tag
|
|
std::map<
|
|
std::size_t, // cc-id
|
|
std::vector< // list of partially sealed connected components (first elem has 1 stitched polygon and the last has all cut-mesh polygons stitched to fill holes)
|
|
std::pair< // mesh instance
|
|
std::shared_ptr<hmesh_t>, // actual mesh data structure
|
|
connected_component_info_t // information about mesh
|
|
>>>>
|
|
color_to_separated_connected_ccsponents;
|
|
|
|
std::map<
|
|
char, // color tag
|
|
std::vector<traced_polygon_t> // traced polygons in colored "m1" mesh
|
|
>
|
|
color_to_m1_polygons;
|
|
|
|
// A halfedge in "m0" that is used to trace a cut-mesh polygon will have
|
|
// two "m1" versions - one for the ccw/normal patch and the other for the
|
|
// cw/reversed patch.
|
|
//
|
|
std::map<
|
|
char, // color tag
|
|
std::unordered_map<
|
|
hd_t, // "m0" cut-mesh halfedge instance
|
|
std::map<
|
|
int, // patch idx
|
|
hd_t // "m1" cut-mesh halfedge instance
|
|
>>>
|
|
color_to_m0_to_m1_he_instances;
|
|
|
|
std::map<
|
|
char, // color value
|
|
std::unordered_map<int, int> // copy of m0_to_m1_face (initially containing mappings just for traced source-mesh polygon)
|
|
>
|
|
color_to_m0_to_m1_face; // = { { 'A', m0_to_m1_face }, { 'B', m0_to_m1_face } };
|
|
|
|
color_to_m0_to_m1_face.emplace(std::make_pair('A', m0_to_m1_face));
|
|
color_to_m0_to_m1_face.emplace(std::make_pair('B', std::move(m0_to_m1_face)));
|
|
// m0_to_m1_face.clear();
|
|
|
|
std::map<
|
|
char, // color value
|
|
std::unordered_map<int, int> // copy of m1_to_m0_face (initially containing mappings just for traced source-mesh polygon)
|
|
>
|
|
color_to_m1_to_m0_face; // = { { 'A', m1_to_m0_face }, { 'B', m1_to_m0_face } };
|
|
color_to_m1_to_m0_face.emplace(std::make_pair('A', m1_to_m0_face));
|
|
color_to_m1_to_m0_face.emplace(std::make_pair('B', std::move(m1_to_m0_face)));
|
|
// m1_to_m0_face.clear();
|
|
|
|
std::map<char, std::unordered_map<vd_t, // "m1" cut-mesh vtx instance
|
|
vd_t // "m0" cut-mesh ovtx instance
|
|
>>
|
|
colour_to_m1_to_m0_cm_ovtx;
|
|
|
|
std::map<
|
|
char, // color value
|
|
std::vector<vd_t> // copy of "m0_to_m1_ovtx" (initially containing mappings just for original source-mesh & cut-mesh vertices i.e. no ivertices included!)
|
|
>
|
|
color_to_m1_to_m0_sm_ovtx; // = { { 'A', m1_to_m0_ovtx }, { 'B', m1_to_m0_ovtx } };
|
|
color_to_m1_to_m0_sm_ovtx.emplace(std::make_pair('A', m1_to_m0_ovtx));
|
|
color_to_m1_to_m0_sm_ovtx.emplace(std::make_pair('B', std::move(m1_to_m0_ovtx)));
|
|
// m1_to_m0_ovtx.clear();
|
|
|
|
// this queue contains information identifying the patch polygons next-in-queue
|
|
// to be stitched into the inferred connected component
|
|
std::deque<std::tuple<hd_t /*m1*/, int /*m0 poly*/, int /*m0 he*/>> patch_poly_stitching_queue;
|
|
|
|
// enough space for cut-mesh polygons
|
|
std::vector<bool> m0_poly_already_enqueued(m0_polygons.size() - traced_sm_polygon_count, false); // i.e. in "patch_poly_stitching_queue"
|
|
const int m0_poly_already_enqueued_size = (int)m0_poly_already_enqueued.size();
|
|
|
|
// for each color ("interior" / "exterior")
|
|
for (std::map<char, std::vector<int>>::const_iterator color_to_patches_iter = color_to_patch.cbegin();
|
|
color_to_patches_iter != color_to_patch.cend();
|
|
++color_to_patches_iter) {
|
|
|
|
const char color_id = color_to_patches_iter->first;
|
|
|
|
const cm_patch_location_t& location = SAFE_ACCESS(patch_color_label_to_location, color_id);
|
|
|
|
if ((location == cm_patch_location_t::INSIDE && !(input.keep_fragments_sealed_inside || input.keep_fragments_sealed_inside_exhaustive)) || //
|
|
(location == cm_patch_location_t::OUTSIDE && !(input.keep_fragments_sealed_outside || input.keep_fragments_sealed_outside_exhaustive))) {
|
|
continue; // skip stitching of exterior/ interior patches as user desires.
|
|
}
|
|
|
|
MCUT_ASSERT(color_to_m1.count(color_id) == 1);
|
|
// get the reference to the copy of "m1" to which patches of the current color will be stitched
|
|
hmesh_t& m1_colored = SAFE_ACCESS(color_to_m1, color_id);
|
|
|
|
m1_colored.reserve_for_additional_elements(cs_vtx_count);
|
|
|
|
// create entry
|
|
color_to_m0_to_m1_he_instances.insert(std::make_pair(color_id, std::unordered_map<hd_t, std::map<int, hd_t>>()));
|
|
// ref to entry
|
|
std::unordered_map<hd_t, std::map<int, hd_t>>& m0_to_m1_he_instances = SAFE_ACCESS(color_to_m0_to_m1_he_instances, color_id);
|
|
m0_to_m1_he_instances.reserve(m0.number_of_halfedges());
|
|
// copy all of the "m1_polygons" that were created before we got to the stitching stage
|
|
// Note: Before stitching has began, "m1_polygons" contains only source-mesh polygons,
|
|
// which have been partition to allow separation of unsealed connected components
|
|
std::pair<std::map<char, std::vector<traced_polygon_t>>::iterator, bool> color_to_m1_polygons_insertion = color_to_m1_polygons.insert(std::make_pair(color_id, m1_polygons)); // copy!
|
|
|
|
MCUT_ASSERT(color_to_m1_polygons_insertion.second == true);
|
|
|
|
// ref to "m1_polygons" i.e. the source-mesh polygons with partitioning
|
|
std::vector<traced_polygon_t>& m1_polygons_colored = color_to_m1_polygons_insertion.first->second;
|
|
m1_polygons_colored.reserve(m1_polygons_colored.size() + cs_face_count);
|
|
|
|
// reference to the list connected components (see declaration for details)
|
|
std::map<std::size_t, std::vector<std::pair<std::shared_ptr<hmesh_t>, connected_component_info_t>>>& separated_stitching_CCs = color_to_separated_connected_ccsponents[color_id]; // insert
|
|
|
|
std::unordered_map<int, int>& m0_to_m1_face_colored = SAFE_ACCESS(color_to_m0_to_m1_face, color_id); // note: containing mappings only for traced source mesh polygons initially!
|
|
m0_to_m1_face_colored.reserve(m0_polygons.size());
|
|
std::unordered_map<int, int>& m1_to_m0_face_colored = SAFE_ACCESS(color_to_m1_to_m0_face, color_id);
|
|
m1_to_m0_face_colored.reserve(m0_polygons.size());
|
|
MCUT_ASSERT(!m0_to_m1_face_colored.empty());
|
|
MCUT_ASSERT(!m1_to_m0_face_colored.empty());
|
|
|
|
std::vector<vd_t>& m1_to_m0_sm_ovtx_colored = SAFE_ACCESS(color_to_m1_to_m0_sm_ovtx, color_id);
|
|
// MCUT_ASSERT(!m0_to_m1_sm_ovtx_colored.empty());
|
|
MCUT_ASSERT(!m1_to_m0_sm_ovtx_colored.empty());
|
|
m1_to_m0_sm_ovtx_colored.reserve(sm_vtx_cnt);
|
|
|
|
// An original in "m0" that is used to trace a cut-mesh polygon will have
|
|
// two "m1" versions - one for the ccw/normal patch and the other for the
|
|
// cw/reversed patch.
|
|
//
|
|
// This map works like "color_to_m0_to_m1_sm_ovtx" but the difference is that each
|
|
// "m0" vertex has two "m1" copies because we generate ccw & cw patches.
|
|
|
|
MCUT_ASSERT(colour_to_m1_to_m0_cm_ovtx.count(color_id) == 0);
|
|
std::unordered_map<vd_t, // "m1" cut-mesh vtx instance
|
|
vd_t // "m0" cut-mesh ovtx instance
|
|
>& m1_to_m0_cm_ovtx_colored
|
|
= colour_to_m1_to_m0_cm_ovtx[color_id];
|
|
|
|
// keeps track of the total number of cut-mesh polygons for the current color tag (interior/ext)
|
|
int stitched_poly_counter = 0;
|
|
|
|
// for each patch with current color
|
|
for (std::vector<int>::const_iterator patch_iter = color_to_patches_iter->second.cbegin();
|
|
patch_iter != color_to_patches_iter->second.cend();
|
|
++patch_iter) {
|
|
|
|
// get patch index
|
|
const int cur_patch_idx = *patch_iter;
|
|
|
|
// is it a ccw/normal patch i.e. not the cw/reversed version
|
|
// NOTE: ccw/normal patches are created/traced before reversed counterparts (hence the modulo trick)
|
|
const bool is_ccw_patch = ((cur_patch_idx % total_ccw_patch_count) == cur_patch_idx);
|
|
|
|
MCUT_ASSERT(patches.find(cur_patch_idx) != patches.cend());
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// stitch patch into a connected component stored in "m1_colored"
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
// We are basically going to search for the connected component (in "m1_colored") to which
|
|
// the current patch will be stitched/glued.
|
|
//
|
|
// PERSONAL NOTE REGARDING `NORMAL` PATCHES:
|
|
// Interior patches are stitched to the connected components which "naturally"
|
|
// match their winding order (i.e. they are stitched to the connected component
|
|
// "below" the patch).
|
|
// Exterior patches are stitched to connected components which DO NOT share
|
|
// the "natural" winding order (i.e. they are stitched to the connected component
|
|
// "above" the patch).
|
|
//
|
|
|
|
MCUT_ASSERT(patch_to_seed_poly_idx.find(cur_patch_idx) != patch_to_seed_poly_idx.cend());
|
|
|
|
// get the seed polygon from which to begin the stitching
|
|
// this polygon will be on the patch boundary/border
|
|
const int m0_patch_seed_poly_idx = SAFE_ACCESS(patch_to_seed_poly_idx, cur_patch_idx);
|
|
|
|
// patch must contain the polygon
|
|
MCUT_ASSERT(std::find(SAFE_ACCESS(patches, cur_patch_idx).cbegin(), SAFE_ACCESS(patches, cur_patch_idx).cend(), m0_patch_seed_poly_idx) != SAFE_ACCESS(patches, cur_patch_idx).cend());
|
|
// the seed polygon must be from the ones that were traced in "m0" (see graph discovery stage above)
|
|
MCUT_ASSERT(m0_patch_seed_poly_idx < (int)m0_polygons.size());
|
|
|
|
// get the seed polygon of the patch
|
|
const traced_polygon_t& m0_patch_seed_poly = SAFE_ACCESS(m0_polygons, m0_patch_seed_poly_idx);
|
|
|
|
// patch must have a seed halfedge (the one used to traced the seed polygon)
|
|
MCUT_ASSERT(patch_to_seed_interior_ihalfedge_idx.find(cur_patch_idx) != patch_to_seed_interior_ihalfedge_idx.cend());
|
|
|
|
// get the index of the seed interior intersection halfedge of the patch
|
|
// this is a halfedge defining the border of the patch and is used to trace
|
|
// the seed polygon
|
|
const int m0_patch_seed_poly_he_idx = SAFE_ACCESS(patch_to_seed_interior_ihalfedge_idx, cur_patch_idx);
|
|
|
|
// must be within the range of the number of halfedge defining the seed polygon
|
|
MCUT_ASSERT(m0_patch_seed_poly_he_idx < (int)m0_patch_seed_poly.size());
|
|
|
|
// get the seed halfedge descriptor
|
|
const hd_t& m0_patch_seed_poly_he = SAFE_ACCESS(m0_patch_seed_poly, m0_patch_seed_poly_he_idx);
|
|
|
|
//
|
|
// Here, we now deduce the connected component to which the current patch will be stitched.
|
|
// To do this we can use the opposite halfedge of the seed halfedge. This opposite halfedge
|
|
// is used to trace a source-mesh polygon next to the cut-path.
|
|
//
|
|
|
|
// get opposite halfedge of the seed halfedge of the current patch
|
|
const hd_t m0_patch_seed_poly_he_opp = m0.opposite(m0_patch_seed_poly_he);
|
|
|
|
// an "m1" version of this opposite halfedge must exist from the halfedge
|
|
// partitioning problem we solved when duplicating intersection points to
|
|
// partition the source-mesh
|
|
MCUT_ASSERT(m0_to_m1_ihe.find(m0_patch_seed_poly_he_opp) != m0_to_m1_ihe.cend());
|
|
|
|
// get the "m1" version of the opposite-halfedge of the seed-halfedge.
|
|
// Note that this halfedge has already been used to trace a source-mesh polygon
|
|
// in "m1"....
|
|
const hd_t m1_seed_interior_ihe_opp = SAFE_ACCESS(m0_to_m1_ihe, m0_patch_seed_poly_he_opp);
|
|
// .... thus, we have to use its opposite, which will be the "m1" version of the
|
|
// seed halfedge of the current patch.
|
|
// PERSONAL NOTE: this probably requires a visual example to properly understand
|
|
const hd_t m1_seed_interior_ihe_opp_opp = m1_colored.opposite(m1_seed_interior_ihe_opp); // i.e. m1 instance of m0_patch_seed_poly_he_opp
|
|
|
|
patch_poly_stitching_queue.clear();
|
|
|
|
// reset
|
|
for (int i = 0; i < m0_poly_already_enqueued_size; ++i) {
|
|
m0_poly_already_enqueued[i] = false;
|
|
}
|
|
|
|
// thus, the first element is the seed polygon and the seed halfedge
|
|
patch_poly_stitching_queue.push_back(
|
|
std::make_tuple(
|
|
m1_seed_interior_ihe_opp_opp,
|
|
m0_patch_seed_poly_idx,
|
|
m0_patch_seed_poly_he_idx));
|
|
|
|
//
|
|
// In the following loop, we will stitch patch polygons iteratively as we
|
|
// discover adjacent ones starting from the seed polygon. In each interation,
|
|
// we process halfedges of the current polygon so that they reference the
|
|
// correct vertex descriptors (src and tgt) in order to fill holes.
|
|
//
|
|
do {
|
|
|
|
// the first processed/stitched of halfedge the current polygon (our starting point)
|
|
hd_t m1_cur_patch_cur_poly_1st_he = hmesh_t::null_halfedge();
|
|
int m0_cur_patch_cur_poly_idx = -1; // index into m0_polygons
|
|
int m0_cur_patch_cur_poly_1st_he_idx = -1; // index into m0_polygon
|
|
|
|
// pop element from queue (the next polygon to stitch)
|
|
std::tie(
|
|
m1_cur_patch_cur_poly_1st_he,
|
|
m0_cur_patch_cur_poly_idx,
|
|
m0_cur_patch_cur_poly_1st_he_idx)
|
|
= patch_poly_stitching_queue.front();
|
|
|
|
m0_poly_already_enqueued[(std::size_t)m0_cur_patch_cur_poly_idx - traced_sm_polygon_count] = true;
|
|
|
|
// must be within the range of the traced polygons (include the reversed ones)
|
|
MCUT_ASSERT(m0_cur_patch_cur_poly_idx < (int)m0_polygons.size());
|
|
|
|
// get the current polygon of the patch
|
|
const traced_polygon_t& m0_cur_patch_cur_poly = SAFE_ACCESS(m0_polygons, m0_cur_patch_cur_poly_idx);
|
|
|
|
// the index of the starting halfedge must be within range of the polygon
|
|
MCUT_ASSERT(m0_cur_patch_cur_poly_1st_he_idx < (int)m0_cur_patch_cur_poly.size());
|
|
|
|
// get the descriptor of the starting halfedge
|
|
const hd_t& m0_cur_patch_cur_poly_1st_he = SAFE_ACCESS(m0_cur_patch_cur_poly, m0_cur_patch_cur_poly_1st_he_idx);
|
|
|
|
// the processed/stitched version of the current polygon
|
|
m1_polygons_colored.emplace_back(traced_polygon_t());
|
|
traced_polygon_t& m1_poly = m1_polygons_colored.back(); // stitched/"m1" version of polygon
|
|
m1_poly.reserve(m0_cur_patch_cur_poly.size());
|
|
m1_poly.push_back(m1_cur_patch_cur_poly_1st_he);
|
|
|
|
// save mapping
|
|
MCUT_ASSERT(m0_to_m1_face_colored.count(m0_cur_patch_cur_poly_idx) == 0);
|
|
const int m1_cur_patch_cur_poly_idx = (int)(m1_polygons_colored.size() - 1);
|
|
m0_to_m1_face_colored[m0_cur_patch_cur_poly_idx] = m1_cur_patch_cur_poly_idx;
|
|
MCUT_ASSERT(m1_to_m0_face_colored.count(m1_cur_patch_cur_poly_idx) == 0);
|
|
m1_to_m0_face_colored[m1_cur_patch_cur_poly_idx] = m0_cur_patch_cur_poly_idx;
|
|
|
|
// the number of halfedges in the current polygon that have been processed
|
|
// Note: we start from "1" because the initial halfedge (m0_cur_patch_cur_poly_1st_he) has already been processed.
|
|
// That is, we already have an "m1" version of it thanks to the halfedge transformation step (intersection point
|
|
// dupication step) which occurs along the cutpath.
|
|
int transformed_he_counter = 1; //
|
|
|
|
//
|
|
// In the following loop, we will process polygon-halfedges iteratively as we
|
|
// advance onto the "next" ones in the sequence starting from the initial. In each interation,
|
|
// we create an "m1" version of the of the current "m0" halfedge so that it references the
|
|
// correct vertex descriptors (src and tgt). The next iteration moves onto the
|
|
// next halfedge, and so on...
|
|
//
|
|
|
|
do { // for each remaining halfedge of current polygon being stitched
|
|
|
|
// if (transformed_he_counter == 1) { // are we processing the second halfedge?
|
|
// log
|
|
// TODO: proper printing functions
|
|
|
|
// << m1_colored.source(m1_cur_patch_cur_poly_1st_he) << " " << m1_colored.target(m1_cur_patch_cur_poly_1st_he) << ">" << std::endl;);
|
|
//}
|
|
|
|
// index of current halfedge index to be processed
|
|
const int m0_cur_patch_cur_poly_cur_he_idx = wrap_integer(m0_cur_patch_cur_poly_1st_he_idx + transformed_he_counter, 0, (int)m0_cur_patch_cur_poly.size() - 1);
|
|
|
|
// must be in range of polygon size
|
|
MCUT_ASSERT(m0_cur_patch_cur_poly_cur_he_idx < (int)m0_cur_patch_cur_poly.size());
|
|
|
|
// descriptor of current halfedge
|
|
const hd_t m0_cur_patch_cur_poly_cur_he = SAFE_ACCESS(m0_cur_patch_cur_poly, m0_cur_patch_cur_poly_cur_he_idx); // current untransformed
|
|
// opposite of current halfedge
|
|
const hd_t m0_cur_patch_cur_poly_cur_he_opp = m0.opposite(m0_cur_patch_cur_poly_cur_he);
|
|
// target of the current halfedge
|
|
vd_t m0_cur_patch_cur_poly_cur_he_tgt = m0.target(m0_cur_patch_cur_poly_cur_he);
|
|
vd_t m0_cur_patch_cur_poly_cur_he_src = m0.source(m0_cur_patch_cur_poly_cur_he);
|
|
const bool src_is_ivertex = m0_is_intersection_point(m0_cur_patch_cur_poly_cur_he_src, ps_vtx_cnt);
|
|
const bool tgt_is_ivertex = m0_is_intersection_point(m0_cur_patch_cur_poly_cur_he_tgt, ps_vtx_cnt);
|
|
|
|
// is the current halfedge the last to be processed in the current polygon?
|
|
const bool cur_is_last_to_be_transformed = ((transformed_he_counter + 1) == (int)m0_cur_patch_cur_poly.size()); // i.e. current he is last one to be transform
|
|
// enumerator of previously processed halfedge
|
|
const int m1_cur_patch_cur_poly_prev_he_idx = transformed_he_counter - 1; // note: transformed_he_counter is init to 1
|
|
|
|
// must be in current polygon's range
|
|
MCUT_ASSERT(m1_cur_patch_cur_poly_prev_he_idx >= 0 && m1_cur_patch_cur_poly_prev_he_idx < (int)m1_poly.size());
|
|
|
|
// get descriptor of the processed copy of the preceeding halfedge in the current polygon
|
|
const hd_t m1_cur_patch_cur_poly_prev_he = SAFE_ACCESS(m1_poly, m1_cur_patch_cur_poly_prev_he_idx); // previously transformed
|
|
// get target of transformed previous
|
|
const vd_t m1_cur_patch_cur_poly_prev_he_tgt = m1_colored.target(m1_cur_patch_cur_poly_prev_he); // transformed target of previous
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// create "m1" version of current halfedge
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
// Note that the source of the processed/"m1" version of the current halfedge is the same as
|
|
// the target of the processed/"m1" version of the previous halfedge in the current polygon
|
|
vd_t m1_cs_cur_patch_polygon_he_src = m1_cur_patch_cur_poly_prev_he_tgt; // known from previous halfedge
|
|
// This initialization assumes the target of the processed/"m1" version of the current halfedge
|
|
// is the same descriptor as the unprocessed/"m0" version (this is generally true
|
|
// when processing non-boundary/border halfedges and the current patch is a normal patch).
|
|
vd_t m1_cs_cur_patch_polygon_he_tgt = m0_cur_patch_cur_poly_cur_he_tgt;
|
|
|
|
// flag whether to insert new edge into "m1_colored"
|
|
// bool create_new_edge = false;
|
|
|
|
hd_t m1_cur_patch_cur_poly_cur_he = hmesh_t::null_halfedge();
|
|
|
|
// is the current halfedge the last one to be process in the current polygon?
|
|
if (cur_is_last_to_be_transformed) {
|
|
|
|
// we can infer the updated version of the target vertex from the halfedge
|
|
// which is already updated. Update tgt will be the source of the first
|
|
// updated halfedge of the current polygon.
|
|
m1_cs_cur_patch_polygon_he_tgt = m1_colored.source(m1_poly[0]);
|
|
|
|
if (src_is_ivertex && tgt_is_ivertex) { // class 3 : // x-->x
|
|
|
|
//
|
|
// we now want to check if the current halfedge is interior or exterior
|
|
//
|
|
|
|
// MCUT_ASSERT(m0_ivtx_to_ps_edge.find(m0.source(m0_cur_patch_cur_poly_cur_he)) != m0_ivtx_to_ps_edge.cend());
|
|
|
|
// get the ps-halfedge in the intersection-registry entry of src
|
|
// const hd_t src_coincident_ps_halfedge = SAFE_ACCESS(m0_ivtx_to_ps_edge, m0.source(m0_cur_patch_cur_poly_cur_he));
|
|
|
|
// MCUT_ASSERT(m0_ivtx_to_ps_edge.find(m0.target(m0_cur_patch_cur_poly_cur_he)) != m0_ivtx_to_ps_edge.cend());
|
|
|
|
// get the ps-halfedge in the intersection-registry entry of src
|
|
// const hd_t tgt_ps_h = SAFE_ACCESS(m0_ivtx_to_ps_edge, m0.target(m0_cur_patch_cur_poly_cur_he));
|
|
// get the ps-edges corresponding to the ps-halfedges
|
|
vd_t src_vertex = m0_cur_patch_cur_poly_cur_he_src; // m0.source(m0_cur_patch_cur_poly_cur_he); // TODO: no need to query m0 [again] (see above)
|
|
MCUT_ASSERT((size_t)src_vertex - ps_vtx_cnt < m0_ivtx_to_intersection_registry_entry.size() /*m0_ivtx_to_intersection_registry_entry.find(src_vertex) != m0_ivtx_to_intersection_registry_entry.cend()*/);
|
|
const std::pair<ed_t, fd_t>& src_vertex_ipair = SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, (std::size_t)src_vertex - ps_vtx_cnt);
|
|
const ed_t src_ps_edge = src_vertex_ipair.first; // SAFE_ACCESS(m0_ivtx_to_ps_edge, m0.source(m0_cur_patch_cur_poly_cur_he)); // ps.edge(src_coincident_ps_halfedge);
|
|
|
|
vd_t tgt_vertex = m0_cur_patch_cur_poly_cur_he_tgt; // m0.target(m0_cur_patch_cur_poly_cur_he);
|
|
MCUT_ASSERT((size_t)tgt_vertex - ps_vtx_cnt < m0_ivtx_to_intersection_registry_entry.size() /*m0_ivtx_to_intersection_registry_entry.find(tgt_vertex) != m0_ivtx_to_intersection_registry_entry.cend()*/);
|
|
const std::pair<ed_t, fd_t>& tgt_vertex_ipair = SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, (std::size_t)tgt_vertex - ps_vtx_cnt);
|
|
const ed_t tgt_ps_edge = tgt_vertex_ipair.first; // SAFE_ACCESS(m0_ivtx_to_ps_edge, m0.target(m0_cur_patch_cur_poly_cur_he)); // ps.edge(tgt_ps_h);
|
|
|
|
// is it an interior halfedge
|
|
bool is_valid_ambiguious_interior_edge = (src_ps_edge != tgt_ps_edge);
|
|
|
|
if (is_valid_ambiguious_interior_edge) { // check is interior ihalfedge (due ambiguity with exterior-interior halfedges x-->x)
|
|
|
|
MCUT_ASSERT(m0_to_m1_ihe.find(m0_cur_patch_cur_poly_cur_he_opp) != m0_to_m1_ihe.cend());
|
|
|
|
const hd_t m1_cur_patch_cur_poly_cur_he_opp = SAFE_ACCESS(m0_to_m1_ihe, m0_cur_patch_cur_poly_cur_he_opp);
|
|
const hd_t m1_cur_patch_cur_poly_cur_he_opp_opp = m1_colored.opposite(m1_cur_patch_cur_poly_cur_he_opp);
|
|
|
|
// halfedge already exists. it was created during source-mesh partitioning stage earlier
|
|
m1_cur_patch_cur_poly_cur_he = m1_cur_patch_cur_poly_cur_he_opp_opp;
|
|
|
|
MCUT_ASSERT(m1_colored.target(m1_cur_patch_cur_poly_cur_he) == m1_cs_cur_patch_polygon_he_tgt);
|
|
}
|
|
}
|
|
} // if (cur_is_last_to_be_transformed) {
|
|
else if (!src_is_ivertex && tgt_is_ivertex) { // class 1 : o-->x : this type of halfedge can only be "coming in" i.e. pointing torward source mesh
|
|
// o-->x
|
|
|
|
/*
|
|
Steps:
|
|
|
|
transformed_src = transformed_prev_tgt // always available since cut-mesh polygon updating always starts from a halfedge whose opposite is already updated
|
|
transformed_tgt = untransformed_tgt // assume descriptor will not be updated
|
|
create_new_edge = FALSE // to insert a new edge into halfedge data structure or not
|
|
|
|
IF "opp" has been transformed // note: if opp is transformed, then polygon coincident to that opp halfedge has been fully updated too since all halfedges of a cut-mesh polygon are transformed before moving onto others.
|
|
transformed_tgt = source of transformed "opp"
|
|
ELSE
|
|
transformed_tgt = source of transformed "next" // note: "next" will always be an interior intersection-halfedge since o-->x ihalfedges are always "incoming" i.e. torward the src-mesh
|
|
create_new_edge = TRUE // because opposite does not exist (in "m1")
|
|
|
|
IF create_new_edge
|
|
create new edge and use halfedge defined by transformed_src and transformed_tgt
|
|
ELSE
|
|
use halfedge defined by computed transformed_src and transformed_tgt
|
|
*/
|
|
|
|
// check if opposite halfedge of current is updated. (NOTE: searching only through
|
|
// the polygons of the current patch)
|
|
|
|
// the updated opposite halfedge in the current patch
|
|
hd_t m1_cs_cur_patch_polygon_he_opp = hmesh_t::null_halfedge();
|
|
// query instances of the "m1" version of the opposite halfedge
|
|
std::unordered_map<hd_t /*m0*/, std::map<int /*patch idx*/, hd_t /*m1*/>>::const_iterator m0_to_m1_he_instances_find_iter = m0_to_m1_he_instances.find(m0_cur_patch_cur_poly_cur_he_opp);
|
|
|
|
// do we have at least one updated copy of the opposite, irrespective of which patch it
|
|
// belongs to.
|
|
if (m0_to_m1_he_instances_find_iter != m0_to_m1_he_instances.cend()) {
|
|
// now check if there is an updated instance corresponding to the current patch
|
|
std::map<int /*initial patch polygon*/, hd_t /*m1*/>::const_iterator m1_he_instances_find_iter = m0_to_m1_he_instances_find_iter->second.find(cur_patch_idx);
|
|
if (m1_he_instances_find_iter != m0_to_m1_he_instances_find_iter->second.cend()) {
|
|
// we have found the already-updated instance of the opposite halfedge
|
|
m1_cs_cur_patch_polygon_he_opp = m1_he_instances_find_iter->second;
|
|
}
|
|
}
|
|
|
|
// check if opposite halfedge is transformed
|
|
const bool opp_is_transformed = m1_cs_cur_patch_polygon_he_opp != hmesh_t::null_halfedge();
|
|
|
|
if (opp_is_transformed) {
|
|
// infer tgt from opposite
|
|
m1_cs_cur_patch_polygon_he_tgt = m1_colored.source(m1_cs_cur_patch_polygon_he_opp);
|
|
} else {
|
|
|
|
//
|
|
// the opposite halfedge has not been transformed.
|
|
// We will deduce the target from the updated "next" halfedge, and
|
|
// we have to create a new edge
|
|
//
|
|
|
|
// look up the updated "next" by looking forward and finding the coincident source-mesh polygon
|
|
// and then getting the updated instance of "next".
|
|
const int m0_next_cs_polygon_he_index = wrap_integer(m0_cur_patch_cur_poly_cur_he_idx + 1, 0, (int)m0_cur_patch_cur_poly.size() - 1);
|
|
|
|
MCUT_ASSERT(m0_next_cs_polygon_he_index < (int)m0_cur_patch_cur_poly.size());
|
|
|
|
const hd_t m0_cs_next_patch_polygon_he = SAFE_ACCESS(m0_cur_patch_cur_poly, m0_next_cs_polygon_he_index); // next untransformed
|
|
const vd_t m0_cs_next_patch_polygon_he_src = m0.source(m0_cs_next_patch_polygon_he);
|
|
const vd_t m0_cs_next_patch_polygon_he_tgt = m0.target(m0_cs_next_patch_polygon_he);
|
|
|
|
MCUT_ASSERT(m0_is_intersection_point(m0_cs_next_patch_polygon_he_src, ps_vtx_cnt) && m0_is_intersection_point(m0_cs_next_patch_polygon_he_tgt, ps_vtx_cnt)); // .. because the current halfedge is "incoming"
|
|
|
|
// get the "m0" polygons which are traced with the "next" halfedge
|
|
// const std::vector<int>& m0_poly_he_coincident_polys = SAFE_ACCESS(m0_h_to_ply, m0_cs_next_patch_polygon_he);
|
|
// get reference to src-mesn polygon which is traced with "next" halfedge
|
|
// const std::vector<int>::const_iterator find_iter = std::find_if(
|
|
// m0_poly_he_coincident_polys.cbegin(),
|
|
// m0_poly_he_coincident_polys.cend(),
|
|
// [&](const int& e) {
|
|
// return (e < traced_sm_polygon_count); // match with src-mesn polygon
|
|
// });
|
|
|
|
// "next" is always incident to a source-mesh polygon
|
|
MCUT_ASSERT(std::find_if(
|
|
SAFE_ACCESS(m0_h_to_ply, m0_cs_next_patch_polygon_he).cbegin(),
|
|
SAFE_ACCESS(m0_h_to_ply, m0_cs_next_patch_polygon_he).cend(),
|
|
[&](const int& e) {
|
|
return (e < traced_sm_polygon_count); // match with src-mesn polygon
|
|
})
|
|
!= SAFE_ACCESS(m0_h_to_ply, m0_cs_next_patch_polygon_he).cend());
|
|
|
|
//
|
|
// At this point, we have found the adjacent connected component which is
|
|
// the one using m0_cs_next_patch_polygon_he. Therefore, we can directly
|
|
// determine the connected component by looking up the updated instance
|
|
// of m0_cs_next_patch_polygon_he_opp since m0_cs_next_patch_polygon_he_opp
|
|
// is guarranteed to have been updated because it is an interior intersection
|
|
// halfedge (i.e. its on the cut path).
|
|
//
|
|
// REMEMBER: exterior patches are stitched to the "upper" source-mesh fragment
|
|
const hd_t m0_cs_next_patch_polygon_he_opp = m0.opposite(m0_cs_next_patch_polygon_he);
|
|
const hd_t m1_cs_next_patch_polygon_he_opp = SAFE_ACCESS(m0_to_m1_ihe, m0_cs_next_patch_polygon_he_opp);
|
|
const hd_t m1_cs_next_patch_polygon_he_opp_opp = m1_colored.opposite(m1_cs_next_patch_polygon_he_opp);
|
|
|
|
m1_cs_cur_patch_polygon_he_tgt = m1_colored.source(m1_cs_next_patch_polygon_he_opp_opp);
|
|
|
|
// create_new_edge = true; // because opposite has not yet been created
|
|
}
|
|
} else if (src_is_ivertex && tgt_is_ivertex) { // class 3 : // x-->x
|
|
|
|
// the current halfedge will either be interior or exterior.
|
|
|
|
// MCUT_ASSERT(m0_ivtx_to_ps_edge.find(m0.source(m0_cur_patch_cur_poly_cur_he)) != m0_ivtx_to_ps_edge.cend());
|
|
|
|
// const hd_t src_coincident_ps_halfedge = SAFE_ACCESS(m0_ivtx_to_ps_edge, m0.source(m0_cur_patch_cur_poly_cur_he));
|
|
|
|
// MCUT_ASSERT(m0_ivtx_to_ps_edge.find(m0.target(m0_cur_patch_cur_poly_cur_he)) != m0_ivtx_to_ps_edge.cend());
|
|
|
|
// const hd_t tgt_ps_h = SAFE_ACCESS(m0_ivtx_to_ps_edge, m0.target(m0_cur_patch_cur_poly_cur_he));
|
|
const vd_t src_vertex = m0_cur_patch_cur_poly_cur_he_src; // m0.source(m0_cur_patch_cur_poly_cur_he); // TODO: no need to query m0 [again] (see above)
|
|
MCUT_ASSERT((size_t)src_vertex - ps_vtx_cnt < m0_ivtx_to_intersection_registry_entry.size() /*m0_ivtx_to_intersection_registry_entry.find(src_vertex) != m0_ivtx_to_intersection_registry_entry.cend()*/);
|
|
const std::pair<ed_t, fd_t>& src_vertex_ipair = SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, (std::size_t)src_vertex - ps_vtx_cnt);
|
|
const ed_t src_ps_edge = src_vertex_ipair.first; // SAFE_ACCESS(m0_ivtx_to_ps_edge, m0.source(m0_cur_patch_cur_poly_cur_he)); // ps.edge(src_coincident_ps_halfedge);
|
|
|
|
const vd_t tgt_vertex = m0_cur_patch_cur_poly_cur_he_tgt; // m0.target(m0_cur_patch_cur_poly_cur_he);
|
|
MCUT_ASSERT((size_t)tgt_vertex - ps_vtx_cnt < m0_ivtx_to_intersection_registry_entry.size() /*m0_ivtx_to_intersection_registry_entry.find(tgt_vertex) != m0_ivtx_to_intersection_registry_entry.cend()*/);
|
|
const std::pair<ed_t, fd_t>& tgt_vertex_ipair = SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, (std::size_t)tgt_vertex - ps_vtx_cnt);
|
|
const ed_t tgt_ps_edge = tgt_vertex_ipair.first;
|
|
|
|
// const ed_t src_ps_edge = SAFE_ACCESS(m0_ivtx_to_ps_edge, m0.source(m0_cur_patch_cur_poly_cur_he)); //ps.edge(src_coincident_ps_halfedge);
|
|
// const ed_t tgt_ps_edge = SAFE_ACCESS(m0_ivtx_to_ps_edge, m0.target(m0_cur_patch_cur_poly_cur_he)); // ps.edge(tgt_ps_h);
|
|
bool is_valid_ambiguious_interior_edge = (src_ps_edge != tgt_ps_edge);
|
|
|
|
// check if current halfedge is interior
|
|
if (is_valid_ambiguious_interior_edge) {
|
|
|
|
MCUT_ASSERT(m0_to_m1_ihe.find(m0_cur_patch_cur_poly_cur_he_opp) != m0_to_m1_ihe.cend());
|
|
const hd_t m1_cur_patch_cur_poly_cur_he_opp = SAFE_ACCESS(m0_to_m1_ihe, m0_cur_patch_cur_poly_cur_he_opp);
|
|
const hd_t m1_cur_patch_cur_poly_cur_he_opp_opp = m1_colored.opposite(m1_cur_patch_cur_poly_cur_he_opp);
|
|
|
|
// halfedge already exists. it was created during src-mesh partitioning
|
|
m1_cur_patch_cur_poly_cur_he = m1_cur_patch_cur_poly_cur_he_opp_opp;
|
|
m1_cs_cur_patch_polygon_he_tgt = m1_colored.target(m1_cur_patch_cur_poly_cur_he_opp_opp);
|
|
} else { // its an exterior x-->x halfedge
|
|
|
|
// look up the transformed "next" by looking finding the
|
|
// coincident source-mesh polygon and then getting the transformed instance of "next".
|
|
const int m0_next_cs_polygon_he_index = wrap_integer(m0_cur_patch_cur_poly_cur_he_idx + 1, 0, (int)m0_cur_patch_cur_poly.size() - 1);
|
|
|
|
MCUT_ASSERT(m0_next_cs_polygon_he_index < (int)m0_cur_patch_cur_poly.size());
|
|
|
|
const hd_t m0_cs_next_patch_polygon_he = m0_cur_patch_cur_poly[m0_next_cs_polygon_he_index]; // next untransformed
|
|
const vd_t m0_cs_next_patch_polygon_he_src = m0.source(m0_cs_next_patch_polygon_he);
|
|
const vd_t m0_cs_next_patch_polygon_he_tgt = m0.target(m0_cs_next_patch_polygon_he);
|
|
|
|
MCUT_ASSERT(m0_is_intersection_point(m0_cs_next_patch_polygon_he_src, ps_vtx_cnt) && m0_is_intersection_point(m0_cs_next_patch_polygon_he_tgt, ps_vtx_cnt));
|
|
MCUT_ASSERT(SAFE_ACCESS(m0_h_to_ply, m0_cs_next_patch_polygon_he).size() > 0 /*m0_h_to_ply.find(m0_cs_next_patch_polygon_he) != m0_h_to_ply.cend()*/);
|
|
|
|
#ifndef NDEBUG
|
|
const std::vector<int>& m0_poly_he_coincident_polys = m0_h_to_ply[m0_cs_next_patch_polygon_he];
|
|
const std::vector<int>::const_iterator find_iter = std::find_if( // points to src-mesh polygon
|
|
m0_poly_he_coincident_polys.cbegin(),
|
|
m0_poly_he_coincident_polys.cend(),
|
|
[&](const int& e) {
|
|
return (e < traced_sm_polygon_count); // match with source-mesh polygon
|
|
});
|
|
|
|
// "next" is always incident to an source-mesh polygon
|
|
MCUT_ASSERT(find_iter != m0_poly_he_coincident_polys.cend());
|
|
#endif
|
|
const hd_t m0_cs_next_patch_polygon_he_opp = m0.opposite(m0_cs_next_patch_polygon_he);
|
|
|
|
// Note: this is always true, even in the case of scoop cuts. This is because
|
|
// halfedges along the cut-path are updated before stitching (during source-mesh partitioning)
|
|
// so we can infer the tgt easily
|
|
MCUT_ASSERT(SAFE_ACCESS(m0_h_to_ply, m0_cs_next_patch_polygon_he_opp).size() > 0 /*m0_h_to_ply.find(m0_cs_next_patch_polygon_he_opp) != m0_h_to_ply.cend()*/);
|
|
|
|
const hd_t m1_cs_next_patch_polygon_he_opp = SAFE_ACCESS(m0_to_m1_ihe, m0_cs_next_patch_polygon_he_opp);
|
|
const hd_t m1_cs_next_patch_polygon_he_opp_opp = m1_colored.opposite(m1_cs_next_patch_polygon_he_opp);
|
|
|
|
m1_cs_cur_patch_polygon_he_tgt = m1_colored.source(m1_cs_next_patch_polygon_he_opp_opp);
|
|
}
|
|
} else { // class 0 or 2 i.e. o-->o or x-->o
|
|
|
|
/*
|
|
In the following steps, our ability to deduce the correct target vertex instance
|
|
by simply checking whether "opp" or "next" is updated before
|
|
duplication is guarranteed to work. This is because we update polygons
|
|
of a patch using BFS (following adjacency) which guarrantees that when
|
|
the condition to create a duplicate vertex is reached, there will have
|
|
been no other halfedge referencing the same vertex that had reached the
|
|
same condition.
|
|
|
|
transformed_src = transformed_prev_tgt // always available because cut-mesh polygon update always starts from a halfedge whose opposite is already updated
|
|
transformed_tgt = untransformed_tgt
|
|
create_new_edge = FALSE
|
|
|
|
IF opposite patch is transformed
|
|
1. IF opposite halfedge is transformed
|
|
2. infer from opposite halfedge
|
|
3. ELSE IF next halfedge is transformed
|
|
4. infer from next
|
|
2. ELSE
|
|
IF an updated halfedge pointing to tgt already exists (i.e. using "halfedges around vertex")
|
|
infer from that halfedge
|
|
ELSE
|
|
create duplicate of untransformed_tgt
|
|
transformed_tgt = duplicate of untransformed_tgt
|
|
create_new_edge = TRUE // because "opposite" AND "next" halfedge are not updated, so we have create a new connection between vertices
|
|
ELSE
|
|
// Do nothing (keep transformed_tgt as it is) because there
|
|
// is no adjacent halfedge which is updated, and the current
|
|
// patch gets precedence to use the first/original vertex instances
|
|
*/
|
|
if (cur_is_last_to_be_transformed) {
|
|
// initial polygon halfedge which was transformed
|
|
m1_cs_cur_patch_polygon_he_tgt = m1_colored.source(m1_poly.front());
|
|
} else {
|
|
|
|
// check opposite patch of current is transformed
|
|
const bool opposite_patch_is_transformed = !is_ccw_patch; // ... since ccw patches are always transformed before their cw counterparts
|
|
|
|
if (opposite_patch_is_transformed) // if true, the current patch is the cw one
|
|
{
|
|
// check if opposite halfedge of current is transformed. (NOTE: searching
|
|
// only through the polygons of the current patch)
|
|
|
|
hd_t m1_cs_cur_patch_polygon_he_opp = hmesh_t::null_halfedge(); // transformed instance of opposite
|
|
std::unordered_map<hd_t /*m0*/, std::map<int /*patch idx*/, hd_t /*m1*/>>::const_iterator m0_to_m1_he_instances_find_iter = m0_to_m1_he_instances.find(m0_cur_patch_cur_poly_cur_he_opp);
|
|
|
|
if (m0_to_m1_he_instances_find_iter != m0_to_m1_he_instances.cend()) { // must transformed at least once since opposite patch is transformed
|
|
|
|
std::map<int /*initial patch polygon*/, hd_t /*m1*/>::const_iterator m1_he_instances_find_iter = m0_to_m1_he_instances_find_iter->second.find(cur_patch_idx);
|
|
|
|
if (m1_he_instances_find_iter != m0_to_m1_he_instances_find_iter->second.cend()) {
|
|
m1_cs_cur_patch_polygon_he_opp = m1_he_instances_find_iter->second;
|
|
}
|
|
}
|
|
|
|
const bool opp_is_transformed = m1_cs_cur_patch_polygon_he_opp != hmesh_t::null_halfedge();
|
|
|
|
if (opp_is_transformed) {
|
|
m1_cs_cur_patch_polygon_he_tgt = m1_colored.source(m1_cs_cur_patch_polygon_he_opp);
|
|
} else {
|
|
|
|
// check if next halfedge of current is transformed.
|
|
const int m0_next_cs_polygon_he_index = wrap_integer(m0_cur_patch_cur_poly_cur_he_idx + 1, 0, (int)m0_cur_patch_cur_poly.size() - 1);
|
|
const hd_t m0_cs_next_patch_polygon_he = m0_cur_patch_cur_poly[m0_next_cs_polygon_he_index]; // next untransformed
|
|
m0_to_m1_he_instances_find_iter = m0_to_m1_he_instances.find(m0_cs_next_patch_polygon_he);
|
|
hd_t m1_cs_next_patch_polygon_he = hmesh_t::null_halfedge();
|
|
|
|
if (m0_to_m1_he_instances_find_iter != m0_to_m1_he_instances.cend()) { // must transformed at least once since opposite patch is transformed
|
|
|
|
std::map<int, hd_t>::const_iterator m1_he_instances_find_iter = m0_to_m1_he_instances_find_iter->second.find(cur_patch_idx);
|
|
|
|
if (m1_he_instances_find_iter != m0_to_m1_he_instances_find_iter->second.cend()) {
|
|
m1_cs_next_patch_polygon_he = m1_he_instances_find_iter->second;
|
|
}
|
|
}
|
|
|
|
const bool next_is_transformed = m1_cs_next_patch_polygon_he != hmesh_t::null_halfedge();
|
|
|
|
if (next_is_transformed) {
|
|
m1_cs_cur_patch_polygon_he_tgt = m1_colored.source(m1_cs_next_patch_polygon_he);
|
|
} else {
|
|
|
|
//
|
|
// find all transformed halfedges which connect to m0_cur_patch_cur_poly_cur_he_tgt in the current patch
|
|
//
|
|
|
|
bool found_transformed_neigh_he = false; // any updated halfedge whose m0 instance references m0_cur_patch_cur_poly_cur_he_tgt
|
|
|
|
/*
|
|
1. get "m0" halfedges around vertex
|
|
2. for each halfedge around vertex, check if it has a transformed instance that belonging to the current patch
|
|
*/
|
|
const std::vector<halfedge_descriptor_t>& m0_incoming_halfedges = m0.get_halfedges_around_vertex(m0_cur_patch_cur_poly_cur_he_tgt);
|
|
|
|
for (std::vector<halfedge_descriptor_t>::const_iterator m0_incoming_halfedges_iter = m0_incoming_halfedges.cbegin();
|
|
m0_incoming_halfedges_iter != m0_incoming_halfedges.cend();
|
|
++m0_incoming_halfedges_iter) {
|
|
|
|
const halfedge_descriptor_t m0_incoming_halfedge = *m0_incoming_halfedges_iter;
|
|
MCUT_ASSERT(m0_incoming_halfedge != hmesh_t::null_halfedge());
|
|
|
|
// is it transformed?
|
|
m0_to_m1_he_instances_find_iter = m0_to_m1_he_instances.find(m0_incoming_halfedge);
|
|
hd_t m1_incoming_halfedge = hmesh_t::null_halfedge();
|
|
|
|
if (m0_to_m1_he_instances_find_iter != m0_to_m1_he_instances.cend()) {
|
|
|
|
// get the transformed instance belonging to the current patch
|
|
std::map<int, hd_t>::const_iterator m1_he_instances_find_iter = m0_to_m1_he_instances_find_iter->second.find(cur_patch_idx);
|
|
|
|
if (m1_he_instances_find_iter != m0_to_m1_he_instances_find_iter->second.cend()) {
|
|
m1_incoming_halfedge = m1_he_instances_find_iter->second;
|
|
|
|
m1_cs_cur_patch_polygon_he_tgt = m1_colored.target(m1_incoming_halfedge);
|
|
found_transformed_neigh_he = true;
|
|
}
|
|
}
|
|
|
|
if (!found_transformed_neigh_he) {
|
|
// We enter this scope if: "m1_incoming_halfedge" does not exist
|
|
|
|
// What we are going to try to do now is check if the opposite of "m0_incoming_halfedge" has been transformed (w.r.t the current patch),
|
|
// and if so, we get it transformed instanced from which we deduce the correct value of "m1_cs_cur_patch_polygon_he_tgt"
|
|
|
|
const halfedge_descriptor_t m0_incoming_halfedge_opp = m0.opposite(m0_incoming_halfedge);
|
|
m0_to_m1_he_instances_find_iter = m0_to_m1_he_instances.find(m0_incoming_halfedge);
|
|
hd_t m1_incoming_halfedge_opp = hmesh_t::null_halfedge();
|
|
|
|
if (m0_to_m1_he_instances_find_iter != m0_to_m1_he_instances.cend()) {
|
|
|
|
std::map<int, hd_t>::const_iterator m1_he_instances_find_iter = m0_to_m1_he_instances_find_iter->second.find(cur_patch_idx);
|
|
|
|
if (m1_he_instances_find_iter != m0_to_m1_he_instances_find_iter->second.cend()) {
|
|
m1_incoming_halfedge_opp = m1_he_instances_find_iter->second;
|
|
m1_cs_cur_patch_polygon_he_tgt = m1_colored.source(m1_incoming_halfedge_opp); // Note: using "m1.source" not "m1.target"
|
|
found_transformed_neigh_he = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (found_transformed_neigh_he) {
|
|
break; // done
|
|
}
|
|
}
|
|
|
|
if (!found_transformed_neigh_he) {
|
|
//
|
|
// none of the adjacent halfedges have been transformed, so we must duplicate m0_cur_patch_cur_poly_cur_he_tgt
|
|
//
|
|
|
|
// is "m1_cs_cur_patch_polygon_he_tgt" a vertex we can duplicate? (partial cut, interior sealing)
|
|
// TODO: std::sort(sm_interior_cs_border_vertices) and then we can use binary search
|
|
const bool is_sm_interior_cs_boundary_vertex = std::find(sm_interior_cs_border_vertices.cbegin(), sm_interior_cs_border_vertices.cend(), m0_cur_patch_cur_poly_cur_he_tgt) != sm_interior_cs_border_vertices.cend();
|
|
|
|
if (!is_sm_interior_cs_boundary_vertex) {
|
|
|
|
const vd_t m0_poly_he_tgt_dupl = m1_colored.add_vertex(m0.vertex(m0_cur_patch_cur_poly_cur_he_tgt));
|
|
|
|
MCUT_ASSERT(m0_poly_he_tgt_dupl != hmesh_t::null_halfedge());
|
|
|
|
m1_cs_cur_patch_polygon_he_tgt = m0_poly_he_tgt_dupl;
|
|
// create_new_edge = true;
|
|
}
|
|
}
|
|
} // if (next_is_transformed) {
|
|
} // if (opp_is_transformed) {
|
|
} // if (opposite_patch_is_transformed)
|
|
} // if (cur_is_last_to_be_transformed) {
|
|
} // class 0 or 2 i.e. o-->o or x-->o
|
|
|
|
// if we could not infer from any pre-existing halfedge
|
|
if (m1_cur_patch_cur_poly_cur_he == hmesh_t::null_halfedge()) {
|
|
// check if edge exists
|
|
// TODO: use mesh built "halfedge(...)" (may require minor update to function)
|
|
// ed_t e = get_computed_edge(/*m1_colored, */ m1_cs_cur_patch_polygon_he_src, m1_cs_cur_patch_polygon_he_tgt);
|
|
// hd_t h = m1_colored.halfedge(m1_cs_cur_patch_polygon_he_src, m1_cs_cur_patch_polygon_he_tgt);
|
|
ed_t e = m1_colored.edge(m1_cs_cur_patch_polygon_he_src, m1_cs_cur_patch_polygon_he_tgt, true);
|
|
|
|
if (e != hmesh_t::null_edge()) { // if edge already exists
|
|
|
|
hd_t h0 = m1_colored.halfedge(e, 0);
|
|
|
|
if (m1_colored.source(h0) == m1_cs_cur_patch_polygon_he_src) {
|
|
m1_cur_patch_cur_poly_cur_he = h0;
|
|
} else {
|
|
hd_t h1 = m1_colored.halfedge(e, 1);
|
|
m1_cur_patch_cur_poly_cur_he = h1;
|
|
}
|
|
} else {
|
|
|
|
m1_cur_patch_cur_poly_cur_he = m1_colored.add_edge(m1_cs_cur_patch_polygon_he_src, m1_cs_cur_patch_polygon_he_tgt);
|
|
// TODO:replace with map (for O(Log N) searches)
|
|
// m1_computed_edges.push_back(m1_colored.edge(m1_cur_patch_cur_poly_cur_he));
|
|
// std::map<vd_t, std::vector<std::pair<vd_t, ed_t>>>
|
|
|
|
// ed_t new_edge = m1_colored.edge(m1_cur_patch_cur_poly_cur_he);
|
|
// m1_computed_edges[m1_cs_cur_patch_polygon_he_src].push_back(std::make_pair(m1_cs_cur_patch_polygon_he_tgt, new_edge));
|
|
// m1_computed_edges[m1_cs_cur_patch_polygon_he_tgt].push_back(std::make_pair(m1_cs_cur_patch_polygon_he_src, new_edge));
|
|
}
|
|
} // if (m1_cur_patch_cur_poly_cur_he == hmesh_t::null_halfedge()) {
|
|
|
|
// << m1_colored.source(m1_cur_patch_cur_poly_cur_he) << " " << m1_colored.target(m1_cur_patch_cur_poly_cur_he) << ">" << std::endl;);
|
|
|
|
// halfedge must have been found (created or inferred)
|
|
MCUT_ASSERT(m1_cur_patch_cur_poly_cur_he != hmesh_t::null_halfedge());
|
|
MCUT_ASSERT(m1_colored.target(m1_poly.back()) == m1_colored.source(m1_cur_patch_cur_poly_cur_he));
|
|
|
|
// add transformed halfedge to currently transformed polygon
|
|
m1_poly.push_back(m1_cur_patch_cur_poly_cur_he);
|
|
|
|
//
|
|
// map halfedge to transformed instance of current patch
|
|
//
|
|
|
|
// NOTE: m0_cur_patch_cur_poly_cur_he will not exist if current patch has CCW orientation
|
|
// since such a patch will always be transformed first before its CW counterpart.
|
|
std::unordered_map<hd_t, std::map<int, hd_t>>::iterator m0_to_m1_he_instances_find_iter = m0_to_m1_he_instances.find(m0_cur_patch_cur_poly_cur_he);
|
|
|
|
if (m0_to_m1_he_instances_find_iter == m0_to_m1_he_instances.end()) { // not yet transformed at all (i.e. m0_cur_patch_cur_poly_cur_he belongs to CCW polygon )
|
|
|
|
std::pair<std::unordered_map<hd_t /*m0*/, std::map<int /*initial patch polygon*/, hd_t /*m1*/>>::iterator, bool> pair = m0_to_m1_he_instances.insert(std::make_pair(m0_cur_patch_cur_poly_cur_he, std::map<int, hd_t>()));
|
|
|
|
MCUT_ASSERT(pair.second == true);
|
|
|
|
m0_to_m1_he_instances_find_iter = pair.first;
|
|
}
|
|
|
|
// stores the an "m1" instance of the current halfedge, for each patch
|
|
std::map<int, hd_t /*m1*/>& patch_to_m1_he = m0_to_m1_he_instances_find_iter->second;
|
|
// const std::map<int, hd_t /*m1*/>::const_iterator patch_idx_to_m1_he = patch_to_m1_he.find(cur_patch_idx);
|
|
|
|
// In general, a halfedge may only be transformed once for each patch it is be associated
|
|
// with (i.e it will have two copies with one for each opposing patch). Note however that
|
|
// in the case that the current halfedge is a border halfedge (partial cut), its transformed
|
|
// copy is the same as its untransformed copy for each patch
|
|
MCUT_ASSERT(patch_to_m1_he.find(cur_patch_idx) == patch_to_m1_he.cend());
|
|
|
|
patch_to_m1_he.insert(std::make_pair(cur_patch_idx, m1_cur_patch_cur_poly_cur_he));
|
|
transformed_he_counter += 1; // next halfedge in m0_cur_patch_cur_poly
|
|
|
|
} while (transformed_he_counter != (int)m0_cur_patch_cur_poly.size()); // while not all halfedges of the current polygon have been transformed.
|
|
|
|
//
|
|
// at this stage, all halfedges of the current polygon have been transformed
|
|
//
|
|
|
|
// ... remove the stitching-initialiation data of current polygon.
|
|
patch_poly_stitching_queue.pop_front();
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// find untransformed neighbouring polygons and queue them
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
// We are basically adding all unstitched neighbours of the current polygon
|
|
// (that we just sticthed) to the queue so they can be stitched as well. These
|
|
// are polygons on the same patch as the current polygon and are adjacent to
|
|
// it i.e. they share an edge.
|
|
//
|
|
|
|
MCUT_ASSERT(patches.find(cur_patch_idx) != patches.cend());
|
|
|
|
// polygons of current patch
|
|
// const std::vector<int>& patch_polys = SAFE_ACCESS(patches, cur_patch_idx);
|
|
|
|
// stores the queued adjacent polygon to be stitched that have just been discovered
|
|
// i.e. discovered while finding the next untransformed adjacent polygons of the
|
|
// current one that we just transformed
|
|
std::deque<std::tuple<hd_t /*m1*/, int /*m0 poly*/, int /*m0 he*/>> patch_poly_stitching_queue_tmp;
|
|
|
|
// for each halfedge of the polygon we just stitched
|
|
for (traced_polygon_t::const_iterator m0_poly_he_iter = m0_cur_patch_cur_poly.cbegin();
|
|
m0_poly_he_iter != m0_cur_patch_cur_poly.cend();
|
|
++m0_poly_he_iter) {
|
|
|
|
const hd_t m0_cur_patch_cur_poly_cur_he = *m0_poly_he_iter;
|
|
|
|
// Skip certain neighbours. The adjacent polygon has been processed (assuming
|
|
// it exists) if the following conditions are true. Theses conditions are
|
|
// evaluated on "m0_cur_patch_cur_poly_cur_he"
|
|
//
|
|
// 1. is same as initial halfedge
|
|
// implies that opposite is already transformed (before the current polygon,
|
|
// we transformed the polygon which is traced by the opposite halfedge of
|
|
// m0_cur_patch_cur_poly_1st_he)
|
|
// 2. is interior intersection-halfedge
|
|
// opposite is already transformed since interior intersection-halfedges
|
|
// are on cut-path (the opposite halfedge is incident to one of the
|
|
// src-mesh connected components)
|
|
// 3. is a halfedge whose opposite has been transformed
|
|
// because that implies that its polygon has been transformed (so no need
|
|
// to add to queue).
|
|
// 4. it is a border halfedge
|
|
// (i.e. the only face incident to the opposite halfedge is another which
|
|
// belong to the opposite patch)
|
|
//
|
|
|
|
//
|
|
// case 1
|
|
//
|
|
if (m0_cur_patch_cur_poly_cur_he == m0_cur_patch_cur_poly_1st_he) {
|
|
continue; // 1
|
|
}
|
|
|
|
//
|
|
// case 2
|
|
//
|
|
const vd_t m0_cur_patch_cur_poly_cur_he_src = m0.source(m0_cur_patch_cur_poly_cur_he);
|
|
const vd_t m0_cur_patch_cur_poly_cur_he_tgt = m0.target(m0_cur_patch_cur_poly_cur_he);
|
|
bool is_ambiguious_interior_edge_case = m0_is_intersection_point(m0_cur_patch_cur_poly_cur_he_src, ps_vtx_cnt) && m0_is_intersection_point(m0_cur_patch_cur_poly_cur_he_tgt, ps_vtx_cnt);
|
|
|
|
if (is_ambiguious_interior_edge_case) {
|
|
MCUT_ASSERT((size_t)m0_cur_patch_cur_poly_cur_he_src - ps_vtx_cnt < m0_ivtx_to_intersection_registry_entry.size() /*m0_ivtx_to_intersection_registry_entry.find(m0_cur_patch_cur_poly_cur_he_src) != m0_ivtx_to_intersection_registry_entry.cend()*/);
|
|
|
|
const std::pair<ed_t, fd_t>& m0_cur_patch_cur_poly_cur_he_src_ipair = SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, (std::size_t)m0_cur_patch_cur_poly_cur_he_src - ps_vtx_cnt);
|
|
const ed_t src_ps_edge = m0_cur_patch_cur_poly_cur_he_src_ipair.first; // SAFE_ACCESS(m0_ivtx_to_ps_edge, m0_cur_patch_cur_poly_cur_he_src); //ps.edge(src_coincident_ps_halfedge);
|
|
|
|
MCUT_ASSERT((size_t)m0_cur_patch_cur_poly_cur_he_tgt - ps_vtx_cnt < m0_ivtx_to_intersection_registry_entry.size() /*m0_ivtx_to_intersection_registry_entry.find(m0_cur_patch_cur_poly_cur_he_tgt) != m0_ivtx_to_intersection_registry_entry.cend()*/);
|
|
const std::pair<ed_t, fd_t>& m0_cur_patch_cur_poly_cur_he_tgt_ipair = SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, (std::size_t)m0_cur_patch_cur_poly_cur_he_tgt - ps_vtx_cnt);
|
|
|
|
const ed_t tgt_ps_edge = m0_cur_patch_cur_poly_cur_he_tgt_ipair.first; // SAFE_ACCESS(m0_ivtx_to_ps_edge, m0_cur_patch_cur_poly_cur_he_tgt); //ps.edge(tgt_ps_h);
|
|
|
|
bool is_valid_ambiguious_interior_edge = (src_ps_edge != tgt_ps_edge);
|
|
|
|
if (is_valid_ambiguious_interior_edge) {
|
|
continue; // 2
|
|
}
|
|
}
|
|
|
|
//
|
|
// case 3
|
|
//
|
|
const hd_t m0_cur_patch_cur_poly_cur_he_opp = m0.opposite(m0_cur_patch_cur_poly_cur_he);
|
|
std::unordered_map<hd_t, std::map<int, hd_t>>::const_iterator m0_to_m1_he_instances_find_iter = m0_to_m1_he_instances.find(m0_cur_patch_cur_poly_cur_he_opp); // value will not exist if current patch positive
|
|
|
|
if (m0_to_m1_he_instances_find_iter != m0_to_m1_he_instances.cend()) { // check exists (i.e. m0_cur_patch_cur_poly_cur_he_opp has be transform but we dont know for which patch it has been transformed (CCW or CW)
|
|
|
|
const std::map<int, hd_t>& patch_to_m1_he = m0_to_m1_he_instances_find_iter->second;
|
|
std::map<int, hd_t>::const_iterator patch_idx_to_m1_he = patch_to_m1_he.find(cur_patch_idx);
|
|
|
|
if (patch_idx_to_m1_he != patch_to_m1_he.cend()) { // check is stitched
|
|
MCUT_ASSERT(patch_idx_to_m1_he->second != hmesh_t::null_halfedge());
|
|
continue; // 3
|
|
}
|
|
}
|
|
|
|
//
|
|
// case 4
|
|
//
|
|
|
|
// must exist because m0_cur_patch_cur_poly_cur_he was just transformed
|
|
MCUT_ASSERT(m0_to_m1_he_instances.find(m0_cur_patch_cur_poly_cur_he) != m0_to_m1_he_instances.cend());
|
|
|
|
// find m1_cur_polygon_he which is the transformed instance of m0_cur_patch_cur_poly_cur_he
|
|
/*const*/ std::map<int, hd_t>& patch_to_m1_he = SAFE_ACCESS(m0_to_m1_he_instances, m0_cur_patch_cur_poly_cur_he);
|
|
|
|
MCUT_ASSERT(patch_to_m1_he.find(cur_patch_idx) != patch_to_m1_he.cend());
|
|
|
|
const hd_t m1_cur_polygon_he = SAFE_ACCESS(patch_to_m1_he, cur_patch_idx);
|
|
// transformed halfedge used by adjacent polygon
|
|
const hd_t m1_next_poly_seed_he = m1_colored.opposite(m1_cur_polygon_he);
|
|
|
|
// infer the index of the next stitched polygon which is traced with m0_cur_patch_cur_poly_cur_he_opp
|
|
MCUT_ASSERT(SAFE_ACCESS(m0_h_to_ply, m0_cur_patch_cur_poly_cur_he_opp).size() > 0 /*m0_h_to_ply.find(m0_cur_patch_cur_poly_cur_he_opp) != m0_h_to_ply.cend()*/);
|
|
|
|
//
|
|
// find the adjacent polygon in the current patch using the opposite of the
|
|
// current halfedge
|
|
//
|
|
|
|
// get the polygons traced with the opposite halfedge
|
|
const std::vector<int> m0_poly_he_opp_coincident_polys = SAFE_ACCESS(m0_h_to_ply, m0_cur_patch_cur_poly_cur_he_opp);
|
|
const std::vector<int>::const_iterator find_iter = std::find_if( // find the current polygon of current patch
|
|
m0_poly_he_opp_coincident_polys.cbegin(),
|
|
m0_poly_he_opp_coincident_polys.cend(),
|
|
[&](const int poly_idx) {
|
|
MCUT_ASSERT(m0_cm_poly_to_patch_idx.count(poly_idx) == 1);
|
|
// return SAFE_ACCESS(m0_cm_poly_to_patch_idx, poly_idx) == cur_patch_idx;
|
|
|
|
bool has_patch_winding_orientation = false;
|
|
|
|
// check if polygon has the same winding order as the current patch
|
|
|
|
if (is_ccw_patch) { // is the current patch a "normal" patch?
|
|
has_patch_winding_orientation = (poly_idx < traced_polygon_count);
|
|
} else {
|
|
has_patch_winding_orientation = (poly_idx >= traced_polygon_count);
|
|
}
|
|
|
|
return has_patch_winding_orientation && SAFE_ACCESS(m0_cm_poly_to_patch_idx, poly_idx) == cur_patch_idx; // std::find(patch_polys.cbegin(), patch_polys.cend(), poly_idx) != patch_polys.cend(); // NOTE: only one polygon in the current patch will match
|
|
});
|
|
|
|
// note: if the current halfedge is on the border of the cut-mesh, then its opposite
|
|
// halfedge can only trace one polygon, which is the opposite polygon to the
|
|
// current (i.e. on the opposing patch). Hence, if find_iter is null then it means "m0_cur_patch_cur_poly_cur_he"
|
|
// is on the border of the cut-mesh.
|
|
const bool opp_is_border_halfedge = (find_iter == m0_poly_he_opp_coincident_polys.cend()); // current patch is reversed-patch and
|
|
|
|
if (opp_is_border_halfedge) {
|
|
// 4 there is no neighbouring polygon which is coincident to
|
|
// "m0_cur_patch_cur_poly_cur_he_opp"
|
|
continue;
|
|
}
|
|
|
|
// the adjacent polygon
|
|
const int m0_next_poly_idx = *find_iter;
|
|
|
|
//
|
|
// TODO: the following conditions below could also be speeded up if we
|
|
// create a tmp vector/map which stores all of the adjacent polygons we have
|
|
// already queued. Searching over this vector could be that bit faster.
|
|
// We could do the right here now that "m0_next_poly_idx" is known.
|
|
//
|
|
|
|
// deduce the index of the next polygon's seed m0 halfedge
|
|
// -------------------------------------------------------
|
|
|
|
MCUT_ASSERT(m0_next_poly_idx < (int)m0_polygons.size());
|
|
|
|
// adjacent polygon
|
|
const traced_polygon_t& next_poly = m0_polygons[m0_next_poly_idx];
|
|
// pointer to the first halfedge in the polygon from which its stitching will begin
|
|
const traced_polygon_t::const_iterator he_find_iter = std::find(
|
|
next_poly.cbegin(),
|
|
next_poly.cend(),
|
|
m0_cur_patch_cur_poly_cur_he_opp);
|
|
|
|
// "m0_cur_patch_cur_poly_cur_he_opp" must exist in next_poly since we have
|
|
// already established that "m0_cur_patch_cur_poly_cur_he" is not a border
|
|
// halfedge. This is further supported by the fact that "next_poly" is in
|
|
// current patch and coincident to "m0_cur_patch_cur_poly_cur_he_opp"
|
|
MCUT_ASSERT(he_find_iter != next_poly.cend());
|
|
|
|
// index of halfedge from which stitching of the adjacent polygon will begin
|
|
const int m0_next_poly_he_idx = (int)std::distance(next_poly.cbegin(), he_find_iter);
|
|
|
|
// NOTE: there is no need to check if the next polygon is transformed here
|
|
// because our 4 conditions above take care of this.
|
|
// However, we do have to take care not to add the polygon to the queue more
|
|
// than once (due to BFS nature of stitching), hence the following.
|
|
|
|
const bool poly_is_already_in_tmp_queue = std::find_if(
|
|
patch_poly_stitching_queue_tmp.crbegin(),
|
|
patch_poly_stitching_queue_tmp.crend(),
|
|
[&](const std::tuple<hd_t, int, int>& elem) {
|
|
return std::get<1>(elem) == m0_next_poly_idx;
|
|
})
|
|
!= patch_poly_stitching_queue_tmp.crend();
|
|
|
|
if (!poly_is_already_in_tmp_queue) {
|
|
// if (!poly_is_already_stitched_wrt_cur_patch) { // TODO: the [if check] will have to go once "poly_is_already_stitched_wrt_cur_patch" is removed
|
|
// check the main global queue to make sure poly has not already been added
|
|
// std::unordered_map<int, bool>::const_iterator qmap_iter = m0_poly_already_enqueued.find(m0_next_poly_idx);
|
|
const bool poly_is_already_in_maqueued = m0_poly_already_enqueued[(std::size_t)m0_next_poly_idx - traced_sm_polygon_count]; /*std::find_if(
|
|
patch_poly_stitching_queue.crbegin(),
|
|
patch_poly_stitching_queue.crend(),
|
|
[&](const std::tuple<hd_t, int, int> &elem)
|
|
{
|
|
return std::get<1>(elem) == m0_next_poly_idx; // there is an element in the queue with the polygon's ID
|
|
}) != patch_poly_stitching_queue.crend();*/
|
|
|
|
if (!poly_is_already_in_maqueued) {
|
|
patch_poly_stitching_queue_tmp.push_back(std::make_tuple(m1_next_poly_seed_he, m0_next_poly_idx, m0_next_poly_he_idx));
|
|
m0_poly_already_enqueued[(std::size_t)m0_next_poly_idx - traced_sm_polygon_count] = true;
|
|
}
|
|
}
|
|
|
|
//
|
|
// update vertex mapping for non-intersection points
|
|
//
|
|
|
|
// since we loop round the whole current polygon, we can just use the target
|
|
// NOTE: Possible optimization we could populate "m1_to_m0_cm_ovtx_colored" in the
|
|
// do-while loop that stitches cut-mesh patches because the are specific if-cases which deal
|
|
// with x-->o and o-->o halfedges, from which we can add elements to "m1_to_m0_cm_ovtx_colored"
|
|
const vd_t m0_cur_poly_cur_he_tgt = m0.target(m0_cur_patch_cur_poly_cur_he);
|
|
const bool tgt_is_original_vtx = !m0_is_intersection_point(m0_cur_poly_cur_he_tgt, ps_vtx_cnt);
|
|
|
|
if (tgt_is_original_vtx) {
|
|
const vd_t m1_cur_poly_cur_he_tgt = m1_colored.target(m1_cur_polygon_he);
|
|
// "cur_poly_cur_he_tgt" may already be mapped to its "m1" version w.r.t the current patch.
|
|
// This is because of the BFS manner in which we stitch polygons of a patch.
|
|
if (m1_to_m0_cm_ovtx_colored.count(m1_cur_poly_cur_he_tgt) == 0) {
|
|
m1_to_m0_cm_ovtx_colored[m1_cur_poly_cur_he_tgt] = m0_cur_poly_cur_he_tgt;
|
|
}
|
|
}
|
|
}
|
|
// } // for each m0 halfedge of current patch-polygon
|
|
|
|
// add elements of tmp/local queue to global queue
|
|
while (!patch_poly_stitching_queue_tmp.empty()) {
|
|
const std::tuple<hd_t, int, int>& elem = patch_poly_stitching_queue_tmp.front();
|
|
patch_poly_stitching_queue.push_back(elem); // add
|
|
patch_poly_stitching_queue_tmp.pop_front(); // rm
|
|
}
|
|
|
|
//
|
|
// NOTE: At this stage, we have finished transforming all the halfedges of the current polygon
|
|
// and we have also added all its neighbouring polygons to the queue for stitching.
|
|
//
|
|
|
|
MCUT_ASSERT(patch_color_label_to_location.find(color_id) != patch_color_label_to_location.cend());
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Update output (with the current polygon stitched into a cc)
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
// const std::string color_tag_stri = to_string(SAFE_ACCESS(patch_color_label_to_location, color_id)); // == cm_patch_location_t::OUTSIDE ? "e" : "i");
|
|
|
|
// save meshes and dump
|
|
|
|
if (input.keep_fragments_sealed_inside_exhaustive || input.keep_fragments_sealed_outside_exhaustive) {
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// create the sealed meshes defined by the [current] set of traced polygons
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
extract_connected_components(
|
|
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
*input.scheduler,
|
|
#endif
|
|
separated_stitching_CCs,
|
|
m1_colored,
|
|
0,
|
|
m1_polygons_colored,
|
|
sm_polygons_below_cs,
|
|
sm_polygons_above_cs,
|
|
m1_vertex_to_seam_flag,
|
|
m1_to_m0_sm_ovtx_colored,
|
|
m1_to_m0_cm_ovtx_colored,
|
|
m1_to_m0_face_colored,
|
|
m0_to_ps_vtx,
|
|
m0_to_ps_face,
|
|
ps_to_sm_vtx,
|
|
ps_to_sm_face,
|
|
ps_to_cm_vtx,
|
|
ps_to_cm_face,
|
|
sm_vtx_cnt,
|
|
sm_face_count,
|
|
input.populate_vertex_maps,
|
|
input.populate_face_maps,
|
|
input.keep_fragments_below_cutmesh,
|
|
input.keep_fragments_above_cutmesh,
|
|
input.keep_fragments_partially_cut);
|
|
}
|
|
|
|
++global_cm_poly_stitch_counter;
|
|
stitched_poly_counter++;
|
|
|
|
} while (!patch_poly_stitching_queue.empty()); // for each polygon of patch
|
|
|
|
//
|
|
// NOTE: At this stage we have finished stitching all polygons of the current patch.
|
|
// So, the current patch has been stitch to a src-mesh fragment
|
|
//
|
|
|
|
} // for each patch
|
|
|
|
} // for each color
|
|
|
|
TIMESTACK_POP(); // &&&&&
|
|
|
|
patch_poly_stitching_queue.clear();
|
|
m0_poly_already_enqueued.clear();
|
|
|
|
m0_cm_poly_to_patch_idx.clear();
|
|
// m0_ivtx_to_ps_edge.clear(); // free
|
|
m0_polygons.clear();
|
|
m0_h_to_ply.clear();
|
|
m0_to_m1_ihe.clear();
|
|
m1_polygons.clear();
|
|
patches.clear();
|
|
patch_to_seed_interior_ihalfedge_idx.clear();
|
|
patch_to_seed_interior_ihalfedge_idx.clear();
|
|
patch_to_seed_poly_idx.clear();
|
|
color_to_patch.clear();
|
|
sm_interior_cs_border_vertices.clear();
|
|
color_to_m0_to_m1_he_instances.clear();
|
|
|
|
//
|
|
// NOTE: At this stage, all patches of the current have been stitched
|
|
//
|
|
|
|
bool userWantsFullySealedFragmentsANY = (input.keep_fragments_sealed_inside || input.keep_fragments_sealed_outside);
|
|
bool userWantsEvenPartiallySealedFragmentsANY = (input.keep_fragments_sealed_inside_exhaustive || input.keep_fragments_sealed_outside_exhaustive);
|
|
|
|
// if the user wants [only] fully sealed fragment (not partially sealed)
|
|
if (userWantsFullySealedFragmentsANY && //
|
|
!userWantsEvenPartiallySealedFragmentsANY) {
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// create the [fully] sealed meshes defined by the final set of traced polygons
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
for (std::map<char, std::map<std::size_t, std::vector<std::pair<std::shared_ptr<hmesh_t>, connected_component_info_t>>>>::iterator color_to_separated_CCs_iter = color_to_separated_connected_ccsponents.begin();
|
|
color_to_separated_CCs_iter != color_to_separated_connected_ccsponents.end();
|
|
++color_to_separated_CCs_iter) {
|
|
|
|
const char color_label = color_to_separated_CCs_iter->first;
|
|
std::map<std::size_t, std::vector<std::pair<std::shared_ptr<hmesh_t>, connected_component_info_t>>>& separated_sealed_CCs = color_to_separated_CCs_iter->second;
|
|
|
|
const hmesh_t& m1_colored = SAFE_ACCESS(color_to_m1, color_label);
|
|
MCUT_ASSERT(color_to_m1_polygons.count(color_label) == 1);
|
|
const std::vector<traced_polygon_t>& m1_polygons_colored = SAFE_ACCESS(color_to_m1_polygons, color_label);
|
|
MCUT_ASSERT(color_to_m1_to_m0_sm_ovtx.count(color_label) == 1);
|
|
const std::vector<vd_t>& m1_to_m0_sm_ovtx_colored = SAFE_ACCESS(color_to_m1_to_m0_sm_ovtx, color_label);
|
|
|
|
MCUT_ASSERT(colour_to_m1_to_m0_cm_ovtx.count(color_label) == 1);
|
|
const std::unordered_map<vd_t, vd_t>& m1_to_m0_cm_ovtx_colored = SAFE_ACCESS(colour_to_m1_to_m0_cm_ovtx, color_label);
|
|
MCUT_ASSERT(color_to_m1_to_m0_face.count(color_label) == 1);
|
|
/*const*/ std::unordered_map<int, int>& m1_to_m0_face_colored = SAFE_ACCESS(color_to_m1_to_m0_face, color_label);
|
|
|
|
// extract the seam vertices
|
|
extract_connected_components(
|
|
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
|
|
*input.scheduler,
|
|
#endif
|
|
separated_sealed_CCs,
|
|
m1_colored,
|
|
0,
|
|
m1_polygons_colored,
|
|
sm_polygons_below_cs,
|
|
sm_polygons_above_cs,
|
|
m1_vertex_to_seam_flag,
|
|
m1_to_m0_sm_ovtx_colored,
|
|
m1_to_m0_cm_ovtx_colored,
|
|
m1_to_m0_face_colored,
|
|
m0_to_ps_vtx,
|
|
m0_to_ps_face,
|
|
ps_to_sm_vtx,
|
|
ps_to_sm_face,
|
|
ps_to_cm_vtx,
|
|
ps_to_cm_face,
|
|
sm_vtx_cnt,
|
|
sm_face_count,
|
|
input.populate_vertex_maps,
|
|
input.populate_face_maps,
|
|
input.keep_fragments_below_cutmesh,
|
|
input.keep_fragments_above_cutmesh,
|
|
input.keep_fragments_partially_cut);
|
|
}
|
|
}
|
|
|
|
sm_polygons_below_cs.clear(); // free
|
|
sm_polygons_above_cs.clear();
|
|
m1_vertex_to_seam_flag.clear();
|
|
color_to_m1.clear();
|
|
color_to_m1_polygons.clear();
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// save output and finish
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
std::map<sm_frag_location_t, std::map<cm_patch_location_t, std::vector<std::shared_ptr<output_mesh_info_t>>>>& out = output.connected_components;
|
|
int idx = 0;
|
|
for (std::map<char, std::map<std::size_t, std::vector<std::pair<std::shared_ptr<hmesh_t>, connected_component_info_t>>>>::iterator color_to_separated_CCs_iter = color_to_separated_connected_ccsponents.begin();
|
|
color_to_separated_CCs_iter != color_to_separated_connected_ccsponents.end();
|
|
++color_to_separated_CCs_iter) {
|
|
|
|
const char color_label = color_to_separated_CCs_iter->first;
|
|
// inside or outside or undefined
|
|
const cm_patch_location_t patchLocation = SAFE_ACCESS(patch_color_label_to_location, color_label);
|
|
std::map<std::size_t, std::vector<std::pair<std::shared_ptr<hmesh_t>, connected_component_info_t>>>& separated_sealed_CCs = color_to_separated_CCs_iter->second;
|
|
|
|
for (std::map<std::size_t, std::vector<std::pair<std::shared_ptr<hmesh_t>, connected_component_info_t>>>::iterator cc_iter = separated_sealed_CCs.begin();
|
|
cc_iter != separated_sealed_CCs.end();
|
|
++cc_iter) {
|
|
|
|
// all instances of current connected component (from 0-or-1 stitched cm-polygon to all stitched cm-polygons)
|
|
// NOTE TO SELF: the first instance may have one or [zero] cut-mesh polygons since stitching works on a per-patch-per-polygon basis.
|
|
// I.e. if the 1st stitched cm-polygon is into an "above" fragmetn (inside or outside), then the opposite
|
|
// fragment that is "below" will have zero cut-mesh polygons. Hence.
|
|
std::vector<std::pair<std::shared_ptr<hmesh_t>, connected_component_info_t>>& cc_instances = cc_iter->second;
|
|
|
|
if (!userWantsEvenPartiallySealedFragmentsANY) {
|
|
MCUT_ASSERT(cc_instances.size() == 1); // there is only one, fully sealed, copy
|
|
}
|
|
|
|
// For each instance of CC (each instance differs by one stitched polygon)
|
|
for (std::vector<std::pair<std::shared_ptr<hmesh_t>, connected_component_info_t>>::iterator cc_instance_iter = cc_instances.begin();
|
|
cc_instance_iter != cc_instances.end();
|
|
++cc_instance_iter) {
|
|
|
|
std::pair<std::shared_ptr<hmesh_t>, connected_component_info_t>& cc_instance = *cc_instance_iter;
|
|
|
|
if (input.verbose) {
|
|
// const int idx = (int)std::distance(cc_instances.begin(), cc_instance_iter);
|
|
dump_mesh(cc_instance.first.get()[0], (std::string("cc") + std::to_string(idx++) + "." + to_string(cc_instance.second.location) + "." + to_string(patchLocation)).c_str());
|
|
}
|
|
|
|
std::shared_ptr<output_mesh_info_t> omi = std::shared_ptr<output_mesh_info_t>(new output_mesh_info_t);
|
|
omi->mesh = (cc_instance.first);
|
|
omi->seam_vertices = std::move(cc_instance.second.seam_vertices);
|
|
omi->data_maps = std::move(cc_instance.second.data_maps);
|
|
out[cc_instance.second.location][patchLocation].emplace_back(std::move(omi));
|
|
}
|
|
}
|
|
}
|
|
|
|
patch_color_label_to_location.clear(); // free
|
|
color_to_separated_connected_ccsponents.clear();
|
|
|
|
TIMESTACK_POP();
|
|
|
|
return;
|
|
} // dispatch
|