diff --git a/CMakeLists.txt b/CMakeLists.txt index a5e50f3f00..f9b17ee559 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -646,7 +646,6 @@ function(bambustudio_copy_dlls target config postfix output_dlls) ${_out_dir}/TKXSBase.dll ${_out_dir}/freetype.dll - PARENT_SCOPE ) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9f65a46bb4..8aaa713861 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -16,6 +16,7 @@ add_subdirectory(Shiny) add_subdirectory(semver) add_subdirectory(libigl) add_subdirectory(hints) +add_subdirectory(mcut) # Adding libnest2d project for bin packing... add_subdirectory(libnest2d) diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 77296eea73..d325c0e3ce 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -425,7 +425,7 @@ if (_opts) target_compile_options(libslic3r_cgal PRIVATE "${_opts_bad}") endif() -target_link_libraries(libslic3r_cgal PRIVATE ${_cgal_tgt} libigl) +target_link_libraries(libslic3r_cgal PRIVATE ${_cgal_tgt} libigl mcut) if (MSVC AND "${CMAKE_SIZEOF_VOID_P}" STREQUAL "4") # 32 bit MSVC workaround target_compile_definitions(libslic3r_cgal PRIVATE CGAL_DO_NOT_USE_MPZF) @@ -492,6 +492,7 @@ target_link_libraries(libslic3r ZLIB::ZLIB ${OCCT_LIBS} Clipper2 + mcut ) if(NOT WIN32) diff --git a/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp b/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp index dd98e431a7..e989033782 100644 --- a/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp +++ b/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp @@ -38,6 +38,32 @@ MeshBoolean::cgal::CGALMeshPtr get_cgalmesh(const CSGPartT &csgpart) return ret; } +// This method can be overriden when a specific CSGPart type supports caching +// of the voxel grid +template +MeshBoolean::mcut::McutMeshPtr get_mcutmesh(const CSGPartT& csgpart) +{ + const indexed_triangle_set* its = csg::get_mesh(csgpart); + indexed_triangle_set dummy; + + if (!its) + its = &dummy; + + MeshBoolean::mcut::McutMeshPtr ret; + + indexed_triangle_set m = *its; + its_transform(m, get_transform(csgpart), true); + + try { + ret = MeshBoolean::mcut::triangle_mesh_to_mcut(m); + } + catch (...) { + // errors are ignored, simply return null + ret = nullptr; + } + + return ret; +} namespace detail_cgal { @@ -83,6 +109,50 @@ std::vector get_cgalptrs(Ex policy, const Range &csgrange) } // namespace detail +namespace detail_mcut { + + using MeshBoolean::mcut::McutMeshPtr; + + inline void perform_csg(CSGType op, McutMeshPtr& dst, McutMeshPtr& src) + { + if (!dst && op == CSGType::Union && src) { + dst = std::move(src); + return; + } + + if (!dst || !src) + return; + + switch (op) { + case CSGType::Union: + MeshBoolean::mcut::do_boolean(*dst, *src,"UNION"); + break; + case CSGType::Difference: + MeshBoolean::mcut::do_boolean(*dst, *src,"A_NOT_B"); + break; + case CSGType::Intersection: + MeshBoolean::mcut::do_boolean(*dst, *src,"INTERSECTION"); + break; + } + } + + template + std::vector get_mcutptrs(Ex policy, const Range& csgrange) + { + std::vector ret(csgrange.size()); + execution::for_each(policy, size_t(0), csgrange.size(), + [&csgrange, &ret](size_t i) { + auto it = csgrange.begin(); + std::advance(it, i); + auto& csgpart = *it; + ret[i] = get_mcutmesh(csgpart); + }); + + return ret; + } + +} // namespace mcut_detail + // Process the sequence of CSG parts with CGAL. template void perform_csgmesh_booleans_cgal(MeshBoolean::cgal::CGALMeshPtr &cgalm, @@ -133,6 +203,58 @@ void perform_csgmesh_booleans_cgal(MeshBoolean::cgal::CGALMeshPtr &cgalm, cgalm = std::move(opstack.top().cgalptr); } +// Process the sequence of CSG parts with mcut. +template +void perform_csgmesh_booleans_mcut(MeshBoolean::mcut::McutMeshPtr& mcutm, + const Range& csgrange) +{ + using MeshBoolean::mcut::McutMesh; + using MeshBoolean::mcut::McutMeshPtr; + using namespace detail_mcut; + + struct Frame { + CSGType op; McutMeshPtr mcutptr; + explicit Frame(CSGType csgop = CSGType::Union) + : op{ csgop } + , mcutptr{ MeshBoolean::mcut::triangle_mesh_to_mcut(indexed_triangle_set{}) } + {} + }; + + std::stack opstack{ std::vector{} }; + + opstack.push(Frame{}); + + std::vector McutMeshes = get_mcutptrs(ex_tbb, csgrange); + + size_t csgidx = 0; + for (auto& csgpart : csgrange) { + + auto op = get_operation(csgpart); + McutMeshPtr& mcutptr = McutMeshes[csgidx++]; + + if (get_stack_operation(csgpart) == CSGStackOp::Push) { + opstack.push(Frame{ op }); + op = CSGType::Union; + } + + Frame* top = &opstack.top(); + + perform_csg(get_operation(csgpart), top->mcutptr, mcutptr); + + if (get_stack_operation(csgpart) == CSGStackOp::Pop) { + McutMeshPtr src = std::move(top->mcutptr); + auto popop = opstack.top().op; + opstack.pop(); + McutMeshPtr& dst = opstack.top().mcutptr; + perform_csg(popop, dst, src); + } + } + + mcutm = std::move(opstack.top().mcutptr); + +} + + template It check_csgmesh_booleans(const Range &csgrange, Visitor &&vfn) { @@ -184,18 +306,66 @@ It check_csgmesh_booleans(const Range &csgrange, Visitor &&vfn) } template -It check_csgmesh_booleans(const Range &csgrange) +It check_csgmesh_booleans(const Range &csgrange, bool use_mcut=false) { + if(!use_mcut) return check_csgmesh_booleans(csgrange, [](auto &) {}); + else { + using namespace detail_mcut; + + std::vector McutMeshes(csgrange.size()); + auto check_part = [&csgrange, &McutMeshes](size_t i) { + auto it = csgrange.begin(); + std::advance(it, i); + auto& csgpart = *it; + auto m = get_mcutmesh(csgpart); + + // mesh can be nullptr if this is a stack push or pull + if (!get_mesh(csgpart) && get_stack_operation(csgpart) != CSGStackOp::Continue) { + McutMeshes[i] = MeshBoolean::mcut::triangle_mesh_to_mcut(indexed_triangle_set{}); + return; + } + + try { + if (!m || MeshBoolean::mcut::empty(*m)) + return; + } + catch (...) { return; } + + McutMeshes[i] = std::move(m); + }; + execution::for_each(ex_tbb, size_t(0), csgrange.size(), check_part); + + It ret = csgrange.end(); + for (size_t i = 0; i < csgrange.size(); ++i) { + if (!McutMeshes[i]) { + auto it = csgrange.begin(); + std::advance(it, i); + + if (ret == csgrange.end()) + ret = it; + } + } + + return ret; + } } template MeshBoolean::cgal::CGALMeshPtr perform_csgmesh_booleans(const Range &csgparts) { auto ret = MeshBoolean::cgal::triangle_mesh_to_cgal(indexed_triangle_set{}); - if (ret) { + if (ret) perform_csgmesh_booleans_cgal(ret, csgparts); - } + return ret; +} + +template +MeshBoolean::mcut::McutMeshPtr perform_csgmesh_booleans_mcut(const Range& csgparts) +{ + auto ret = MeshBoolean::mcut::triangle_mesh_to_mcut(indexed_triangle_set{}); + if (ret) + perform_csgmesh_booleans_mcut(ret, csgparts); return ret; } diff --git a/src/libslic3r/MeshBoolean.cpp b/src/libslic3r/MeshBoolean.cpp index 6bf60f105b..bd65a6ddae 100644 --- a/src/libslic3r/MeshBoolean.cpp +++ b/src/libslic3r/MeshBoolean.cpp @@ -23,6 +23,8 @@ #include #include #include +// BBS: for boolean using mcut +#include "mcut/include/mcut/mcut.h" namespace Slic3r { namespace MeshBoolean { @@ -470,5 +472,318 @@ CGALMeshPtr clone(const CGALMesh &m) } } // namespace cgal + + +namespace mcut { +/* BBS: MusangKing + * mcut mesh array format for Boolean Opts calculation + */ +struct McutMesh +{ + // variables for mesh data in a format suited for mcut + std::vector faceSizesArray; + std::vector faceIndicesArray; + std::vector vertexCoordsArray; +}; +void McutMeshDeleter::operator()(McutMesh *ptr) { delete ptr; } + +bool empty(const McutMesh &mesh) { return mesh.vertexCoordsArray.empty() || mesh.faceIndicesArray.empty(); } +void triangle_mesh_to_mcut(const TriangleMesh &src_mesh, McutMesh &srcMesh, const Transform3d &src_nm = Transform3d::Identity()) +{ + // vertices precision convention and copy + srcMesh.vertexCoordsArray.reserve(src_mesh.its.vertices.size() * 3); + for (int i = 0; i < src_mesh.its.vertices.size(); ++i) { + const Vec3d v = src_nm * src_mesh.its.vertices[i].cast(); + srcMesh.vertexCoordsArray.push_back(v[0]); + srcMesh.vertexCoordsArray.push_back(v[1]); + srcMesh.vertexCoordsArray.push_back(v[2]); + } + + // faces copy + srcMesh.faceIndicesArray.reserve(src_mesh.its.indices.size() * 3); + srcMesh.faceSizesArray.reserve(src_mesh.its.indices.size()); + for (int i = 0; i < src_mesh.its.indices.size(); ++i) { + const int &f0 = src_mesh.its.indices[i][0]; + const int &f1 = src_mesh.its.indices[i][1]; + const int &f2 = src_mesh.its.indices[i][2]; + srcMesh.faceIndicesArray.push_back(f0); + srcMesh.faceIndicesArray.push_back(f1); + srcMesh.faceIndicesArray.push_back(f2); + + srcMesh.faceSizesArray.push_back((uint32_t) 3); + } +} + +McutMeshPtr triangle_mesh_to_mcut(const indexed_triangle_set &M) +{ + std::unique_ptr out(new McutMesh{}); + TriangleMesh trimesh(M); + triangle_mesh_to_mcut(trimesh, *out.get()); + return out; +} + +TriangleMesh mcut_to_triangle_mesh(const McutMesh &mcutmesh) +{ + uint32_t ccVertexCount = mcutmesh.vertexCoordsArray.size() / 3; + auto &ccVertices = mcutmesh.vertexCoordsArray; + auto &ccFaceIndices = mcutmesh.faceIndicesArray; + auto &faceSizes = mcutmesh.faceSizesArray; + uint32_t ccFaceCount = faceSizes.size(); + // rearrange vertices/faces and save into result mesh + std::vector vertices(ccVertexCount); + for (uint32_t i = 0; i < ccVertexCount; i++) { + vertices[i][0] = (float) ccVertices[(uint64_t) i * 3 + 0]; + vertices[i][1] = (float) ccVertices[(uint64_t) i * 3 + 1]; + vertices[i][2] = (float) ccVertices[(uint64_t) i * 3 + 2]; + } + + // output faces + int faceVertexOffsetBase = 0; + + // for each face in CC + std::vector faces(ccFaceCount); + for (uint32_t f = 0; f < ccFaceCount; ++f) { + int faceSize = faceSizes.at(f); + + // for each vertex in face + for (int v = 0; v < faceSize; v++) { faces[f][v] = ccFaceIndices[(uint64_t) faceVertexOffsetBase + v]; } + faceVertexOffsetBase += faceSize; + } + + TriangleMesh out(vertices, faces); + return out; +} + +void do_boolean(McutMesh &srcMesh, const McutMesh &cutMesh, const std::string &boolean_opts) +{ + // create context + McContext context = MC_NULL_HANDLE; + McResult err = mcCreateContext(&context, static_cast(MC_DEBUG)); + + // We can either let MCUT compute all possible meshes (including patches etc.), or we can + // constrain the library to compute exactly the boolean op mesh we want. This 'constrained' case + // is done with the following flags. + // NOTE#1: you can extend these flags by bitwise ORing with additional flags (see `McDispatchFlags' in mcut.h) + // NOTE#2: below order of columns MATTERS + const std::map booleanOpts = { + {"A_NOT_B", MC_DISPATCH_FILTER_FRAGMENT_SEALING_INSIDE | MC_DISPATCH_FILTER_FRAGMENT_LOCATION_ABOVE}, + {"B_NOT_A", MC_DISPATCH_FILTER_FRAGMENT_SEALING_OUTSIDE | MC_DISPATCH_FILTER_FRAGMENT_LOCATION_BELOW}, + {"UNION", MC_DISPATCH_FILTER_FRAGMENT_SEALING_OUTSIDE | MC_DISPATCH_FILTER_FRAGMENT_LOCATION_ABOVE}, + {"INTERSECTION", MC_DISPATCH_FILTER_FRAGMENT_SEALING_INSIDE | MC_DISPATCH_FILTER_FRAGMENT_LOCATION_BELOW}, + }; + + std::map::const_iterator it = booleanOpts.find(boolean_opts); + McFlags boolOpFlags = it->second; + + if (srcMesh.vertexCoordsArray.empty() && (boolean_opts == "UNION" || boolean_opts == "B_NOT_A")) { + srcMesh = cutMesh; + return; + } + + err = mcDispatch(context, + MC_DISPATCH_VERTEX_ARRAY_DOUBLE | // vertices are in array of doubles + MC_DISPATCH_ENFORCE_GENERAL_POSITION | // perturb if necessary + boolOpFlags, // filter flags which specify the type of output we want + // source mesh + reinterpret_cast(srcMesh.vertexCoordsArray.data()), reinterpret_cast(srcMesh.faceIndicesArray.data()), + srcMesh.faceSizesArray.data(), static_cast(srcMesh.vertexCoordsArray.size() / 3), static_cast(srcMesh.faceSizesArray.size()), + // cut mesh + reinterpret_cast(cutMesh.vertexCoordsArray.data()), cutMesh.faceIndicesArray.data(), cutMesh.faceSizesArray.data(), + static_cast(cutMesh.vertexCoordsArray.size() / 3), static_cast(cutMesh.faceSizesArray.size())); + + // query the number of available connected component + uint32_t numConnComps; + err = mcGetConnectedComponents(context, MC_CONNECTED_COMPONENT_TYPE_FRAGMENT, 0, NULL, &numConnComps); + + std::vector connectedComponents(numConnComps, MC_NULL_HANDLE); + err = mcGetConnectedComponents(context, MC_CONNECTED_COMPONENT_TYPE_FRAGMENT, (uint32_t) connectedComponents.size(), connectedComponents.data(), NULL); + + McutMesh outMesh; + int N_vertices = 0; + // traversal of all connected components + for (int n = 0; n < numConnComps; ++n) { + // query the data of each connected component from MCUT + McConnectedComponent connComp = connectedComponents[n]; + + // query the vertices + McSize numBytes = 0; + err = mcGetConnectedComponentData(context, connComp, MC_CONNECTED_COMPONENT_DATA_VERTEX_DOUBLE, 0, NULL, &numBytes); + uint32_t ccVertexCount = (uint32_t) (numBytes / (sizeof(double) * 3)); + std::vector ccVertices((uint64_t) ccVertexCount * 3u, 0); + err = mcGetConnectedComponentData(context, connComp, MC_CONNECTED_COMPONENT_DATA_VERTEX_DOUBLE, numBytes, (void *) ccVertices.data(), NULL); + + // query the faces + numBytes = 0; + err = mcGetConnectedComponentData(context, connComp, MC_CONNECTED_COMPONENT_DATA_FACE_TRIANGULATION, 0, NULL, &numBytes); + std::vector ccFaceIndices(numBytes / sizeof(uint32_t), 0); + err = mcGetConnectedComponentData(context, connComp, MC_CONNECTED_COMPONENT_DATA_FACE_TRIANGULATION, numBytes, ccFaceIndices.data(), NULL); + std::vector faceSizes(ccFaceIndices.size() / 3, 3); + + const uint32_t ccFaceCount = static_cast(faceSizes.size()); + + // Here we show, how to know when connected components, pertain particular boolean operations. + McPatchLocation patchLocation = (McPatchLocation) 0; + err = mcGetConnectedComponentData(context, connComp, MC_CONNECTED_COMPONENT_DATA_PATCH_LOCATION, sizeof(McPatchLocation), &patchLocation, NULL); + + McFragmentLocation fragmentLocation = (McFragmentLocation) 0; + err = mcGetConnectedComponentData(context, connComp, MC_CONNECTED_COMPONENT_DATA_FRAGMENT_LOCATION, sizeof(McFragmentLocation), &fragmentLocation, NULL); + + outMesh.vertexCoordsArray.insert(outMesh.vertexCoordsArray.end(), ccVertices.begin(), ccVertices.end()); + + // add offset to face index + for (size_t i = 0; i < ccFaceIndices.size(); i++) { + ccFaceIndices[i] += N_vertices; + } + + int faceVertexOffsetBase = 0; + + // for each face in CC + std::vector faces(ccFaceCount); + for (uint32_t f = 0; f < ccFaceCount; ++f) { + bool reverseWindingOrder = (fragmentLocation == MC_FRAGMENT_LOCATION_BELOW) && (patchLocation == MC_PATCH_LOCATION_OUTSIDE); + int faceSize = faceSizes.at(f); + if (reverseWindingOrder) { + std::vector faceIndex(faceSize); + // for each vertex in face + for (int v = faceSize - 1; v >= 0; v--) { faceIndex[v] = ccFaceIndices[(uint64_t) faceVertexOffsetBase + v]; } + std::copy(faceIndex.begin(), faceIndex.end(), ccFaceIndices.begin() + faceVertexOffsetBase); + } + faceVertexOffsetBase += faceSize; + } + + outMesh.faceIndicesArray.insert(outMesh.faceIndicesArray.end(), ccFaceIndices.begin(), ccFaceIndices.end()); + outMesh.faceSizesArray.insert(outMesh.faceSizesArray.end(), faceSizes.begin(), faceSizes.end()); + + N_vertices += ccVertexCount; + } + + // free connected component data + err = mcReleaseConnectedComponents(context, (uint32_t) connectedComponents.size(), connectedComponents.data()); + + // destroy context + err = mcReleaseContext(context); + + srcMesh = outMesh; +} + +/* BBS: Musang King + * mcut for Mesh Boolean which provides C-style syntax API + */ +std::vector make_boolean(const McutMesh &srcMesh, const McutMesh &cutMesh, const std::string &boolean_opts) +{ + // create context + McContext context = MC_NULL_HANDLE; + McResult err = mcCreateContext(&context, static_cast(MC_DEBUG)); + + // We can either let MCUT compute all possible meshes (including patches etc.), or we can + // constrain the library to compute exactly the boolean op mesh we want. This 'constrained' case + // is done with the following flags. + // NOTE#1: you can extend these flags by bitwise ORing with additional flags (see `McDispatchFlags' in mcut.h) + // NOTE#2: below order of columns MATTERS + const std::map booleanOpts = { + {"A_NOT_B", MC_DISPATCH_FILTER_FRAGMENT_SEALING_INSIDE | MC_DISPATCH_FILTER_FRAGMENT_LOCATION_ABOVE}, + {"B_NOT_A", MC_DISPATCH_FILTER_FRAGMENT_SEALING_OUTSIDE | MC_DISPATCH_FILTER_FRAGMENT_LOCATION_BELOW}, + {"UNION", MC_DISPATCH_FILTER_FRAGMENT_SEALING_OUTSIDE | MC_DISPATCH_FILTER_FRAGMENT_LOCATION_ABOVE}, + {"INTERSECTION", MC_DISPATCH_FILTER_FRAGMENT_SEALING_INSIDE | MC_DISPATCH_FILTER_FRAGMENT_LOCATION_BELOW}, + }; + + std::map::const_iterator it = booleanOpts.find(boolean_opts); + McFlags boolOpFlags = it->second; + + err = mcDispatch(context, + MC_DISPATCH_VERTEX_ARRAY_DOUBLE | // vertices are in array of doubles + MC_DISPATCH_ENFORCE_GENERAL_POSITION | // perturb if necessary + boolOpFlags, // filter flags which specify the type of output we want + // source mesh + reinterpret_cast(srcMesh.vertexCoordsArray.data()), reinterpret_cast(srcMesh.faceIndicesArray.data()), + srcMesh.faceSizesArray.data(), static_cast(srcMesh.vertexCoordsArray.size() / 3), static_cast(srcMesh.faceSizesArray.size()), + // cut mesh + reinterpret_cast(cutMesh.vertexCoordsArray.data()), cutMesh.faceIndicesArray.data(), cutMesh.faceSizesArray.data(), + static_cast(cutMesh.vertexCoordsArray.size() / 3), static_cast(cutMesh.faceSizesArray.size())); + + // query the number of available connected component + uint32_t numConnComps; + err = mcGetConnectedComponents(context, MC_CONNECTED_COMPONENT_TYPE_FRAGMENT, 0, NULL, &numConnComps); + + std::vector connectedComponents(numConnComps, MC_NULL_HANDLE); + err = mcGetConnectedComponents(context, MC_CONNECTED_COMPONENT_TYPE_FRAGMENT, (uint32_t) connectedComponents.size(), connectedComponents.data(), NULL); + + std::vector outs; + // traversal of all connected components + for (int n = 0; n < numConnComps; ++n) { + // query the data of each connected component from MCUT + McConnectedComponent connComp = connectedComponents[n]; + + // query the vertices + McSize numBytes = 0; + err = mcGetConnectedComponentData(context, connComp, MC_CONNECTED_COMPONENT_DATA_VERTEX_DOUBLE, 0, NULL, &numBytes); + uint32_t ccVertexCount = (uint32_t) (numBytes / (sizeof(double) * 3)); + std::vector ccVertices((uint64_t) ccVertexCount * 3u, 0); + err = mcGetConnectedComponentData(context, connComp, MC_CONNECTED_COMPONENT_DATA_VERTEX_DOUBLE, numBytes, (void *) ccVertices.data(), NULL); + + // query the faces + numBytes = 0; + err = mcGetConnectedComponentData(context, connComp, MC_CONNECTED_COMPONENT_DATA_FACE_TRIANGULATION, 0, NULL, &numBytes); + std::vector ccFaceIndices(numBytes / sizeof(uint32_t), 0); + err = mcGetConnectedComponentData(context, connComp, MC_CONNECTED_COMPONENT_DATA_FACE_TRIANGULATION, numBytes, ccFaceIndices.data(), NULL); + std::vector faceSizes(ccFaceIndices.size() / 3, 3); + + const uint32_t ccFaceCount = static_cast(faceSizes.size()); + + // Here we show, how to know when connected components, pertain particular boolean operations. + McPatchLocation patchLocation = (McPatchLocation) 0; + err = mcGetConnectedComponentData(context, connComp, MC_CONNECTED_COMPONENT_DATA_PATCH_LOCATION, sizeof(McPatchLocation), &patchLocation, NULL); + + McFragmentLocation fragmentLocation = (McFragmentLocation) 0; + err = mcGetConnectedComponentData(context, connComp, MC_CONNECTED_COMPONENT_DATA_FRAGMENT_LOCATION, sizeof(McFragmentLocation), &fragmentLocation, NULL); + + // rearrange vertices/faces and save into result mesh + std::vector vertices(ccVertexCount); + for (uint32_t i = 0; i < ccVertexCount; ++i) { + vertices[i][0] = (float) ccVertices[(uint64_t) i * 3 + 0]; + vertices[i][1] = (float) ccVertices[(uint64_t) i * 3 + 1]; + vertices[i][2] = (float) ccVertices[(uint64_t) i * 3 + 2]; + } + + // output faces + int faceVertexOffsetBase = 0; + + // for each face in CC + std::vector faces(ccFaceCount); + for (uint32_t f = 0; f < ccFaceCount; ++f) { + bool reverseWindingOrder = (fragmentLocation == MC_FRAGMENT_LOCATION_BELOW) && (patchLocation == MC_PATCH_LOCATION_OUTSIDE); + int faceSize = faceSizes.at(f); + + // for each vertex in face + for (int v = (reverseWindingOrder ? (faceSize - 1) : 0); (reverseWindingOrder ? (v >= 0) : (v < faceSize)); v += (reverseWindingOrder ? -1 : 1)) { + faces[f][v] = ccFaceIndices[(uint64_t) faceVertexOffsetBase + v]; + } + faceVertexOffsetBase += faceSize; + } + + TriangleMesh out(vertices, faces); + outs.emplace_back(out); + } + + // free connected component data + err = mcReleaseConnectedComponents(context, (uint32_t) connectedComponents.size(), connectedComponents.data()); + + // destroy context + err = mcReleaseContext(context); + + return outs; +} + +void make_boolean(const TriangleMesh &src_mesh, const TriangleMesh &cut_mesh, std::vector &dst_mesh, const std::string &boolean_opts) +{ + McutMesh srcMesh, cutMesh; + triangle_mesh_to_mcut(src_mesh, srcMesh); + triangle_mesh_to_mcut(cut_mesh, cutMesh); + dst_mesh = make_boolean(srcMesh, cutMesh, boolean_opts); +} + +} // namespace mcut + + } // namespace MeshBoolean } // namespace Slic3r diff --git a/src/libslic3r/MeshBoolean.hpp b/src/libslic3r/MeshBoolean.hpp index b778af0bb6..520d2f189a 100644 --- a/src/libslic3r/MeshBoolean.hpp +++ b/src/libslic3r/MeshBoolean.hpp @@ -72,6 +72,27 @@ bool does_bound_a_volume(const CGALMesh &mesh); bool empty(const CGALMesh &mesh); } +namespace mcut { +struct McutMesh; +struct McutMeshDeleter +{ + void operator()(McutMesh *ptr); +}; +using McutMeshPtr = std::unique_ptr; +bool empty(const McutMesh &mesh); + +McutMeshPtr triangle_mesh_to_mcut(const indexed_triangle_set &M); +TriangleMesh mcut_to_triangle_mesh(const McutMesh &mcutmesh); + +// do boolean and save result to srcMesh +void do_boolean(McutMesh &srcMesh, const McutMesh &cutMesh, const std::string &boolean_opts); + +std::vector make_boolean(const McutMesh &srcMesh, const McutMesh &cutMesh, const std::string &boolean_opts); + +// do boolean and convert result to TriangleMesh +void make_boolean(const TriangleMesh &src_mesh, const TriangleMesh &cut_mesh, std::vector &dst_mesh, const std::string &boolean_opts); +} // namespace mcut + } // namespace MeshBoolean } // namespace Slic3r #endif // libslic3r_MeshBoolean_hpp_ diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 770f9b23db..027d848260 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1053,6 +1053,28 @@ void ModelObject::assign_new_unique_ids_recursive() // BBS: production extension int ModelObject::get_backup_id() const { return m_model ? get_model()->get_object_backup_id(*this) : -1; } +// BBS: Boolean Operations impl. - MusangKing +bool ModelObject::make_boolean(ModelObject *cut_object, const std::string &boolean_opts) +{ + // merge meshes into single volume instead of multi-parts object + if (this->volumes.size() != 1) { + // we can't merge meshes if there's not just one volume + return false; + } + std::vector new_meshes; + + const TriangleMesh &cut_mesh = cut_object->mesh(); + MeshBoolean::mcut::make_boolean(this->mesh(), cut_mesh, new_meshes, boolean_opts); + + this->clear_volumes(); + int i = 1; + for (TriangleMesh &mesh : new_meshes) { + ModelVolume *vol = this->add_volume(mesh); + vol->name = this->name + "_" + std::to_string(i++); + } + return true; +} + ModelVolume* ModelObject::add_volume(const TriangleMesh &mesh) { ModelVolume* v = new ModelVolume(this, mesh); diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index b444a5e183..a0a1a6582b 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -508,6 +508,10 @@ public: ModelObjectPtrs segment(size_t instance, unsigned int max_extruders, double smoothing_alpha = 0.5, int segment_number = 5); void split(ModelObjectPtrs* new_objects); void merge(); + + // BBS: Boolean opts - Musang King + bool make_boolean(ModelObject *cut_object, const std::string &boolean_opts); + ModelObjectPtrs merge_volumes(std::vector& vol_indeces);//BBS // Support for non-uniform scaling of instances. If an instance is rotated by angles, which are not multiples of ninety degrees, // then the scaling in world coordinate system is not representable by the Geometry::Transformation structure. diff --git a/src/mcut/CMakeLists.txt b/src/mcut/CMakeLists.txt new file mode 100644 index 0000000000..9024986d75 --- /dev/null +++ b/src/mcut/CMakeLists.txt @@ -0,0 +1,417 @@ +# +# 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: +# (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 +# +############################################################################################ +# +# You can configure MCUT with the following CMake options: +# +# MCUT_BUILD_AS_SHARED_LIB [default=ON] - Build MCUT as a shared/dynamic library (.so/.dll). -- SET TO OFF +# MCUT_BUILD_DOCUMENTATION [default=OFF] - Build documentation (explicit dependancy on Doxygen) +# MCUT_BUILD_TESTS [default=OFF] - Build the tests (implicit dependancy on GoogleTest) +# MCUT_BUILD_TUTORIALS [default=OFF] - Build tutorials +# MCUT_BUILD_WITH_COMPUTE_HELPER_THREADPOOL [default=ON] - Build as configurable multi-threaded library +# +# This script will define the following CMake cache variables: +# +# MCUT_INCLUDE_DIR - the MCUT include directory +# MCUT_LIB_PATH - path to the MCUT library (i.e. .so/.dll or .a/.lib files) + +cmake_minimum_required(VERSION 3.12...3.13 FATAL_ERROR) + +project(mcut LANGUAGES CXX C) + +get_directory_property(MCUT_PARENT_DIR PARENT_DIRECTORY) +if(NOT MCUT_PARENT_DIR) + set(MCUT_TOPLEVEL_PROJECT ON) +else() + set(MCUT_TOPLEVEL_PROJECT OFF) +endif() + +set ( DESCRIPTION "Mesh cutting library." ) + +if (NOT WIN32 AND NOT CMAKE_BUILD_TYPE) + message(STATUS "No build type selected, default to Release") + set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE) +endif() + +set (CMAKE_DEBUG_POSTFIX "d") + +#set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +#set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) +#set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) + +list (APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + +set(MCUT_MAJOR 1) +set(MCUT_MINOR 1) +set(MCUT_PATCH 0) + +set( MCUT_VERSION "${MCUT_MAJOR}.${MCUT_MINOR}.${MCUT_PATCH}" ) + +message(STATUS "[MCUT] version: ${MCUT_VERSION}") + +set (CMAKE_CXX_STANDARD 11) +set (CMAKE_CXX_STANDARD_REQUIRED True) +set (CMAKE_EXPORT_COMPILE_COMMANDS ON) + +set (project_API_version_string "${MCUT_VERSION}") +set (project_build_version_string "${MCUT_VERSION}") +set (project_namespace_name MCUT) + +# Only do these if this is the main project, and not if it is included through add_subdirectory +if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) + + # Let's ensure -std=c++xx instead of -std=g++xx + set(CMAKE_CXX_EXTENSIONS OFF) + + # Let's nicely support folders in IDEs + set_property(GLOBAL PROPERTY USE_FOLDERS ON) + + # Testing only available if this is the main app + # Note this needs to be done in the main CMakeLists + # since it calls enable_testing, which must be in the + # main CMakeLists. + include(CTest) + + if(MCUT_BUILD_DOCUMENTATION) + # Docs only available if this is the main app + find_package(Doxygen) + + if(Doxygen_FOUND) + add_subdirectory(docs) + else() + message(STATUS "Doxygen not found, not building docs") + endif() + endif() +endif() + +include(CMakePrintHelpers) + +# +# User options +# +option(MCUT_BUILD_DOCUMENTATION "Configure to build docs with Doxygen" OFF) # OFF by default +if (MCUT_TOPLEVEL_PROJECT AND NOT MCUT_BUILD_TESTS) + option(MCUT_BUILD_TESTS "Configure to build tests" ON) +endif() +option(MCUT_BUILD_AS_SHARED_LIB "Configure to build MCUT as a shared/dynamic library" OFF) +option(MCUT_BUILD_WITH_COMPUTE_HELPER_THREADPOOL "Configure to build MCUT engine with a shared (amongst contexts) thread-pool" ON) +if (MCUT_TOPLEVEL_PROJECT AND NOT MCUT_BUILD_TUTORIALS) + option(MCUT_BUILD_TUTORIALS "Configure to build MCUT tutorials" ON) +endif() +# +# machine-precision-numbers library targets +# +set (target_name mcut) + +# +# MCUT compilation variables/settings +# +set (MCUT_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include CACHE STRING "The MCUT include directory") + +message(STATUS "[MCUT] MCUT_INCLUDE_DIR=${MCUT_INCLUDE_DIR}") + +list(APPEND include_dirs ${MCUT_INCLUDE_DIR}) +list(APPEND compilation_flags "") +if(MCUT_BUILD_AS_SHARED_LIB) + list(APPEND preprocessor_defs -DMCUT_SHARED_LIB=1) +endif() + +find_package(Threads REQUIRED) + +list(APPEND extra_libs Threads::Threads) + +if (MCUT_BUILD_WITH_COMPUTE_HELPER_THREADPOOL) + list(APPEND preprocessor_defs -DMCUT_WITH_COMPUTE_HELPER_THREADPOOL=1) +endif() + +if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") + list(APPEND compilation_flags -Wall -Wextra) +elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel") + list(APPEND compilation_flags w3 -diag-disable:remark) +elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") + list(APPEND compilation_flags /W4 /wd26812 /bigobj) + list(APPEND preprocessor_defs -D_CRT_SECURE_NO_WARNINGS) +endif() + +message(STATUS "[MCUT] compilation_flags=${compilation_flags}") +message(STATUS "[MCUT] preprocessor_defs=${preprocessor_defs}") +message(STATUS "[MCUT] extra_libs=${extra_libs}") + +set ( project_source_files + ${CMAKE_CURRENT_SOURCE_DIR}/source/mcut.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/source/kernel.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/source/hmesh.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/source/math.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/source/bvh.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/source/shewchuk.c + ${CMAKE_CURRENT_SOURCE_DIR}/source/frontend.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/source/preproc.cpp) + +# +# Create MCUT target(s) +# + +# This function invokes commands which create a library target (static, shared etc.) +function(create_library_target LIBRARY_TYPE) + message(STATUS "[MCUT] create target: name=${target_name} type=${LIBRARY_TYPE}") + + add_library(${target_name} ${LIBRARY_TYPE} ${project_source_files}) + + target_include_directories(${target_name} PRIVATE ${include_dirs}) + target_link_libraries(${target_name} PRIVATE ${extra_libs}) + target_compile_options(${target_name} PRIVATE ${compilation_flags}) + target_compile_definitions(${target_name} PRIVATE ${preprocessor_defs} ) + + if (MCUT_BUILD_AS_SHARED_LIB AND WIN32) + # add macro to export .dll symbols + target_compile_definitions(${target_name} PRIVATE -DMCUT_EXPORT_SHARED_LIB_SYMBOLS=1) + endif() + + set_property(TARGET ${target_name} PROPERTY VERSION ${project_build_version_string}) + set_property(TARGET ${target_name} PROPERTY SOVERSION ${project_API_version_string}) + + get_target_property(target_type ${target_name} TYPE) + + if ("${target_type}" STREQUAL "SHARED") + set_property(TARGET ${target_name} PROPERTY POSITION_INDEPENDENT_CODE ON) + if(MSVC) + set_property(TARGET ${target_name} PROPERTY WINDOWS_EXPORT_ALL_SYMBOLS ON) + endif() + endif() +endfunction() + +# +# create target +# +if(MCUT_BUILD_AS_SHARED_LIB) + create_library_target(SHARED) +else() + create_library_target(STATIC) +endif() + +set (MCUT_LIB_PATH $ CACHE STRING "Path to the compiled MCUT library file") +message(STATUS "[MCUT] MCUT_LIB_PATH=${MCUT_LIB_PATH}") + +# +# tests, tutorials etc. are dependant on third-party libs (file loaders etc.) +# We enable the code that is used to download those projects here: +# +if(${MCUT_BUILD_TESTS} OR ${MCUT_BUILD_TUTORIALS}) + + # FetchContent added in CMake 3.11, downloads during the configure step + include(FetchContent) + # FetchContent_MakeAvailable was not added until CMake 3.14; use our shim + if(${CMAKE_VERSION} VERSION_LESS 3.14) + include(cmake/add_FetchContent_MakeAvailable.cmake) + endif() + + FetchContent_Populate( + libigl + GIT_REPOSITORY https://github.com/libigl/libigl.git + GIT_TAG v2.3.0 + GIT_PROGRESS TRUE + ) + + #set(libigl_include_dir ${CMAKE_BINARY_DIR}/libigl-src/include) + set(libigl_include_dir ${libigl_SOURCE_DIR}/include) + + set(LIBIGL_EIGEN_VERSION 3.3.7 CACHE STRING "Default version of Eigen used by libigl.") + + # used by tests & tutorials + #download_project(PROJ eigen + # GIT_REPOSITORY https://gitlab.com/libeigen/eigen.git + # GIT_TAG ${LIBIGL_EIGEN_VERSION} + # ${UPDATE_DISCONNECTED_IF_AVAILABLE} + #) + FetchContent_Declare( + eigen + GIT_REPOSITORY https://gitlab.com/libeigen/eigen.git + GIT_TAG ${LIBIGL_EIGEN_VERSION} + GIT_SHALLOW TRUE + GIT_PROGRESS TRUE + ) + + set(EIGEN_BUILD_DOC OFF) + # note: To disable eigen tests, + # you should put this code in a add_subdirectory to avoid to change + # BUILD_TESTING for your own project too since variables are directory + # scoped + set(BUILD_TESTING OFF) + set(EIGEN_BUILD_PKGCONFIG OFF) + set( OFF) + FetchContent_MakeAvailable(eigen) + + #set(eigen_include_dir ${CMAKE_BINARY_DIR}/eigen-src) + set(eigen_include_dir ${eigen_SOURCE_DIR}) +endif() + +# +# tests +# +if(MCUT_BUILD_TESTS) + add_subdirectory(tests) +endif() + +# +# tutorials +# +if(MCUT_BUILD_TUTORIALS) + add_subdirectory(tutorials) +endif() + +# +# documentation +# +if(MCUT_BUILD_DOCUMENTATION) + add_subdirectory(docs) +endif() + +######################################################## +### PACKAGING ### +### This is a quite INCOMPLETE set of variables that ### +### should be set for the various generators. ### +### Consult the CPack documentations for a full set. ### +######################################################## + +# https://gitlab.kitware.com/cmake/community/-/wikis/doc/cpack/Component-Install-With-CPack +# https://stackoverflow.com/questions/6003374/what-is-cmake-equivalent-of-configure-prefix-dir-make-all-install + +# TODO: package documentation files + +if(MCUT_BUILD_AS_SHARED_LIB) + # + # dynamic libs + # + + install(TARGETS ${mpn_shared_lib_name} + LIBRARY + DESTINATION lib/shared + COMPONENT dynamic_libraries) +else() + # + # static libs + # + + install(TARGETS ${mpn_static_lib_name} + ARCHIVE + DESTINATION lib/static + COMPONENT static_libraries) +endif() + +# +# headers +# +install(FILES ${MCUT_INCLUDE_DIR}/mcut/mcut.h ${MCUT_INCLUDE_DIR}/mcut/platform.h + DESTINATION include/mcut + COMPONENT headers) + +install(FILES + ${CMAKE_CURRENT_SOURCE_DIR}/LICENSE.txt + ${CMAKE_CURRENT_SOURCE_DIR}/README.md + DESTINATION ./ + COMPONENT text_files) + +# +# notify CPack of the names of all of the components in the project +# +set(CPACK_COMPONENTS_ALL static_libraries dynamic_libraries headers text_files) # applications + +set(CPACK_COMPONENT_APPLICATIONS_DISPLAY_NAME "MCUT Application") +set(CPACK_COMPONENT_STATIC_LIBRARIES_DISPLAY_NAME "Static Libraries") +set(CPACK_COMPONENT_DYNAMIC_LIBRARIES_DISPLAY_NAME "Dynamics Libraries") +set(CPACK_COMPONENT_HEADERS_DISPLAY_NAME "C++ Headers") + +set(CPACK_COMPONENT_APPLICATIONS_DESCRIPTION + "A simple application using MCUT") +set(CPACK_COMPONENT_STATIC_LIBRARIES_DESCRIPTION + "Static libraries used to build programs with MCUT") + set(CPACK_COMPONENT_DYNAMIC_LIBRARIES_DESCRIPTION + "Dynamic libraries used to build programs with MCUT") +set(CPACK_COMPONENT_HEADERS_DESCRIPTION + "C/C++ header files for use with MCUT") + +# +# component dependencies +# +set(CPACK_COMPONENT_HEADERS_DEPENDS static_libraries dynamic_libraries) + +set(CPACK_COMPONENT_APPLICATIONS_GROUP "Runtime") +set(CPACK_COMPONENT_STATIC_LIBRARIES_GROUP "Development") +set(CPACK_COMPONENT_DYNAMIC_LIBRARIES_GROUP "Development") +set(CPACK_COMPONENT_HEADERS_GROUP "Development") + +set(CPACK_COMPONENT_GROUP_DEVELOPMENT_DESCRIPTION + "All of the tools you'll ever need to develop software") + +set (CPACK_PACKAGE_NAME "MCUT") +set (CPACK_PACKAGE_VENDOR "Floyd M. Chitalu") +set (CPACK_PACKAGE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}") +set (CPACK_PACKAGE_VERSION_MAJOR "${MCUT_MAJOR}") +set (CPACK_PACKAGE_VERSION_MINOR "${MCUT_MINOR}") +set (CPACK_PACKAGE_VERSION_PATCH "${MCUT_PATCH}") +#set (CPACK_PACKAGE_DESCRIPTION "MCUT (pronounced ‘emcut’) is a tool for cutting meshes.") +#set (CPACK_PACKAGE_DESCRIPTION_FILE ${CMAKE_CURRENT_SOURCE_DIR}/DESCRIPTION.txt) +set (CPACK_PACKAGE_DESCRIPTION_SUMMARY "MCUT is a library for cutting meshes to perform tasks like boolean operations and more.") +set (CPACK_PACKAGE_HOMEPAGE_URL "https://cutdigital.github.io/mcut.site/") +set (CPACK_PACKAGE_INSTALL_DIRECTORY ${CPACK_PACKAGE_NAME}) +# set (CPACK_PACKAGE_ICON ) +set (CPACK_PACKAGE_CHECKSUM SHA256) +#set (CPACK_PROJECT_CONFIG_FILE ) +set (CPACK_RESOURCE_FILE_LICENSE ${CMAKE_CURRENT_SOURCE_DIR}/LICENSE.txt) # must also include in install command +set (CPACK_RESOURCE_FILE_README ${CMAKE_CURRENT_SOURCE_DIR}/README.md) +#set (CPACK_RESOURCE_FILE_WELCOME ${CMAKE_CURRENT_SOURCE_DIR}/WELCOME.txt) + +if (WIN32) + if (USE_WIX_TOOLSET) + set(CPACK_GENERATOR "WIX") # this need WiX Tooset installed and a path to candle.exe + else () + set(CPACK_GENERATOR "NSIS") # this needs NSIS installed, and available + endif () +elseif ( ${CMAKE_SYSTEM_NAME} MATCHES "Darwin" ) + set(CPACK_GENERATOR "PackageMake") +else () + set(CPACK_GENERATOR "TGZ") +endif () + +#set (CPACK_OUTPUT_CONFIG_FILE ) # Defaults to CPackConfig.cmake. +#set (CPACK_PACKAGE_EXECUTABLES ) +set (CPACK_STRIP_FILES TRUE) +# set (CPACK_VERBATIM_VARIABLES ) +# set (CPACK_SOURCE_PACKAGE_FILE_NAME ) +# set (CPACK_SOURCE_STRIP_FILES ) +# set (CPACK_SOURCE_GENERATOR ) +# set (CPACK_SOURCE_OUTPUT_CONFIG_FILE ) +# set (CPACK_SOURCE_IGNORE_FILES ) +# set (CPACK_CMAKE_GENERATOR ) +# set (CPACK_INSTALL_CMAKE_PROJECTS ) +# set (CPACK_INSTALL_CMAKE_PROJECTS ) +# set (CPACK_SYSTEM_NAME ) +# set (CPACK_PACKAGE_VERSION ) +# set (CPACK_TOPLEVEL_TAG ) +# set (CPACK_INSTALL_COMMANDS ) +# set (CPACK_INSTALLED_DIRECTORIES ) +# set ( ) + + +include(CPack) + +# eof \ No newline at end of file diff --git a/src/mcut/LICENSE.GPL.txt b/src/mcut/LICENSE.GPL.txt new file mode 100644 index 0000000000..94a9ed024d --- /dev/null +++ b/src/mcut/LICENSE.GPL.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/src/mcut/LICENSE.txt b/src/mcut/LICENSE.txt new file mode 100644 index 0000000000..237246b1dc --- /dev/null +++ b/src/mcut/LICENSE.txt @@ -0,0 +1,19 @@ +Permission to use this software is governed by two licensing options which are mutually exclusive: + +(A) Open source GNU General Public License ("GPL"); a copy of which you should have recieved. + - see also: + +(B) Commercial license, which can be purchased from the owner. + + The commercial license option is for users that wish to use MCUT in + their products for comercial purposes but do not wish to release their + software under the GPL. + + For more information, email "floyd.m.chitalu@gmail.com". + +These options protect the project's commercial value and thus make it possible for the +author to guarantee long term support, maintenance and further development of the +code for the benefit of the project and its users. + +This software is also partly based on "CDT" (C++ library for constrained Delaunay triangulation): https://github.com/artem-ogre/CDT +CDT files that were originally under the MPL-2.0 are dual licensed under the MPL-2.0 and the GNU General Public License (GPL) licenses. \ No newline at end of file diff --git a/src/mcut/README.md b/src/mcut/README.md new file mode 100644 index 0000000000..3f37efbea6 --- /dev/null +++ b/src/mcut/README.md @@ -0,0 +1,112 @@ +# MCUT Overview + +Gist: _A simple and fast C++ library for mesh booleans and more..._ + +[![Windows](https://github.com/cutdigital/mcut/actions/workflows/windows.yml/badge.svg)](https://github.com/cutdigital/mcut/actions/workflows/windows.yml) +[![MacOS](https://github.com/cutdigital/mcut/actions/workflows/macos.yml/badge.svg)](https://github.com/cutdigital/mcut/actions/workflows/macos.yml) [![Linux](https://github.com/cutdigital/mcut/actions/workflows/linux.yaml/badge.svg)](https://github.com/cutdigital/mcut/actions/workflows/linux.yaml) + +This is a software project designed for a broad range of real-world problems relating to 3D modelling and design tasks. Application areas include computer animation, aerospace and automotive engineering, mining, civil and mechanical engineering amongst others. + +The project is called "MCUT" (short for 'mesh cutting'), and it provides functionality to perform fast and robust geometry operations, as shown below: + +

+ + Figure 1: Generate, slice and perform Booleans without errors. +

+ + +The codebase provides a comprehensive tool for ensuring that computer-aided planning tasks for e.g. mine-design, rock strata boring (e.g. underground-tunnel excavations), oil-well drilling and general 3D modelling for animation are achievable with robustness. The tool is developed to take advantage of modern high-performance parallel computing hardware for you, and is demonstrably robust by using precise geometric algorithms that are implemented in C++ and accessed through an intuitive API that resembles the all-familiar C programming language. + +Importantly, MCUT is designed with the philosophy that users don't know or don't care about esoteric problems with floating point arithmetic. + +# Capabilities + +MCUT is a tool for partitioning meshes that represent solids or open surfaces: It is a code library for cutting 3D mesh objects using their geometry to produce crisp fragments at fine scale, which is useful for operations like slicing and boolean operations (union, subtraction and intersection). Supported features include (see images below): + +* **Stencilling**: exact cut-outs of the cutting surface +* **Intersection curve access**: geometry representing lines of intersection-contour points +* **Partial cuts**: producing valid results where an open-surface is not necessarily completely cutting through a solid. +* **Concatenation**: merging a solids or open-surfaces with another. +* **Sectioning**: elimination of material/volume on one side of a specified surface (e.g. a plane) +* **Splitting**: partitioning one mesh using another that might be open or solid. +* **Cross-platform**: tested on Windows, Linux (Ubuntu), and macOS +* **Bloat-free**: no external dependencies. +* **Performant**: continuously profiled, and optimized. +* **Numerically robust**: Algorithms rely on robust geometric predicates. + +What is being offered is a general solution to the problem of resolving solid- and/or open-mesh intersections. It is a solution that is sought by many companies, researchers, and private individuals for its ability to address extremely difficult problems relating to computational geometry in 3D. A classic application is constructive solid geometry (CSG) i.e. the “boolean operation”, which is shown below, where the resulting meshes/objects are produced with MCUT: + +

+ + Figure 2: Generate solids / polygons using a robust Boolean engine, where other technologies fail, MCUT solids will be valid. +

+ +# Practical benefits and advantages for users + +The expert capabilities of MCUT will allow companies, individuals and researchers-alike to develop robust (and fast) Computer-Aided Design (CAD) and Manufacturing (CAM) tools. For example, these tools could cater to the design of industry-specific structural models like tunnels, drill holes, mechanical instruments and rock-block models. All this alongside the ability to handle general 3D modelling tasks that are typical in industry and academic-fields related to computer graphics (e.g. game-engine level design) and mechanical engineering (e.g. fracture simulation). In essence, users of MCUT are provided with the capability to create robust derivative products and tools for generating (and testing) structural designs in a virtual setting for short- and long-term production operations and feasibility tests/studies. + +The following images show more examples of what users can achieve with MCUT: + +

+ + Figure 3: Fracture simulation using the Extended Finite Element Method (XFEM) (https://onlinelibrary.wiley.com/doi/abs/10.1111/cgf.13953), where MCUT is used to create fragment geometry by intersecting the simulation domain with propagated cracks. +

+ +

+ + Figure 4: Intersecting a gear cog with a surface to model the fracturing of steel. +

+ +

+ + Figure 5: Merging an engine with the axle shaft to model their connectivity. +

+ +

+ + Figure 6: Intersecting a hand model and a sphere, showing how MCUT can also be useful for planning and designing molding processes for e.g. 3D printing. +

+ +

+ + Figure 8: Assorted results produced by intersecting the Stanford bunny model and armadillo. +

+ +

+ + Figure 9: Tunnel excavation of a mountainous terrain for modelling underground construction with a boring machine (represented with cylinder). Note how the input meshes need not be solids. +

+ +

+ + Figure 10: Creating an Open-Pit mine model on a rough terrain for e.g. pre-planning operations. +

+ +

+ + Figure 11: An example of sectioning with a flat plane, which can be used to eliminate material/volume on either side of this plane or create hollow carve-outs. +

+ +# Source code and test applications + +The source code is available for your perusal and evaluation. You can access right here on Github. This is an opportunity for you to trial and experiment with MCUT for your needs. Here is a quick example of how you clone and build the library: + +* `git clone https://github.com/cutdigital/mcut.git` +* `mkdir build` +* `cd build` +* `cmake ..` (see `CMakeLists.txt` for available build configuration options) +* run `make -j4` *IF* you are on Linux/MacOS terminal, *ELSE* open the generated `.sln` with e.g. Visual Studio + +Next, try out one of the tutorials! + +# Licensing + +MCUT is available under an Open Source license as well as a commercial license. Users choosing to use MCUT under the free-of-charge Open Source license (e.g. for academic purposes) simply need to comply to its terms, otherwise a commercial license is required. The Open Source license is the "GNU General Public License" (GPL). In cases where the constraints of the Open source license prevent you from using MCUT, a commercial license can be purchased. The library is licensed with an attractively low price which is a one-off sum, requiring no further loyalty fees with guarranteed future updates for free. + +These options protect the project's commercial value and thus make it possible for the author to guarantee long term support, maintenance and further development of the code for the benefit of the project and its users. + +--- + +If MCUT helped you please consider adding a star here on GitHub. This means a lot to the author. + +_You can also send an [email](floyd.m.chitalu@gmail.com) to the author if you have questions about MCUT_. diff --git a/src/mcut/cmake/add_FetchContent_MakeAvailable.cmake b/src/mcut/cmake/add_FetchContent_MakeAvailable.cmake new file mode 100644 index 0000000000..f939da810c --- /dev/null +++ b/src/mcut/cmake/add_FetchContent_MakeAvailable.cmake @@ -0,0 +1,7 @@ +macro(FetchContent_MakeAvailable NAME) + FetchContent_GetProperties(${NAME}) + if(NOT ${NAME}_POPULATED) + FetchContent_Populate(${NAME}) + add_subdirectory(${${NAME}_SOURCE_DIR} ${${NAME}_BINARY_DIR}) + endif() +endmacro() diff --git a/src/mcut/include/mcut/internal/bvh.h b/src/mcut/include/mcut/internal/bvh.h new file mode 100644 index 0000000000..cb040ac71d --- /dev/null +++ b/src/mcut/include/mcut/internal/bvh.h @@ -0,0 +1,327 @@ +/** + * 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: + * (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 + */ +#ifndef MCUT_BVH_H_ +#define MCUT_BVH_H_ + +#include "mcut/internal/hmesh.h" +#include "mcut/internal/math.h" + +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) +#include "mcut/internal/tpool.h" +#endif + +// OIBVH is over 2-3x faster than the alternative (classic) BVH approaches. +// Our alternative BVH implementations follow: https://www.pbrt.org/chapters/pbrt-2ed-chap4.pdf +#define USE_OIBVH 1 + +// Expands a 10-bit integer into 30 bits by inserting 2 zeros after each bit. +extern unsigned int expandBits(unsigned int v); + +// Calculates a 30-bit Morton code for the given 3D point located within the unit cube [0,1]. +extern unsigned int morton3D(float x, float y, float z); + +#if defined(USE_OIBVH) + +// TODO: just use std::pair +typedef struct +{ + int m_left; // node-A ID (implicit index) + int m_right; // node-B ID (implicit index) +} node_pair_t; // collision tree node + +// count leading zeros in 32 bit bitfield +extern unsigned int clz(unsigned int x); + +// next power of two from x +extern int next_power_of_two(int x); + +// check if "x" is a power of two +extern bool is_power_of_two(int x); + +// compute log-base-2 of "x" +extern int ilog2(unsigned int x); + +// compute index (0...N-1) of the leaf level from the number of leaves +extern int get_leaf_level_from_real_leaf_count(const int t); + +// compute tree-level index from implicit index of a node +extern int get_level_from_implicit_idx(const int bvhNodeImplicitIndex); + +// compute previous power of two +extern unsigned int flp2(unsigned int x); + +// compute size of of Oi-BVH give number of triangles +extern int get_ostensibly_implicit_bvh_size(const int t); + +// compute left-most node on a given level +extern int get_level_leftmost_node(const int node_level); + +// compute right-most leaf node in tree +extern int get_rightmost_real_leaf(const int bvhLeafLevelIndex, const int num_real_leaf_nodes_in_bvh); + +// check if node is a "real node" +extern bool is_real_implicit_tree_node_id(const int bvhNodeImplicitIndex, const int num_real_leaf_nodes_in_bvh); + +// get the right most real node on a given tree level +extern int get_level_rightmost_real_node( + const int rightmostRealLeafNodeImplicitIndex, + const int bvhLeafLevelIndex, + const int ancestorLevelIndex); + +// compute implicit index of a node's ancestor +extern int get_node_ancestor( + const int nodeImplicitIndex, + const int nodeLevelIndex, + const int ancestorLevelIndex); + +// calculate linear memory index of a real node +extern int get_node_mem_index( + const int nodeImplicitIndex, + const int leftmostImplicitIndexOnNodeLevel, + const int bvh_data_base_offset, + const int rightmostRealNodeImplicitIndexOnNodeLevel); + +extern void build_oibvh( +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + thread_pool& pool, +#endif + const hmesh_t& mesh, + std::vector>& bvhAABBs, + std::vector& bvhLeafNodeFaces, + std::vector>& face_bboxes, + const double& slightEnlargmentEps = double(0.0)); + +extern void intersectOIBVHs( + std::map>& ps_face_to_potentially_intersecting_others, + const std::vector>& srcMeshBvhAABBs, + const std::vector& srcMeshBvhLeafNodeFaces, + const std::vector>& cutMeshBvhAABBs, + const std::vector& cutMeshBvhLeafNodeFaces); +#else +typedef bounding_box_t BBox; +static inline BBox Union(const BBox& a, const BBox& b) +{ + BBox out = a; + out.expand(b); + return out; +} + +static inline BBox Union(const BBox& a, const vec3& b) +{ + BBox out = a; + out.expand(b); + return out; +} + +enum class SplitMethod { + SPLIT_SAH = 0, + SPLIT_MIDDLE = 1, + SPLIT_EQUAL_COUNTS = 2, +}; + +// For each primitive to be stored in the BVH, we store the centroid +// of its bounding box, its complete bounding box, and its index in +// the primitives array +struct BVHPrimitiveInfo { + BVHPrimitiveInfo(int pn, const BBox& b) + : primitiveNumber(pn) + , bounds(b) + { + centroid = b.minimum() * (0.5) + b.maximum() * (0.5); + } + + int primitiveNumber; + vec3 centroid; + bounding_box_t bounds; +}; + +// Each BVHBuildNode represents a node of the BVH. All nodes store a BBox, which stores +// the bounds of all of the children beneath the node. Each interior node stores pointers to +// its two children in children. Interior nodes also record the coordinate axis along which +// primitives were sorted for distribution to their two children; this information is used to +// improve the performance of the traversal algorithm. Leaf nodes need to record which +// primitive or primitives are stored in them; the elements of the BVHAccel::primitives +// array from the offset firstPrimOffset up to but not including firstPrimOffset + +// nPrimitives are the primitives in the leaf. (Hence the need for reordering the primi- +// tives array, so that this representation can be used, rather than, for example, storing a +// variable-sized array of primitive indices at each leaf node.) +struct BVHBuildNode { + // The BVHBuildNode constructor only initializes the children pointers; we’ll distinguish + // between leaf and interior nodes by whether their children pointers are NULL or not, + // respectively + BVHBuildNode() + { + children[0] = children[1] = NULL; + } + + void InitLeaf(uint32_t first, uint32_t n, const BBox& b) + { + firstPrimOffset = first; + nPrimitives = n; + bounds = b; + } + + // The InitInterior() method requires that the two children nodes already have been cre- + // ated, so that their pointers can be passed in. This requirement makes it easy to compute + // the bounds of the interior node, since the children bounds are immediately available. + void InitInterior(uint32_t axis, std::shared_ptr& c0, std::shared_ptr& c1) + { + children[0] = c0; + children[1] = c1; + bounds = Union(c0->bounds, c1->bounds); + splitAxis = axis; + nPrimitives = 0; + } + + bounding_box_t bounds; + std::shared_ptr children[2]; + uint32_t splitAxis, firstPrimOffset, nPrimitives; +}; + +struct CompareToMid { + CompareToMid(int d, double m) + { + dim = d; + mid = m; + } + int dim; + float mid; + bool operator()(const BVHPrimitiveInfo& a) const + { + return a.centroid[dim] < mid; + } +}; + +struct ComparePoints { + ComparePoints(int d) { dim = d; } + int dim; + bool operator()(const BVHPrimitiveInfo& a, + const BVHPrimitiveInfo& b) const + { + return a.centroid[dim] < b.centroid[dim]; + } +}; + +struct BucketInfo { + BucketInfo() { count = 0; } + int count; + BBox bounds; +}; + +struct CompareToBucket { + CompareToBucket(int split, int num, int d, const BBox& b) + : centroidBounds(b) + { + splitBucket = split; + nBuckets = num; + dim = d; + } + + // bool operator()(const BVHPrimitiveInfo &p) const; + bool operator()(const BVHPrimitiveInfo& p) const + { + int b = nBuckets * ((p.centroid[dim] - centroidBounds.minimum()[dim]) / (centroidBounds.maximum()[dim] - centroidBounds.minimum()[dim])); + if (b == nBuckets) + b = nBuckets - 1; + return b <= splitBucket; + } + + int splitBucket, nBuckets, dim; + const BBox& centroidBounds; +}; + +// The LinearBVHNode structure stores the information needed to traverse the BVH. In +// addition to the bounding box for each node, for leaf nodes it stores the offset and +// primitive count for the primitives in the node. For interior nodes, it stores the offset to +// the second child as well as which of the coordinate axes the primitives were partitioned +// along when the hierarchy was built; this information is used in the traversal routine below +// to try to visit nodes in front-to-back order along the ray. +struct LinearBVHNode { + BBox bounds; + union { + uint32_t primitivesOffset; // leaf + uint32_t secondChildOffset; // interior + }; + uint8_t nPrimitives; // 0 -> interior node + uint8_t axis; // interior node: xyz + uint8_t pad[2]; // ensure 32 byte total size +}; + +class BoundingVolumeHierarchy { + +public: + BoundingVolumeHierarchy(); + + ~BoundingVolumeHierarchy(); + + // three stages to BVH construction + void buildTree(const hmesh_t& mesh_, + const fixed_precision_number_t& enlargementEps_ = fixed_precision_number_t(0.0), + uint32_t mp_ = 1, + const SplitMethod& sm_ = SplitMethod::SPLIT_MIDDLE); + + const BBox& GetPrimitiveBBox(int primitiveIndex) const; + + uint32_t flattenBVHTree(std::shared_ptr node, uint32_t* offset); + + // The initial call to recursiveBuild() is given all of the primitives to be stored in + // the tree. It returns a pointer to the root of the tree, which is represented with + // the BVHBuildNode structure. + + // responsible for returning a BVH for the subset of primitives represented by the range from + // buildData[start] up to and including buildData[end-1] + std::shared_ptr recursiveBuild( + std::vector& buildData, + uint32_t start, + uint32_t end, + uint32_t* totalNodes, + std::vector& orderedPrims); + + int GetNodeCount() const; + + const std::shared_ptr& GetNode(int idx) const; + + const fd_t& GetPrimitive(int index) const; + + static void intersectBVHTrees( +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + thread_pool& scheduler, +#endif + std::map>& symmetric_intersecting_pairs, + const BoundingVolumeHierarchy& bvhA, + const BoundingVolumeHierarchy& bvhB, + const uint32_t primitiveOffsetA, + const uint32_t primitiveOffsetB); + +private: + const hmesh_t* mesh; + int maxPrimsInNode; + SplitMethod splitMethod; + fixed_precision_number_t enlargementEps; // used to slight enlarge BVH (with bounds of max cut-mesh perturbation magnitude) + std::vector buildData; + std::vector primitives; // ordered primitives + std::vector primitiveOrderedBBoxes; // unsorted elements correspond to mesh indices + std::vector> nodes; +}; +#endif // #if defined(USE_OIBVH) + +#endif // MCUT_BVH_H_ diff --git a/src/mcut/include/mcut/internal/cdt/LICENSE.txt b/src/mcut/include/mcut/internal/cdt/LICENSE.txt new file mode 100644 index 0000000000..14e2f777f6 --- /dev/null +++ b/src/mcut/include/mcut/internal/cdt/LICENSE.txt @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/src/mcut/include/mcut/internal/cdt/cdt.h b/src/mcut/include/mcut/internal/cdt/cdt.h new file mode 100644 index 0000000000..cdc96095e4 --- /dev/null +++ b/src/mcut/include/mcut/internal/cdt/cdt.h @@ -0,0 +1,555 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef _CONSTRAINED_DELAUNAY_TRIANGULATION_H_ +#define _CONSTRAINED_DELAUNAY_TRIANGULATION_H_ + +#include "mcut/internal/cdt/triangulate.h" +#include "mcut/internal/cdt/utils.h" + +#include +#include +#include +#include +#include +#include +#include + +/// Namespace containing triangulation functionality +namespace cdt { + +/** @defgroup API Public API + * Contains API for constrained and conforming Delaunay triangulations + */ +/// @{ + +/** + * Type used for storing layer depths for triangles + * @note layer_depth_t should support 60K+ layers, which could be to much or + * too little for some use cases. Feel free to re-define this typedef. + */ +typedef unsigned short layer_depth_t; +typedef layer_depth_t boundary_overlap_count_t; + +/** @defgroup helpers Helpers + * Helpers for working with cdt::triangulator_t. + */ +/// @{ + +/** + * Calculate triangles adjacent to vertices (triangles by vertex index) + * @param triangles triangulation + * @param verticesSize total number of vertices to pre-allocate the output + * @return triangles by vertex index (array of size V, where V is the number of vertices; each element is a list of triangles incident to corresponding vertex). + */ +inline std::vector> get_vertex_to_triangles_map( + const std::vector& triangles, + const std::uint32_t verticesSize) +{ + std::vector> map(verticesSize); + + for (std::uint32_t i = 0; i < (std::uint32_t)triangles.size(); ++i) { + + const std::uint32_t triangle_index = i; + const triangle_t& triangle = triangles[triangle_index]; + const std::array& vertices = triangle.vertices; + + for (std::array::const_iterator j = vertices.begin(); j != vertices.end(); ++j) { + const std::uint32_t vertex_index = *j; + map[vertex_index].push_back(i); + } + } + + return map; +} + +/** + * Information about removed duplicated vertices. + * + * Contains mapping information and removed duplicates indices. + * @note vertices {0,1,2,3,4} where 0 and 3 are the same will produce mapping + * {0,1,2,0,3} (to new vertices {0,1,2,3}) and duplicates {3} + */ +struct duplicates_info_t { + std::vector mapping; ///< vertex index mapping + std::vector duplicates; ///< duplicates' indices +}; + +/*! + * Remove elements in the range [first; last) with indices from the sorted + * unique range [ii_first, ii_last) + */ +template +inline ForwardIt remove_at( + ForwardIt first, + ForwardIt last, + SortUniqIndsFwdIt ii_first, + SortUniqIndsFwdIt ii_last) +{ + if (ii_first == ii_last) // no indices-to-remove are given + return last; + + typedef typename std::iterator_traits::difference_type diff_t; + typedef typename std::iterator_traits::value_type ind_t; + + ForwardIt destination = first + static_cast(*ii_first); + + while (ii_first != ii_last) { + + // advance to an index after a chunk of elements-to-keep + for (ind_t cur = *ii_first++; ii_first != ii_last; ++ii_first) { + const ind_t nxt = *ii_first; + if (nxt - cur > 1) + break; + cur = nxt; + } + + // move the chunk of elements-to-keep to new destination + const ForwardIt source_first = first + static_cast(*(ii_first - 1)) + 1; + const ForwardIt source_last = ii_first != ii_last ? first + static_cast(*ii_first) : last; + + std::move(source_first, source_last, destination); + + destination += source_last - source_first; + } + return destination; +} + +/** + * Find duplicates in given custom point-type range + * @note duplicates are points with exactly same X and Y coordinates + * @tparam TVertexIter iterator that dereferences to custom point type + * @tparam TGetVertexCoordX function object getting x coordinate from vertex. + * Getter signature: const TVertexIter::value_type& -> T + * @tparam TGetVertexCoordY function object getting y coordinate from vertex. + * Getter signature: const TVertexIter::value_type& -> T + * @param first beginning of the range of vertices + * @param last end of the range of vertices + * @param get_x_coord getter of X-coordinate + * @param get_y_coord getter of Y-coordinate + * @returns information about vertex duplicates + */ +template < + typename T, + typename TVertexIter, + typename TGetVertexCoordX, + typename TGetVertexCoordY> +duplicates_info_t find_duplicates( + TVertexIter first, + TVertexIter last, + TGetVertexCoordX get_x_coord, + TGetVertexCoordY get_y_coord) +{ + std::unordered_map, std::size_t> uniqueVerts; // position to index map + const std::size_t verticesSize = std::distance(first, last); + + duplicates_info_t di = { + std::vector(verticesSize), + std::vector() + }; + + for (std::size_t iIn = 0, iOut = iIn; iIn < verticesSize; ++iIn, ++first) { + typename std::unordered_map, std::size_t>::const_iterator it; + bool isUnique; + + // check if the coordinates match [exactly] (in the bitwise sense) + std::tie(it, isUnique) = uniqueVerts.insert( + std::make_pair( + vec2_::make(get_x_coord(*first), get_y_coord(*first)), + iOut)); + + if (isUnique) { + di.mapping[iIn] = iOut++; + continue; + } + + di.mapping[iIn] = it->second; // found a duplicate + di.duplicates.push_back(iIn); + } + + return di; +} + +/** + * Remove duplicates in-place from vector of custom points + * @tparam TVertex vertex type + * @tparam TAllocator allocator used by input vector of vertices + * @param vertices vertices to remove duplicates from + * @param duplicates information about duplicates + */ +template +void remove_duplicates( + std::vector& vertices, + const std::vector& duplicates) +{ + vertices.erase( + remove_at( + vertices.begin(), + vertices.end(), + duplicates.begin(), + duplicates.end()), + vertices.end()); +} + +/** + * Remove duplicated points in-place + * + * @tparam T type of vertex coordinates (e.g., float, double) + * @param[in, out] vertices collection of vertices to remove duplicates from + * @returns information about duplicated vertices that were removed. + */ +template +duplicates_info_t remove_duplicates(std::vector>& vertices) +{ + const duplicates_info_t di = find_duplicates( + vertices.begin(), + vertices.end(), + get_x_coord_vec2d, + get_y_coord_vec2d); + + remove_duplicates(vertices, di.duplicates); + + return di; +} + +/** + * Remap vertex indices in edges (in-place) using given vertex-index mapping. + * @tparam TEdgeIter iterator that dereferences to custom edge type + * @tparam TGetEdgeVertexStart function object getting start vertex index + * from an edge. + * Getter signature: const TEdgeIter::value_type& -> std::uint32_t + * @tparam TGetEdgeVertexEnd function object getting end vertex index from + * an edge. Getter signature: const TEdgeIter::value_type& -> std::uint32_t + * @tparam TMakeEdgeFromStartAndEnd function object that makes new edge from + * start and end vertices + * @param first beginning of the range of edges + * @param last end of the range of edges + * @param mapping vertex-index mapping + * @param getStart getter of edge start vertex index + * @param getEnd getter of edge end vertex index + * @param makeEdge factory for making edge from vetices + */ + +template < + typename TEdgeIter, + typename TGetEdgeVertexStart, + typename TGetEdgeVertexEnd, + typename TMakeEdgeFromStartAndEnd> +void remap_edges( + TEdgeIter first, + const TEdgeIter last, + const std::vector& mapping, + TGetEdgeVertexStart getStart, + TGetEdgeVertexEnd getEnd, + TMakeEdgeFromStartAndEnd makeEdge) +{ + for (; first != last; ++first) { + *first = makeEdge( + static_cast(mapping[getStart(*first)]), + static_cast(mapping[getEnd(*first)])); + } +} + +/** + * Remap vertex indices in edges (in-place) using given vertex-index mapping. + * + * @note Mapping can be a result of remove_duplicates function + * @param[in,out] edges collection of edges to remap + * @param mapping vertex-index mapping + */ +inline void +remap_edges(std::vector& edges, const std::vector& mapping) +{ + remap_edges( + edges.begin(), + edges.end(), + mapping, + edge_get_v1, + edge_get_v2, + edge_make); +} + +/** + * Find point duplicates, remove them from vector (in-place) and remap edges + * (in-place) + * @note Same as a chained call of cdt::find_duplicates, cdt::remove_duplicates, + * and cdt::remap_edges + * @tparam T type of vertex coordinates (e.g., float, double) + * @tparam TVertex type of vertex + * @tparam TGetVertexCoordX function object getting x coordinate from vertex. + * Getter signature: const TVertexIter::value_type& -> T + * @tparam TGetVertexCoordY function object getting y coordinate from vertex. + * Getter signature: const TVertexIter::value_type& -> T + * @tparam TEdgeIter iterator that dereferences to custom edge type + * @tparam TGetEdgeVertexStart function object getting start vertex index + * from an edge. + * Getter signature: const TEdgeIter::value_type& -> std::uint32_t + * @tparam TGetEdgeVertexEnd function object getting end vertex index from + * an edge. Getter signature: const TEdgeIter::value_type& -> std::uint32_t + * @tparam TMakeEdgeFromStartAndEnd function object that makes new edge from + * start and end vertices + * @param[in, out] vertices vertices to remove duplicates from + * @param[in, out] edges collection of edges connecting vertices + * @param get_x_coord getter of X-coordinate + * @param get_y_coord getter of Y-coordinate + * @param edgesFirst beginning of the range of edges + * @param edgesLast end of the range of edges + * @param getStart getter of edge start vertex index + * @param getEnd getter of edge end vertex index + * @param makeEdge factory for making edge from vetices + * @returns information about vertex duplicates + */ + +template < + typename T, + typename TVertex, + typename TGetVertexCoordX, + typename TGetVertexCoordY, + typename TVertexAllocator, + typename TEdgeIter, + typename TGetEdgeVertexStart, + typename TGetEdgeVertexEnd, + typename TMakeEdgeFromStartAndEnd> +duplicates_info_t remove_duplicates_and_remap_edges( + std::vector& vertices, + TGetVertexCoordX get_x_coord, + TGetVertexCoordY get_y_coord, + const TEdgeIter edgesFirst, + const TEdgeIter edgesLast, + TGetEdgeVertexStart getStart, + TGetEdgeVertexEnd getEnd, + TMakeEdgeFromStartAndEnd makeEdge) +{ + const duplicates_info_t di = find_duplicates(vertices.begin(), vertices.end(), get_x_coord, get_y_coord); + + remove_duplicates(vertices, di.duplicates); + remap_edges(edgesFirst, edgesLast, di.mapping, getStart, getEnd, makeEdge); + + return di; +} + +/** + * Same as a chained call of cdt::remove_duplicates + cdt::remap_edges + * + * @tparam T type of vertex coordinates (e.g., float, double) + * @param[in, out] vertices collection of vertices to remove duplicates from + * @param[in,out] edges collection of edges to remap + */ +template +duplicates_info_t remove_duplicates_and_remap_edges( + std::vector>& vertices, + std::vector& edges) +{ + return remove_duplicates_and_remap_edges( + vertices, + get_x_coord_vec2d, + get_y_coord_vec2d, + edges.begin(), + edges.end(), + edge_get_v1, + edge_get_v2, + edge_make); +} + +/** + * Extract all edges of triangles + * + * @param triangles triangles used to extract edges + * @return an unordered set of all edges of triangulation + */ +inline std::unordered_set +extract_edges_from_triangles(const std::vector& triangles) +{ + std::unordered_set edges; + + for (std::vector::const_iterator t = triangles.begin(); t != triangles.end(); ++t) { + edges.insert(edge_t(std::uint32_t(t->vertices[0]), std::uint32_t(t->vertices[1]))); + edges.insert(edge_t(std::uint32_t(t->vertices[1]), std::uint32_t(t->vertices[2]))); + edges.insert(edge_t(std::uint32_t(t->vertices[2]), std::uint32_t(t->vertices[0]))); + } + return edges; +} + +/*! + * Converts piece->original_edges mapping to original_edge->pieces + * @param pieceToOriginals maps pieces to original edges + * @return mapping of original edges to pieces + */ +inline std::unordered_map> +edge_to_pieces_mapping(const std::unordered_map>& pieceToOriginals) +{ + std::unordered_map> originalToPieces; + typedef std::unordered_map>::const_iterator Cit; + for (Cit ptoIt = pieceToOriginals.begin(); ptoIt != pieceToOriginals.end(); + ++ptoIt) { + const edge_t piece = ptoIt->first; + const std::vector& originals = ptoIt->second; + for (std::vector::const_iterator origIt = originals.begin(); + origIt != originals.end(); + ++origIt) { + originalToPieces[*origIt].push_back(piece); + } + } + return originalToPieces; +} + +/*! + * Convert edge-to-pieces mapping into edge-to-split-vertices mapping + * @tparam T type of vertex coordinates (e.g., float, double) + * @param edgeToPieces edge-to-pieces mapping + * @param vertices vertex buffer + * @return mapping of edge-to-split-points. + * Split points are sorted from edge's start (v1) to end (v2) + */ + +template +std::unordered_map> get_edge_to_split_vertices_map( + const std::unordered_map>& edgeToPieces, + const std::vector>& vertices) +{ + typedef std::pair VertCoordPair; + + struct ComparePred { + bool operator()(const VertCoordPair& a, const VertCoordPair& b) const + { + return a.second < b.second; + } + } comparePred; + + std::unordered_map> edgeToSplitVerts; + + for (std::unordered_map>::const_iterator it = edgeToPieces.begin(); + it != edgeToPieces.end(); + ++it) { + + const edge_t& e = it->first; + const T dX = vertices[e.v2()].x() - vertices[e.v1()].x(); + const T dY = vertices[e.v2()].y() - vertices[e.v1()].y(); + const bool isX = std::abs(dX) >= std::abs(dY); // X-coord longer + const bool isAscending = isX ? dX >= 0 : dY >= 0; // Longer coordinate ascends + const std::vector& pieces = it->second; + + std::vector splitVerts; + // size is: 2[ends] + (pieces - 1)[split vertices] = pieces + 1 + splitVerts.reserve(pieces.size() + 1); + + for (std::vector::const_iterator it = pieces.begin(); it != pieces.end(); ++it) { + + const std::array vv = { it->v1(), it->v2() }; + + for (std::array::const_iterator v = vv.begin(); v != vv.end(); ++v) { + const T c = isX ? vertices[*v].x() : vertices[*v].y(); + splitVerts.push_back(std::make_pair(*v, isAscending ? c : -c)); + } + } + + // sort by longest coordinate + std::sort(splitVerts.begin(), splitVerts.end(), comparePred); + + // remove duplicates + splitVerts.erase( + std::unique(splitVerts.begin(), splitVerts.end()), + splitVerts.end()); + + MCUT_ASSERT(splitVerts.size() > 2); // 2 end points with split vertices + + std::pair> val = std::make_pair(e, std::vector()); + + val.second.reserve(splitVerts.size()); + + for (typename std::vector::const_iterator it = splitVerts.begin() + 1; + it != splitVerts.end() - 1; + ++it) { + val.second.push_back(it->first); + } + + edgeToSplitVerts.insert(val); + } + return edgeToSplitVerts; +} + +/// @} + +/// @} + +} // namespace cdt + +//***************************************************************************** +// Implementations of template functionlity +//***************************************************************************** +// hash for vec2_ +namespace std { + +template +struct hash> { + size_t operator()(const vec2_& xy) const + { + return std::hash()(xy.x()) ^ std::hash()(xy.y()); + } +}; + +} // namespace std + +namespace cdt { + +//----- +// API +//----- + +/** + * Verify that triangulation topology is consistent. + * + * Checks: + * - for each vertex adjacent triangles contain the vertex + * - each triangle's neighbor in turn has triangle as its neighbor + * - each of triangle's vertices has triangle as adjacent + * + * @tparam T type of vertex coordinates (e.g., float, double) + * @tparam TNearPointLocator class providing locating near point for efficiently + */ +template +inline bool check_topology(const cdt::triangulator_t& cdt) +{ + // Check if vertices' adjacent triangles contain vertex + const std::vector> vertTris = cdt.is_finalized() + ? get_vertex_to_triangles_map( + cdt.triangles, static_cast(cdt.vertices.size())) + : cdt.vertTris; + for (std::uint32_t iV(0); iV < std::uint32_t(cdt.vertices.size()); ++iV) { + const std::vector& vTris = vertTris[iV]; + typedef std::vector::const_iterator TriIndCit; + for (TriIndCit it = vTris.begin(); it != vTris.end(); ++it) { + const std::array& vv = cdt.triangles[*it].vertices; + if (std::find(vv.begin(), vv.end(), iV) == vv.end()) + return false; + } + } + // Check if triangle neighbor links are fine + for (std::uint32_t iT(0); iT < std::uint32_t(cdt.triangles.size()); ++iT) { + const triangle_t& t = cdt.triangles[iT]; + typedef std::array::const_iterator NCit; + for (NCit it = t.neighbors.begin(); it != t.neighbors.end(); ++it) { + if (*it == null_neighbour) + continue; + const std::array& nn = cdt.triangles[*it].neighbors; + if (std::find(nn.begin(), nn.end(), iT) == nn.end()) + return false; + } + } + // Check if triangle's vertices have triangle as adjacent + for (std::uint32_t iT(0); iT < std::uint32_t(cdt.triangles.size()); ++iT) { + const triangle_t& t = cdt.triangles[iT]; + typedef std::array::const_iterator VCit; + for (VCit it = t.vertices.begin(); it != t.vertices.end(); ++it) { + const std::vector& tt = vertTris[*it]; + if (std::find(tt.begin(), tt.end(), iT) == tt.end()) + return false; + } + } + return true; +} + +} // namespace cdt + +#endif // #ifndef _CONSTRAINED_DELAUNAY_TRIANGULATION_H_ diff --git a/src/mcut/include/mcut/internal/cdt/kdtree.h b/src/mcut/include/mcut/internal/cdt/kdtree.h new file mode 100644 index 0000000000..0517fa777a --- /dev/null +++ b/src/mcut/include/mcut/internal/cdt/kdtree.h @@ -0,0 +1,421 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef KDTREE_KDTREE_H +#define KDTREE_KDTREE_H + +#include "mcut/internal/cdt/utils.h" +#include "mcut/internal/math.h" + +#include +#include + +namespace kdt { + +struct NodeSplitDirection { + enum Enum { + X, + Y, + }; +}; + +/// Simple tree structure with alternating half splitting nodes +/// @details Simple tree structure +/// - Tree to incrementally add points to the structure. +/// - Get the nearest point to a given input. +/// - Does not check for duplicates, expect unique points. +/// @tparam TCoordType type used for storing point coordinate. +/// @tparam NumVerticesInLeaf The number of points per leaf. +/// @tparam InitialStackDepth initial size of stack depth for nearest query. +/// Should be at least 1. +/// @tparam StackDepthIncrement increment of stack depth for nearest query when +/// stack depth is reached. +template < + typename TCoordType, + size_t NumVerticesInLeaf, + size_t InitialStackDepth, + size_t StackDepthIncrement> +class kdtree_t_ { +public: + typedef TCoordType coord_type; + typedef vec2_ point_type; + typedef std::pair value_type; + typedef std::vector point_data_vec; + typedef point_data_vec::const_iterator pd_cit; + typedef std::array children_type; + + /// Stores kd-tree node data + struct Node { + children_type children; ///< two children if not leaf; {0,0} if leaf + point_data_vec data; ///< points' data if leaf + /// Create empty leaf + Node() + { + setChildren(0, 0); + data.reserve(NumVerticesInLeaf); + } + /// Children setter for convenience + void setChildren(const std::uint32_t c1, const std::uint32_t c2) + { + children[0] = c1; + children[1] = c2; + } + /// Check if node is a leaf (has no valid children) + bool isLeaf() const + { + return children[0] == children[1]; + } + }; + + /// Default constructor + kdtree_t_() + : m_rootDir(NodeSplitDirection::X) + , m_min(point_type::make( + -std::numeric_limits::max(), + -std::numeric_limits::max())) + , m_max(point_type::make( + std::numeric_limits::max(), + std::numeric_limits::max())) + , m_isRootBoxInitialized(false) + , m_tasksStack(InitialStackDepth, NearestTask()) + { + m_root = addNewNode(); + } + + /// Constructor with bounding box known in advance + kdtree_t_(const point_type& min, const point_type& max) + : m_rootDir(NodeSplitDirection::X) + , m_min(min) + , m_max(max) + , m_isRootBoxInitialized(true) + , m_tasksStack(InitialStackDepth, NearestTask()) + { + m_root = addNewNode(); + } + + /// Insert a point into kd-tree + /// @note external point-buffer is used to reduce kd-tree's memory footprint + /// @param iPoint index of point in external point-buffer + /// @param points external point-buffer + void + insert(const std::uint32_t& iPoint, const std::vector& points) + { + // if point is outside root, extend tree by adding new roots + const point_type& pos = points[iPoint]; + while (!isInsideBox(pos, m_min, m_max)) { + extendTree(pos); + } + // now insert the point into the tree + std::uint32_t node = m_root; + point_type min = m_min; + point_type max = m_max; + NodeSplitDirection::Enum dir = m_rootDir; + + // below: initialized only to suppress warnings + NodeSplitDirection::Enum newDir(NodeSplitDirection::X); + coord_type mid(0); + point_type newMin, newMax; + while (true) { + if (m_nodes[node].isLeaf()) { + // add point if capacity is not reached + point_data_vec& pd = m_nodes[node].data; + if (pd.size() < NumVerticesInLeaf) { + pd.push_back(iPoint); + return; + } + // initialize bbox first time the root capacity is reached + if (!m_isRootBoxInitialized) { + initializeRootBox(points); + min = m_min; + max = m_max; + } + // split a full leaf node + calcSplitInfo(min, max, dir, mid, newDir, newMin, newMax); + const std::uint32_t c1 = addNewNode(), c2 = addNewNode(); + Node& n = m_nodes[node]; + n.setChildren(c1, c2); + point_data_vec& c1data = m_nodes[c1].data; + point_data_vec& c2data = m_nodes[c2].data; + // move node's points to children + for (pd_cit it = n.data.begin(); it != n.data.end(); ++it) { + whichChild(points[*it], mid, dir) == 0 + ? c1data.push_back(*it) + : c2data.push_back(*it); + } + n.data = point_data_vec(); + } else { + calcSplitInfo(min, max, dir, mid, newDir, newMin, newMax); + } + // add the point to a child + const std::size_t iChild = whichChild(points[iPoint], mid, dir); + iChild == 0 ? max = newMax : min = newMin; + node = m_nodes[node].children[iChild]; + dir = newDir; + } + } + + /// Query kd-tree for a nearest neighbor point + /// @note external point-buffer is used to reduce kd-tree's memory footprint + /// @param point query point position + /// @param points external point-buffer + value_type nearest( + const point_type& point, + const std::vector& points) const + { + value_type out; + int iTask = -1; + coord_type minDistSq = std::numeric_limits::max(); + m_tasksStack[++iTask] = NearestTask(m_root, m_min, m_max, m_rootDir, minDistSq); + while (iTask != -1) { + const NearestTask t = m_tasksStack[iTask--]; + if (t.distSq > minDistSq) + continue; + const Node& n = m_nodes[t.node]; + if (n.isLeaf()) { + for (pd_cit it = n.data.begin(); it != n.data.end(); ++it) { + const point_type& p = points[*it]; + const coord_type distSq = cdt::get_square_distance(point, p); + if (distSq < minDistSq) { + minDistSq = distSq; + out.first = p; + out.second = *it; + } + } + } else { + coord_type mid(0); + NodeSplitDirection::Enum newDir; + point_type newMin, newMax; + calcSplitInfo(t.min, t.max, t.dir, mid, newDir, newMin, newMax); + + const coord_type distToMid = t.dir == NodeSplitDirection::X + ? (point.x() - mid) + : (point.y() - mid); + const coord_type toMidSq = distToMid * distToMid; + + const std::size_t iChild = whichChild(point, mid, t.dir); + if (iTask + 2 >= static_cast(m_tasksStack.size())) { + m_tasksStack.resize( + m_tasksStack.size() + StackDepthIncrement); + } + // node containing point should end up on top of the stack + if (iChild == 0) { + m_tasksStack[++iTask] = NearestTask( + n.children[1], newMin, t.max, newDir, toMidSq); + m_tasksStack[++iTask] = NearestTask( + n.children[0], t.min, newMax, newDir, toMidSq); + } else { + m_tasksStack[++iTask] = NearestTask( + n.children[0], t.min, newMax, newDir, toMidSq); + m_tasksStack[++iTask] = NearestTask( + n.children[1], newMin, t.max, newDir, toMidSq); + } + } + } + return out; + } + +private: + /// Add a new node and return it's index in nodes buffer + std::uint32_t addNewNode() + { + const std::uint32_t newNodeIndex = static_cast(m_nodes.size()); + m_nodes.push_back(Node()); + return newNodeIndex; + } + + /// Test which child point belongs to after the split + /// @returns 0 if first child, 1 if second child + std::size_t whichChild( + const point_type& point, + const coord_type& split, + const NodeSplitDirection::Enum dir) const + { + return static_cast( + dir == NodeSplitDirection::X ? point.x() > split : point.y() > split); + } + + /// Calculate split location, direction, and children boxes + static void calcSplitInfo( + const point_type& min, + const point_type& max, + const NodeSplitDirection::Enum dir, + coord_type& midOut, + NodeSplitDirection::Enum& newDirOut, + point_type& newMinOut, + point_type& newMaxOut) + { + newMaxOut = max; + newMinOut = min; + switch (dir) { + case NodeSplitDirection::X: + midOut = (min.x() + max.x()) / coord_type(2); + newDirOut = NodeSplitDirection::Y; + newMinOut.x() = midOut; + newMaxOut.x() = midOut; + return; + case NodeSplitDirection::Y: + midOut = (min.y() + max.y()) / coord_type(2); + newDirOut = NodeSplitDirection::X; + newMinOut.y() = midOut; + newMaxOut.y() = midOut; + return; + } + } + + /// Test if point is inside a box + static bool isInsideBox( + const point_type& p, + const point_type& min, + const point_type& max) + { + return p.x() >= min.x() && p.x() <= max.x() && p.y() >= min.y() && p.y() <= max.y(); + } + + /// Extend a tree by creating new root with old root and a new node as + /// children + void extendTree(const point_type& point) + { + const std::uint32_t newRoot = addNewNode(); + const std::uint32_t newLeaf = addNewNode(); + switch (m_rootDir) { + case NodeSplitDirection::X: + m_rootDir = NodeSplitDirection::Y; + point.y() < m_min.y() ? m_nodes[newRoot].setChildren(newLeaf, m_root) + : m_nodes[newRoot].setChildren(m_root, newLeaf); + if (point.y() < m_min.y()) + m_min.y() -= m_max.y() - m_min.y(); + else if (point.y() > m_max.y()) + m_max.y() += m_max.y() - m_min.y(); + break; + case NodeSplitDirection::Y: + m_rootDir = NodeSplitDirection::X; + point.x() < m_min.x() ? m_nodes[newRoot].setChildren(newLeaf, m_root) + : m_nodes[newRoot].setChildren(m_root, newLeaf); + if (point.x() < m_min.x()) + m_min.x() -= m_max.x() - m_min.x(); + else if (point.x() > m_max.x()) + m_max.x() += m_max.x() - m_min.x(); + break; + } + m_root = newRoot; + } + + /// Calculate root's box enclosing all root points + void initializeRootBox(const std::vector& points) + { + const point_data_vec& data = m_nodes[m_root].data; + m_min = points[data.front()]; + m_max = m_min; + for (pd_cit it = data.begin(); it != data.end(); ++it) { + const point_type& p = points[*it]; + m_min = point_type::make( + std::min(m_min.x(), p.x()), std::min(m_min.y(), p.y())); + m_max = point_type::make( + std::max(m_max.x(), p.x()), std::max(m_max.y(), p.y())); + } + // Make sure bounding box does not have a zero size by adding padding: + // zero-size bounding box cannot be extended properly + const TCoordType padding(1); + if (m_min.x() == m_max.x()) { + m_min.x() -= padding; + m_max.x() += padding; + } + if (m_min.y() == m_max.y()) { + m_min.y() -= padding; + m_max.y() += padding; + } + m_isRootBoxInitialized = true; + } + +private: + std::uint32_t m_root; + std::vector m_nodes; + NodeSplitDirection::Enum m_rootDir; + point_type m_min; + point_type m_max; + bool m_isRootBoxInitialized; + + // used for nearest query + struct NearestTask { + std::uint32_t node; + point_type min, max; + NodeSplitDirection::Enum dir; + coord_type distSq; + NearestTask() + { + } + NearestTask( + const std::uint32_t node, + const point_type& min, + const point_type& max, + const NodeSplitDirection::Enum dir, + const coord_type distSq) + : node(node) + , min(min) + , max(max) + , dir(dir) + , distSq(distSq) + { + } + }; + // allocated in class (not in the 'nearest' method) for better performance + mutable std::vector m_tasksStack; +}; + +} // namespace kdt + +namespace cdt { + +/// KD-tree holding points +template < + typename TCoordType, + size_t NumVerticesInLeaf = 32, + size_t InitialStackDepth = 32, + size_t StackDepthIncrement = 32> +class locator_kdtree_t { +public: + /// Initialize KD-tree with points + void initialize(const std::vector>& points) + { + vec2_ min = points.front(); + vec2_ max = min; + + for (typename std::vector>::const_iterator it = points.begin(); + it != points.end(); + ++it) { + + min = vec2_::make(std::min(min.x(), it->x()), std::min(min.y(), it->y())); + max = vec2_::make(std::max(max.x(), it->x()), std::max(max.y(), it->y())); + + } + + m_tree = kdtree_t(min, max); + + for (std::uint32_t i = 0; i < (std::uint32_t)points.size(); ++i) { + m_tree.insert(i, points); + } + } + /// Add point to KD-tree + void add_point(const std::uint32_t i, const std::vector>& points) + { + m_tree.insert(i, points); + } + + /// Find nearest point using R-tree + std::uint32_t nearPoint( + const vec2_& pos, + const std::vector>& points) const + { + return m_tree.nearest(pos, points).second; + } + +private: + typedef kdt::kdtree_t_ kdtree_t; + + kdtree_t m_tree; +}; + +} // namespace cdt + +#endif // header guard diff --git a/src/mcut/include/mcut/internal/cdt/triangulate.h b/src/mcut/include/mcut/internal/cdt/triangulate.h new file mode 100644 index 0000000000..8ca9c0ac90 --- /dev/null +++ b/src/mcut/include/mcut/internal/cdt/triangulate.h @@ -0,0 +1,2120 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef CDT_vW1vZ0lO8rS4gY4uI4fB +#define CDT_vW1vZ0lO8rS4gY4uI4fB + +#include "mcut/internal/cdt/kdtree.h" +#include "mcut/internal/cdt/utils.h" + +#include +#include +#include +#include +#include +#include +#include + +/// Namespace containing triangulation functionality +namespace cdt { + +/// @addtogroup API +/// @{ + +/** + * Enum of strategies specifying order in which a range of vertices is inserted + * @note vertex_insertion_order_t::RANDOM will only randomize order of + * inserting in triangulation, vertex indices will be preserved as they were + * specified in the final triangulation + */ +struct vertex_insertion_order_t { + /** + * The Enum itself + * @note needed to pre c++11 compilers that don't support 'class enum' + */ + enum Enum { + RANDOM, ///< vertices will be inserted in random order + AS_GIVEN, ///< vertices will be inserted in the same order as provided + }; +}; + +/// Enum of what type of geometry used to embed triangulation into +struct super_geometry_type_t { + /** + * The Enum itself + * @note needed to pre c++11 compilers that don't support 'class enum' + */ + enum Enum { + SUPER_TRIANGLE, ///< conventional super-triangle + CUSTOM, ///< user-specified custom geometry (e.g., grid) + }; +}; + +/** + * Enum of strategies for treating intersecting constraint edges + */ +struct action_on_intersecting_constraint_edges_t { + /** + * The Enum itself + * @note needed to pre c++11 compilers that don't support 'class enum' + */ + enum Enum { + IGNORE, ///< constraint edge intersections are not checked + RESOLVE, ///< constraint edge intersections are resolved + }; +}; + +/** + * Type used for storing layer depths for triangles + * @note layer_depth_t should support 60K+ layers, which could be to much or + * too little for some use cases. Feel free to re-define this typedef. + */ +typedef unsigned short layer_depth_t; +typedef layer_depth_t boundary_overlap_count_t; + +namespace detail { + + /// Needed for c++03 compatibility (no uniform initialization available) + template + std::array arr3(const T& v0, const T& v1, const T& v2) + { + const std::array out = { v0, v1, v2 }; + return out; + } + + namespace defaults { + + const std::size_t nTargetVerts = 0; + const super_geometry_type_t::Enum superGeomType = super_geometry_type_t::SUPER_TRIANGLE; + const vertex_insertion_order_t::Enum vertexInsertionOrder = vertex_insertion_order_t::RANDOM; + const action_on_intersecting_constraint_edges_t::Enum intersectingEdgesStrategy = action_on_intersecting_constraint_edges_t::IGNORE; + const float minDistToConstraintEdge(0); + + } // namespace defaults + + // add element to 'to' if not already in 'to' + template + void insert_unique(std::vector& to, const T& elem) + { + if (std::find(to.begin(), to.end(), elem) == to.end()) { + to.push_back(elem); + } + } + + // add elements of 'from' that are not present in 'to' to 'to' + template + void insert_unique( + std::vector& to, + const std::vector& from) + { + typedef typename std::vector::const_iterator Cit; + to.reserve(to.size() + from.size()); + for (Cit cit = from.begin(); cit != from.end(); ++cit) { + insert_unique(to, *cit); + } + } + +} // namespace detail + +/** + * @defgroup triangulator_t triangulator_t Class + * Class performing triangulations. + */ +/// @{ + +/** + * Data structure representing a 2D constrained Delaunay triangulation + * + * @tparam T type of vertex coordinates (e.g., float, double) + * @tparam TNearPointLocator class providing locating near point for efficiently + * inserting new points. Provides methods: 'add_point(vPos, iV)' and + * 'nearPoint(vPos) -> iV' + */ +template > +class triangulator_t { +public: + // typedef std::vector> vec2_vector_t; ///< Vertices vector + std::vector> vertices; ///< triangulation's vertices + std::vector triangles; ///< triangulation's triangles + std::unordered_set fixedEdges; ///< triangulation's constraints (fixed edges) + /** + * triangles adjacent to each vertex + * @note will be reset to empty when super-triangle is removed and + * triangulation is finalized. To re-calculate adjacent triangles use + * cdt::get_vertex_to_triangles_map helper + */ + std::vector> vertTris; + + /** Stores count of overlapping boundaries for a fixed edge. If no entry is + * present for an edge: no boundaries overlap. + * @note map only has entries for fixed for edges that represent overlapping + * boundaries + * @note needed for handling depth calculations and hole-removel in case of + * overlapping boundaries + */ + std::unordered_map overlapCount; + + /** Stores list of original edges represented by a given fixed edge + * @note map only has entries for edges where multiple original fixed edges + * overlap or where a fixed edge is a part of original edge created by + * conforming Delaunay triangulation vertex insertion + */ + std::unordered_map> pieceToOriginals; + + /*____ API _____*/ + /// Default constructor + triangulator_t(); + /** + * Constructor + * @param vertexInsertionOrder strategy used for ordering vertex insertions + */ + triangulator_t(vertex_insertion_order_t::Enum vertexInsertionOrder); + /** + * Constructor + * @param vertexInsertionOrder strategy used for ordering vertex insertions + * @param intersectingEdgesStrategy strategy for treating intersecting + * constraint edges + * @param minDistToConstraintEdge distance within which point is considered + * to be lying on a constraint edge. Used when adding constraints to the + * triangulation. + */ + triangulator_t( + vertex_insertion_order_t::Enum vertexInsertionOrder, + action_on_intersecting_constraint_edges_t::Enum intersectingEdgesStrategy, + T minDistToConstraintEdge); + /** + * Constructor + * @param vertexInsertionOrder strategy used for ordering vertex insertions + * @param nearPtLocator class providing locating near point for efficiently + * inserting new points + * @param intersectingEdgesStrategy strategy for treating intersecting + * constraint edges + * @param minDistToConstraintEdge distance within which point is considered + * to be lying on a constraint edge. Used when adding constraints to the + * triangulation. + */ + triangulator_t( + vertex_insertion_order_t::Enum vertexInsertionOrder, + const TNearPointLocator& nearPtLocator, + action_on_intersecting_constraint_edges_t::Enum intersectingEdgesStrategy, + T minDistToConstraintEdge); + /** + * Insert custom point-types specified by iterator range and X/Y-getters + * @tparam TVertexIter iterator that dereferences to custom point type + * @tparam TGetVertexCoordX function object getting x coordinate from + * vertex. Getter signature: const TVertexIter::value_type& -> T + * @tparam TGetVertexCoordY function object getting y coordinate from + * vertex. Getter signature: const TVertexIter::value_type& -> T + * @param first beginning of the range of vertices to add + * @param last end of the range of vertices to add + * @param get_x_coord getter of X-coordinate + * @param get_y_coord getter of Y-coordinate + */ + template < + typename TVertexIter, + typename TGetVertexCoordX, + typename TGetVertexCoordY> + void insert_vertices( + TVertexIter first, + TVertexIter last, + TGetVertexCoordX get_x_coord, + TGetVertexCoordY get_y_coord); + /** + * Insert vertices into triangulation + * @param vertices vector of vertices to insert + */ + void insert_vertices(const std::vector>& vertices); + /** + * Insert constraints (custom-type fixed edges) into triangulation + * @note Each fixed edge is inserted by deleting the triangles it crosses, + * followed by the triangulation of the polygons on each side of the edge. + * No new vertices are inserted. + * @note If some edge appears more than once in the input this means that + * multiple boundaries overlap at the edge and impacts how hole detection + * algorithm of triangulator_t::erase_outer_triangles_and_holes works. + * Make sure there are no erroneous duplicates. + * @tparam TEdgeIter iterator that dereferences to custom edge type + * @tparam TGetEdgeVertexStart function object getting start vertex index + * from an edge. + * Getter signature: const TEdgeIter::value_type& -> std::uint32_t + * @tparam TGetEdgeVertexEnd function object getting end vertex index from + * an edge. Getter signature: const TEdgeIter::value_type& -> std::uint32_t + * @param first beginning of the range of edges to add + * @param last end of the range of edges to add + * @param getStart getter of edge start vertex index + * @param getEnd getter of edge end vertex index + */ + template < + typename TEdgeIter, + typename TGetEdgeVertexStart, + typename TGetEdgeVertexEnd> + void insert_edges( + TEdgeIter first, + TEdgeIter last, + TGetEdgeVertexStart getStart, + TGetEdgeVertexEnd getEnd); + /** + * Insert constraint edges into triangulation + * @note Each fixed edge is inserted by deleting the triangles it crosses, + * followed by the triangulation of the polygons on each side of the edge. + * No new vertices are inserted. + * @note If some edge appears more than once in the input this means that + * multiple boundaries overlap at the edge and impacts how hole detection + * algorithm of triangulator_t::erase_outer_triangles_and_holes works. + * Make sure there are no erroneous duplicates. + * @tparam edges constraint edges + */ + void insert_edges(const std::vector& edges); + + /*! + * Returns: + * - intersected triangle index + * - index of point on the left of the line + * - index of point on the right of the line + * If left point is right on the line: no triangle is intersected: + * - triangle index is no-neighbor (invalid) + * - index of point on the line + * - index of point on the right of the line + */ + std::tuple + get_intersected_triangle( + const std::uint32_t iA, + const std::vector& candidates, + const vec2_& a, + const vec2_& b, + const T orientationTolerance) const + { + typedef std::vector::const_iterator TriIndCit; + for (TriIndCit it = candidates.begin(); it != candidates.end(); ++it) { + const std::uint32_t iT = *it; + const triangle_t t = triangles[iT]; + const std::uint32_t i = get_vertex_index(t, iA); + const std::uint32_t iP2 = t.vertices[ccw(i)]; + const T orientP2 = orient2d(vertices[iP2], a, b); + const point_to_line_location_t::Enum locP2 = classify_orientation(orientP2); + + if (locP2 == point_to_line_location_t::RIGHT_SIDE) { + const std::uint32_t iP1 = t.vertices[cw(i)]; + const T orientP1 = orient2d(vertices[iP1], a, b); + const point_to_line_location_t::Enum locP1 = classify_orientation(orientP1); + if (locP1 == point_to_line_location_t::COLLINEAR) { + return std::make_tuple(null_neighbour, iP1, iP1); + } + if (locP1 == point_to_line_location_t::LEFT_SIDE) { + if (orientationTolerance) { + T closestOrient; + std::uint32_t iClosestP; + if (std::abs(orientP1) <= std::abs(orientP2)) { + closestOrient = orientP1; + iClosestP = iP1; + } else { + closestOrient = orientP2; + iClosestP = iP2; + } + if (classify_orientation( + closestOrient, orientationTolerance) + == point_to_line_location_t::COLLINEAR) { + return std::make_tuple(null_neighbour, iClosestP, iClosestP); + } + } + return std::make_tuple(iT, iP1, iP2); + } + } + } + throw std::runtime_error("Could not find vertex triangle intersected by " + "edge. Note: can be caused by duplicate points."); + } + + /// Returns indices of four resulting triangles + /* Inserting a point on the edge between two triangles + * T1 (top) v1 + * /|\ + * n1 / | \ n4 + * / | \ + * / T1' | Tnew1\ + * v2-------v-------v4 + * \ Tnew2| T2' / + * \ | / + * n2 \ | / n3 + * \|/ + * T2 (bottom) v3 + */ + std::stack insert_point_on_edge( + const std::uint32_t v, + const std::uint32_t iT1, + const std::uint32_t iT2) + { + const std::uint32_t iTnew1 = add_triangle(); + const std::uint32_t iTnew2 = add_triangle(); + + triangle_t& t1 = triangles[iT1]; + triangle_t& t2 = triangles[iT2]; + std::uint32_t i = get_opposite_vertex_index(t1, iT2); + const std::uint32_t v1 = t1.vertices[i]; + const std::uint32_t v2 = t1.vertices[ccw(i)]; + const std::uint32_t n1 = t1.neighbors[i]; + const std::uint32_t n4 = t1.neighbors[cw(i)]; + i = get_opposite_vertex_index(t2, iT1); + const std::uint32_t v3 = t2.vertices[i]; + const std::uint32_t v4 = t2.vertices[ccw(i)]; + const std::uint32_t n3 = t2.neighbors[i]; + const std::uint32_t n2 = t2.neighbors[cw(i)]; + // add new triangles and change existing ones + using detail::arr3; + t1 = triangle_t::make(arr3(v1, v2, v), arr3(n1, iTnew2, iTnew1)); + t2 = triangle_t::make(arr3(v3, v4, v), arr3(n3, iTnew1, iTnew2)); + triangles[iTnew1] = triangle_t::make(arr3(v1, v, v4), arr3(iT1, iT2, n4)); + triangles[iTnew2] = triangle_t::make(arr3(v3, v, v2), arr3(iT2, iT1, n2)); + // make and add new vertex + add_adjacent_triangles(v, iT1, iTnew2, iT2, iTnew1); + // adjust neighboring triangles and vertices + change_neighbour(n4, iT1, iTnew1); + change_neighbour(n2, iT2, iTnew2); + add_adjacent_triangle(v1, iTnew1); + add_adjacent_triangle(v3, iTnew2); + remove_adjacent_triangle(v2, iT2); + add_adjacent_triangle(v2, iTnew2); + remove_adjacent_triangle(v4, iT1); + add_adjacent_triangle(v4, iTnew1); + // return newly added triangles + std::stack newTriangles; + newTriangles.push(iT1); + newTriangles.push(iTnew2); + newTriangles.push(iT2); + newTriangles.push(iTnew1); + return newTriangles; + } + + std::array trianglesAt(const vec2_& pos) const + { + std::array out = { null_neighbour, null_neighbour }; + for (std::uint32_t i = std::uint32_t(0); i < std::uint32_t(triangles.size()); ++i) { + const triangle_t& t = triangles[i]; + const vec2_& v1 = vertices[t.vertices[0]]; + const vec2_& v2 = vertices[t.vertices[1]]; + const vec2_& v3 = vertices[t.vertices[2]]; + const point_to_triangle_location_t::Enum loc = locate_point_wrt_triangle(pos, v1, v2, v3); + if (loc == point_to_triangle_location_t::OUTSIDE) + continue; + out[0] = i; + if (check_on_edge(loc)) + out[1] = t.neighbors[edge_neighbour(loc)]; + return out; + } + throw std::runtime_error("No triangle was found at position"); + } + + /** + * Ensure that triangulation conforms to constraints (fixed edges) + * @note For each fixed edge that is not present in the triangulation its + * midpoint is recursively added until the original edge is represented by a + * sequence of its pieces. New vertices are inserted. + * @note If some edge appears more than once the input this + * means that multiple boundaries overlap at the edge and impacts how hole + * detection algorithm of triangulator_t::erase_outer_triangles_and_holes works. + * Make sure there are no erroneous duplicates. + * @tparam TEdgeIter iterator that dereferences to custom edge type + * @tparam TGetEdgeVertexStart function object getting start vertex index + * from an edge. + * Getter signature: const TEdgeIter::value_type& -> std::uint32_t + * @tparam TGetEdgeVertexEnd function object getting end vertex index from + * an edge. Getter signature: const TEdgeIter::value_type& -> std::uint32_t + * @param first beginning of the range of edges to add + * @param last end of the range of edges to add + * @param getStart getter of edge start vertex index + * @param getEnd getter of edge end vertex index + */ + template < + typename TEdgeIter, + typename TGetEdgeVertexStart, + typename TGetEdgeVertexEnd> + void conform_to_edges( + TEdgeIter first, + TEdgeIter last, + TGetEdgeVertexStart getStart, + TGetEdgeVertexEnd getEnd); + + /*! + * Returns: + * - intersected triangle index + * - index of point on the left of the line + * - index of point on the right of the line + * If left point is right on the line: no triangle is intersected: + * - triangle index is no-neighbor (invalid) + * - index of point on the line + * - index of point on the right of the line + */ + std::tuple + TintersectedTriangle( + const std::uint32_t iA, + const std::vector& candidates, + const vec2_& a, + const vec2_& b, + const T orientationTolerance = T(0)) const + { + typedef std::vector::const_iterator TriIndCit; + for (TriIndCit it = candidates.begin(); it != candidates.end(); ++it) { + const std::uint32_t iT = *it; + const triangle_t t = triangles[iT]; + const std::uint32_t i = get_vertex_index(t, iA); + const std::uint32_t iP2 = t.vertices[ccw(i)]; + const T orientP2 = orient2D(vertices[iP2], a, b); + const point_to_line_location_t::Enum locP2 = classify_orientation(orientP2); + if (locP2 == point_to_line_location_t::RIGHT_SIDE) { + const std::uint32_t iP1 = t.vertices[cw(i)]; + const T orientP1 = orient2D(vertices[iP1], a, b); + const point_to_line_location_t::Enum locP1 = classify_orientation(orientP1); + if (locP1 == point_to_line_location_t::COLLINEAR) { + return std::make_tuple(null_neighbour, iP1, iP1); + } + if (locP1 == point_to_line_location_t::LEFT_SIDE) { + if (orientationTolerance) { + T closestOrient; + std::uint32_t iClosestP; + if (std::abs(orientP1) <= std::abs(orientP2)) { + closestOrient = orientP1; + iClosestP = iP1; + } else { + closestOrient = orientP2; + iClosestP = iP2; + } + if (classify_orientation( + closestOrient, orientationTolerance) + == point_to_line_location_t::COLLINEAR) { + return std::make_tuple(null_neighbour, iClosestP, iClosestP); + } + } + return std::make_tuple(iT, iP1, iP2); + } + } + } + throw std::runtime_error("Could not find vertex triangle intersected by " + "edge. Note: can be caused by duplicate points."); + } + + /// Returns indices of three resulting triangles + /* Insert point into triangle: split into 3 triangles: + * - create 2 new triangles + * - re-use old triangle for the 3rd + * v3 + * / | \ + * / | \ <-- original triangle (t) + * / | \ + * n3 / | \ n2 + * /newT2|newT1\ + * / v \ + * / __/ \__ \ + * / __/ \__ \ + * / _/ t' \_ \ + * v1 ___________________ v2 + * n1 + */ + std::stack insert_point_in_triangle( + const std::uint32_t v, + const std::uint32_t iT) + { + const std::uint32_t iNewT1 = add_triangle(); + const std::uint32_t iNewT2 = add_triangle(); + + triangle_t& t = triangles[iT]; + const std::array vv = t.vertices; + const std::array nn = t.neighbors; + const std::uint32_t v1 = vv[0], v2 = vv[1], v3 = vv[2]; + const std::uint32_t n1 = nn[0], n2 = nn[1], n3 = nn[2]; + // make two new triangles and convert current triangle to 3rd new + // triangle + using detail::arr3; + triangles[iNewT1] = triangle_t::make(arr3(v2, v3, v), arr3(n2, iNewT2, iT)); + triangles[iNewT2] = triangle_t::make(arr3(v3, v1, v), arr3(n3, iT, iNewT1)); + t = triangle_t::make(arr3(v1, v2, v), arr3(n1, iNewT1, iNewT2)); + // make and add a new vertex + add_adjacent_triangles(v, iT, iNewT1, iNewT2); + // adjust lists of adjacent triangles for v1, v2, v3 + add_adjacent_triangle(v1, iNewT2); + add_adjacent_triangle(v2, iNewT1); + remove_adjacent_triangle(v3, iT); + add_adjacent_triangle(v3, iNewT1); + add_adjacent_triangle(v3, iNewT2); + // change triangle neighbor's neighbors to new triangles + change_neighbour(n2, iT, iNewT1); + change_neighbour(n3, iT, iNewT2); + // return newly added triangles + std::stack newTriangles; + newTriangles.push(iT); + newTriangles.push(iNewT1); + newTriangles.push(iNewT2); + return newTriangles; + } + + std::array walking_search_triangle_at( + const vec2_& pos) const + { + std::array out = { null_neighbour, null_neighbour }; + // Query for a vertex close to pos, to start the search + const std::uint32_t startVertex = m_nearPtLocator.nearPoint(pos, vertices); + const std::uint32_t iT = walk_triangles(startVertex, pos); + // Finished walk, locate point in current triangle + const triangle_t& t = triangles[iT]; + const vec2_& v1 = vertices[t.vertices[0]]; + const vec2_& v2 = vertices[t.vertices[1]]; + const vec2_& v3 = vertices[t.vertices[2]]; + const point_to_triangle_location_t::Enum loc = locate_point_wrt_triangle(pos, v1, v2, v3); + if (loc == point_to_triangle_location_t::OUTSIDE) + throw std::runtime_error("No triangle was found at position"); + out[0] = iT; + if (check_on_edge(loc)) + out[1] = t.neighbors[edge_neighbour(loc)]; + return out; + } + /** + * Ensure that triangulation conforms to constraints (fixed edges) + * @note For each fixed edge that is not present in the triangulation its + * midpoint is recursively added until the original edge is represented by a + * sequence of its pieces. New vertices are inserted. + * @note If some edge appears more than once the input this + * means that multiple boundaries overlap at the edge and impacts how hole + * detection algorithm of triangulator_t::erase_outer_triangles_and_holes works. + * Make sure there are no erroneous duplicates. + * @tparam edges edges to conform to + */ + void conform_to_edges(const std::vector& edges); + /** + * Erase triangles adjacent to super triangle + * + * @note does nothing if custom geometry is used + */ + void eraseSuperTriangle(); + /// Erase triangles outside of constrained boundary using growing + void erase_outer_triangles(); + /** + * Erase triangles outside of constrained boundary and auto-detected holes + * + * @note detecting holes relies on layer peeling based on layer depth + * @note supports overlapping or touching boundaries + */ + void erase_outer_triangles_and_holes(); + /** + * Call this method after directly setting custom super-geometry via + * vertices and triangles members + */ + void initialise_with_custom_supergeometry(); + + /** + * Check if the triangulation was finalized with `erase...` method and + * super-triangle was removed. + * @return true if triangulation is finalized, false otherwise + */ + bool is_finalized() const; + + /** + * Calculate depth of each triangle in constraint triangulation. Supports + * overlapping boundaries. + * + * Perform depth peeling from super triangle to outermost boundary, + * then to next boundary and so on until all triangles are traversed.@n + * For example depth is: + * - 0 for triangles outside outermost boundary + * - 1 for triangles inside boundary but outside hole + * - 2 for triangles in hole + * - 3 for triangles in island and so on... + * @return vector where element at index i stores depth of i-th triangle + */ + + std::vector + calculate_triangle_depths() const + { + std::vector triDepths( + triangles.size(), std::numeric_limits::max()); + std::stack seeds(std::deque(1, vertTris[0].front())); + layer_depth_t layerDepth = 0; + layer_depth_t deepestSeedDepth = 0; + + std::unordered_map> seedsByDepth; + + do { + const std::unordered_map& newSeeds = peel_layer(seeds, layerDepth, triDepths); + + seedsByDepth.erase(layerDepth); + typedef std::unordered_map::const_iterator Iter; + for (Iter it = newSeeds.begin(); it != newSeeds.end(); ++it) { + deepestSeedDepth = std::max(deepestSeedDepth, it->second); + seedsByDepth[it->second].insert(it->first); + } + const std::unordered_set& nextLayerSeeds = seedsByDepth[layerDepth + 1]; + seeds = std::stack( + std::deque(nextLayerSeeds.begin(), nextLayerSeeds.end())); + ++layerDepth; + } while (!seeds.empty() || deepestSeedDepth > layerDepth); + + return triDepths; + } + + /** + * @defgroup Advanced Advanced triangulator_t Methods + * Advanced methods for manually modifying the triangulation from + * outside. Please only use them when you know what you are doing. + */ + /// @{ + + /** + * Flip an edge between two triangle. + * @note Advanced method for manually modifying the triangulation from + * outside. Please call it when you know what you are doing. + * @param iT first triangle + * @param iTopo second triangle + + */ + void do_edgeflip(std::uint32_t iT, std::uint32_t iTopo); + + /** + * Remove triangles with specified indices. + * Adjust internal triangulation state accordingly. + * @param removedTriangles indices of triangles to remove + */ + void remove_triangles(const std::unordered_set& removedTriangles); + /// @} + +private: + /*____ Detail __*/ + void add_super_triangle(const box2d_t& box); + void create_vertex(const vec2_& pos, const std::vector& tris); + void insert_vertex(std::uint32_t iVert); + void enforce_delaunay_property_using_edge_flips( + const vec2_& v, + std::uint32_t iVert, + std::stack& triStack); + /// Flip fixed edges and return a list of flipped fixed edges + std::vector insert_vertex_and_flip_fixed_edges(std::uint32_t iVert); + /** + * Insert an edge into constraint Delaunay triangulation + * @param edge edge to insert + * @param originalEdge original edge inserted edge is part of + */ + void insert_edge(edge_t edge, edge_t originalEdge); + /** + * Conform Delaunay triangulation to a fixed edge by recursively inserting + * mid point of the edge and then conforming to its halves + * @param edge fixed edge to conform to + * @param originalEdges original edges that new edge is piece of + * @param overlaps count of overlapping boundaries at the edge. Only used + * when re-introducing edge with overlaps > 0 + * @param orientationTolerance tolerance for orient2d predicate, + * values [-tolerance,+tolerance] are considered as 0. + */ + void conform_to_edge( + edge_t edge, + std::vector originalEdges, + boundary_overlap_count_t overlaps); + + std::uint32_t walk_triangles(std::uint32_t startVertex, const vec2_& pos) const; + bool check_is_edgeflip_needed( + const vec2_& v, + std::uint32_t iV, + std::uint32_t iV1, + std::uint32_t iV2, + std::uint32_t iV3) const; + bool + check_is_edgeflip_needed(const vec2_& v, std::uint32_t iT, std::uint32_t iTopo, std::uint32_t iVert) const; + void change_neighbour(std::uint32_t iT, std::uint32_t oldNeighbor, std::uint32_t newNeighbor); + void change_neighbour( + std::uint32_t iT, + std::uint32_t iVedge1, + std::uint32_t iVedge2, + std::uint32_t newNeighbor); + void add_adjacent_triangle(std::uint32_t iVertex, std::uint32_t iTriangle); + void + add_adjacent_triangles(std::uint32_t iVertex, std::uint32_t iT1, std::uint32_t iT2, std::uint32_t iT3); + void add_adjacent_triangles( + std::uint32_t iVertex, + std::uint32_t iT1, + std::uint32_t iT2, + std::uint32_t iT3, + std::uint32_t iT4); + void remove_adjacent_triangle(std::uint32_t iVertex, std::uint32_t iTriangle); + std::uint32_t triangulate_pseudo_polygon( + std::uint32_t ia, + std::uint32_t ib, + std::vector::const_iterator pointsFirst, + std::vector::const_iterator pointsLast); + std::uint32_t find_delaunay_point( + std::uint32_t ia, + std::uint32_t ib, + std::vector::const_iterator pointsFirst, + std::vector::const_iterator pointsLast) const; + std::uint32_t pseudo_polygon_outer_triangle(std::uint32_t ia, std::uint32_t ib) const; + std::uint32_t add_triangle(const triangle_t& t); // note: invalidates iterators! + std::uint32_t add_triangle(); // note: invalidates triangle iterators! + /** + * Remove super-triangle (if used) and triangles with specified indices. + * Adjust internal triangulation state accordingly. + * @removedTriangles indices of triangles to remove + */ + void finalise_triangulation(const std::unordered_set& removedTriangles); + std::unordered_set grow_to_boundary(std::stack seeds) const; + + void fixEdge( + const edge_t& edge, + const boundary_overlap_count_t overlaps) + { + fixedEdges.insert(edge); + overlapCount[edge] = overlaps; // override overlap counter + } + + void fixEdge(const edge_t& edge) + { + if (!fixedEdges.insert(edge).second) { + ++overlapCount[edge]; // if edge is already fixed increment the counter + } + } + + void fixEdge( + const edge_t& edge, + const edge_t& originalEdge) + { + fixEdge(edge); + if (edge != originalEdge) + detail::insert_unique(pieceToOriginals[edge], originalEdge); + } + /** + * Flag triangle as dummy + * @note Advanced method for manually modifying the triangulation from + * outside. Please call it when you know what you are doing. + * @param iT index of a triangle to flag + */ + void make_dummies(const std::uint32_t iT) + { + const triangle_t& t = triangles[iT]; + + typedef std::array::const_iterator VCit; + for (VCit iV = t.vertices.begin(); iV != t.vertices.end(); ++iV) + remove_adjacent_triangle(*iV, iT); + + typedef std::array::const_iterator NCit; + for (NCit iTn = t.neighbors.begin(); iTn != t.neighbors.end(); ++iTn) + change_neighbour(*iTn, iT, null_neighbour); + + m_dummyTris.push_back(iT); + } + /** + * Erase all dummy triangles + * @note Advanced method for manually modifying the triangulation from + * outside. Please call it when you know what you are doing. + */ + void erase_dummies() + { + if (m_dummyTris.empty()) + return; + const std::unordered_set dummySet(m_dummyTris.begin(), m_dummyTris.end()); + std::unordered_map triIndMap; + triIndMap[null_neighbour] = null_neighbour; + for (std::uint32_t iT(0), iTnew(0); iT < std::uint32_t(triangles.size()); ++iT) { + if (dummySet.count(iT)) + continue; + triIndMap[iT] = iTnew; + triangles[iTnew] = triangles[iT]; + iTnew++; + } + triangles.erase(triangles.end() - dummySet.size(), triangles.end()); + + // remap adjacent triangle indices for vertices + typedef typename std::vector>::iterator VertTrisIt; + for (VertTrisIt vTris = vertTris.begin(); vTris != vertTris.end(); ++vTris) { + for (std::vector::iterator iT = vTris->begin(); iT != vTris->end(); ++iT) + *iT = triIndMap[*iT]; + } + // remap neighbor indices for triangles + for (std::vector::iterator t = triangles.begin(); t != triangles.end(); ++t) { + std::array& nn = t->neighbors; + for (std::array::iterator iN = nn.begin(); iN != nn.end(); ++iN) + *iN = triIndMap[*iN]; + } + // clear dummy triangles + m_dummyTris = std::vector(); + } + + /** + * Depth-peel a layer in triangulation, used when calculating triangle + * depths + * + * It takes starting seed triangles, traverses neighboring triangles, and + * assigns given layer depth to the traversed triangles. Traversal is + * blocked by constraint edges. Triangles behind constraint edges are + * recorded as seeds of next layer and returned from the function. + * + * @param seeds indices of seed triangles + * @param layerDepth current layer's depth to mark triangles with + * @param[in, out] triDepths depths of triangles + * @return triangles of the deeper layers that are adjacent to the peeled + * layer. To be used as seeds when peeling deeper layers. + */ + std::unordered_map + peel_layer( + std::stack seeds, + const layer_depth_t layerDepth, + std::vector& triDepths) const + { + std::unordered_map behindBoundary; + while (!seeds.empty()) { + const std::uint32_t iT = seeds.top(); + seeds.pop(); + triDepths[iT] = layerDepth; + behindBoundary.erase(iT); + const triangle_t& t = triangles[iT]; + for (std::uint32_t i(0); i < std::uint32_t(3); ++i) { + const edge_t opEdge(t.vertices[ccw(i)], t.vertices[cw(i)]); + const std::uint32_t iN = t.neighbors[get_opposite_neighbour_from_vertex(i)]; + if (iN == null_neighbour || triDepths[iN] <= layerDepth) + continue; + if (fixedEdges.count(opEdge)) { + const std::unordered_map::const_iterator cit = overlapCount.find(opEdge); + const layer_depth_t triDepth = cit == overlapCount.end() + ? layerDepth + 1 + : layerDepth + cit->second + 1; + behindBoundary[iN] = triDepth; + continue; + } + seeds.push(iN); + } + } + return behindBoundary; + } + + std::vector m_dummyTris; + TNearPointLocator m_nearPtLocator; + std::size_t m_nTargetVerts; + super_geometry_type_t::Enum m_superGeomType; + vertex_insertion_order_t::Enum m_vertexInsertionOrder; + action_on_intersecting_constraint_edges_t::Enum m_intersectingEdgesStrategy; + T m_minDistToConstraintEdge; +}; + +/// @} +/// @} + +namespace detail { + + static std::mt19937 randGenerator(9001); + + template + void random_shuffle(RandomIt first, RandomIt last) + { + typename std::iterator_traits::difference_type i, n; + n = last - first; + for (i = n - 1; i > 0; --i) { + std::swap(first[i], first[randGenerator() % (i + 1)]); + } + } + +} // namespace detail + +//----------------------- +// triangulator_t methods +//----------------------- +template +template < + typename TVertexIter, + typename TGetVertexCoordX, + typename TGetVertexCoordY> +void triangulator_t::insert_vertices( + const TVertexIter first, + const TVertexIter last, + TGetVertexCoordX get_x_coord, + TGetVertexCoordY get_y_coord) +{ + if (is_finalized()) { + throw std::runtime_error( + "triangulator_t was finalized with 'erase...' method. Inserting new " + "vertices is not possible"); + } + + detail::randGenerator.seed(9001); // ensure deterministic behavior + + if (vertices.empty()) { + add_super_triangle(expand_with_points(first, last, get_x_coord, get_y_coord)); + } + + const std::size_t nExistingVerts = vertices.size(); + + vertices.reserve(nExistingVerts + std::distance(first, last)); + + for (TVertexIter it = first; it != last; ++it) { + create_vertex(vec2_::make(get_x_coord(*it), get_y_coord(*it)), std::vector()); + } + + switch (m_vertexInsertionOrder) { + + case vertex_insertion_order_t::AS_GIVEN: { + + for (TVertexIter it = first; it != last; ++it) { + insert_vertex(std::uint32_t(nExistingVerts + std::distance(first, it))); + } + + break; + } + case vertex_insertion_order_t::RANDOM: { + std::vector ii(std::distance(first, last)); + typedef std::vector::iterator Iter; + std::uint32_t value = static_cast(nExistingVerts); + for (Iter it = ii.begin(); it != ii.end(); ++it, ++value) + *it = value; + detail::random_shuffle(ii.begin(), ii.end()); + for (Iter it = ii.begin(); it != ii.end(); ++it) + insert_vertex(*it); + break; + } + } +} + +template +template < + typename TEdgeIter, + typename TGetEdgeVertexStart, + typename TGetEdgeVertexEnd> +void triangulator_t::insert_edges( + TEdgeIter first, + const TEdgeIter last, + TGetEdgeVertexStart getStart, + TGetEdgeVertexEnd getEnd) +{ + if (is_finalized()) { + throw std::runtime_error( + "triangulator_t was finalized with 'erase...' method. Inserting new " + "edges is not possible"); + } + for (; first != last; ++first) { + // +3 to account for super-triangle vertices + const edge_t edge( + std::uint32_t(getStart(*first) + m_nTargetVerts), + std::uint32_t(getEnd(*first) + m_nTargetVerts)); + insert_edge(edge, edge); + } + erase_dummies(); +} + +template +template < + typename TEdgeIter, + typename TGetEdgeVertexStart, + typename TGetEdgeVertexEnd> +void triangulator_t::conform_to_edges( + TEdgeIter first, + const TEdgeIter last, + TGetEdgeVertexStart getStart, + TGetEdgeVertexEnd getEnd) +{ + if (is_finalized()) { + throw std::runtime_error( + "triangulator_t was finalized with 'erase...' method. Conforming to " + "new edges is not possible"); + } + for (; first != last; ++first) { + // +3 to account for super-triangle vertices + const edge_t e( + std::uint32_t(getStart(*first) + m_nTargetVerts), + std::uint32_t(getEnd(*first) + m_nTargetVerts)); + conform_to_edge(e, std::vector(1, e), 0); + } + erase_dummies(); +} + +} // namespace cdt + +#ifndef CDT_USE_AS_COMPILED_LIBRARY +//#include "triangulator_t.hpp" + +#include +#include +#include +#include + +namespace cdt { + +template +triangulator_t::triangulator_t() + : m_nTargetVerts(detail::defaults::nTargetVerts) + , m_superGeomType(detail::defaults::superGeomType) + , m_vertexInsertionOrder(detail::defaults::vertexInsertionOrder) + , m_intersectingEdgesStrategy(detail::defaults::intersectingEdgesStrategy) + , m_minDistToConstraintEdge(detail::defaults::minDistToConstraintEdge) +{ +} + +template +triangulator_t::triangulator_t( + const vertex_insertion_order_t::Enum vertexInsertionOrder) + : m_nTargetVerts(detail::defaults::nTargetVerts) + , m_superGeomType(detail::defaults::superGeomType) + , m_vertexInsertionOrder(vertexInsertionOrder) + , m_intersectingEdgesStrategy(detail::defaults::intersectingEdgesStrategy) + , m_minDistToConstraintEdge(detail::defaults::minDistToConstraintEdge) +{ +} + +template +triangulator_t::triangulator_t( + const vertex_insertion_order_t::Enum vertexInsertionOrder, + const action_on_intersecting_constraint_edges_t::Enum intersectingEdgesStrategy, + const T minDistToConstraintEdge) + : m_nTargetVerts(detail::defaults::nTargetVerts) + , m_superGeomType(detail::defaults::superGeomType) + , m_vertexInsertionOrder(vertexInsertionOrder) + , m_intersectingEdgesStrategy(intersectingEdgesStrategy) + , m_minDistToConstraintEdge(minDistToConstraintEdge) +{ +} + +template +triangulator_t::triangulator_t( + const vertex_insertion_order_t::Enum vertexInsertionOrder, + const TNearPointLocator& nearPtLocator, + const action_on_intersecting_constraint_edges_t::Enum intersectingEdgesStrategy, + const T minDistToConstraintEdge) + : m_nTargetVerts(detail::defaults::nTargetVerts) + , m_nearPtLocator(nearPtLocator) + , m_superGeomType(detail::defaults::superGeomType) + , m_vertexInsertionOrder(vertexInsertionOrder) + , m_intersectingEdgesStrategy(intersectingEdgesStrategy) + , m_minDistToConstraintEdge(minDistToConstraintEdge) +{ +} + +template +void triangulator_t::change_neighbour( + const std::uint32_t iT, + const std::uint32_t iVedge1, + const std::uint32_t iVedge2, + const std::uint32_t newNeighbor) +{ + triangle_t& t = triangles[iT]; + t.neighbors[opposite_triangle_index(t, iVedge1, iVedge2)] = newNeighbor; +} + +template +void triangulator_t::eraseSuperTriangle() +{ + if (m_superGeomType != super_geometry_type_t::SUPER_TRIANGLE) + return; + // find triangles adjacent to super-triangle's vertices + std::unordered_set toErase; + toErase.reserve( + vertTris[0].size() + vertTris[1].size() + vertTris[2].size()); + for (std::uint32_t iT(0); iT < std::uint32_t(triangles.size()); ++iT) { + triangle_t& t = triangles[iT]; + if (t.vertices[0] < 3 || t.vertices[1] < 3 || t.vertices[2] < 3) + toErase.insert(iT); + } + finalise_triangulation(toErase); +} + +template +void triangulator_t::erase_outer_triangles() +{ + // make dummy triangles adjacent to super-triangle's vertices + const std::stack seed(std::deque(1, vertTris[0].front())); + const std::unordered_set toErase = grow_to_boundary(seed); + finalise_triangulation(toErase); +} + +template +void triangulator_t::erase_outer_triangles_and_holes() +{ + const std::vector triDepths = calculate_triangle_depths(); + std::unordered_set toErase; + toErase.reserve(triangles.size()); + + for (std::size_t iT = 0; iT != triangles.size(); ++iT) { + if (triDepths[iT] % 2 == 0){ + toErase.insert(static_cast(iT)); + } + } + + finalise_triangulation(toErase); +} + +/// Remap removing super-triangle: subtract 3 from vertices +inline edge_t remap_no_supertriangle(const edge_t& e) +{ + return edge_t(e.v1() - 3, e.v2() - 3); +} + +template +void triangulator_t::remove_triangles( + const std::unordered_set& removedTriangles) +{ + if (removedTriangles.empty()) + return; + // remove triangles and calculate triangle index mapping + std::unordered_map triIndMap; + for (std::uint32_t iT(0), iTnew(0); iT < std::uint32_t(triangles.size()); ++iT) { + if (removedTriangles.count(iT)) + continue; + triIndMap[iT] = iTnew; + triangles[iTnew] = triangles[iT]; + iTnew++; + } + triangles.erase(triangles.end() - removedTriangles.size(), triangles.end()); + // adjust triangles' neighbors + vertTris = std::vector>(); + for (std::uint32_t iT = 0; iT < triangles.size(); ++iT) { + triangle_t& t = triangles[iT]; + // update neighbors to account for removed triangles + std::array& nn = t.neighbors; + for (std::array::iterator n = nn.begin(); n != nn.end(); ++n) { + if (removedTriangles.count(*n)) { + *n = null_neighbour; + } else if (*n != null_neighbour) { + *n = triIndMap[*n]; + } + } + } +} + +template +void triangulator_t::finalise_triangulation( + const std::unordered_set& removedTriangles) +{ + erase_dummies(); + // remove super-triangle + if (m_superGeomType == super_geometry_type_t::SUPER_TRIANGLE) { + vertices.erase(vertices.begin(), vertices.begin() + 3); + if (removedTriangles.empty()) + vertTris.erase(vertTris.begin(), vertTris.begin() + 3); + // edge_t re-mapping + { // fixed edges + std::unordered_set updatedFixedEdges; + typedef std::unordered_set::const_iterator It; + for (It e = fixedEdges.begin(); e != fixedEdges.end(); ++e) { + updatedFixedEdges.insert(remap_no_supertriangle(*e)); + } + fixedEdges = updatedFixedEdges; + } + { // overlap count + std::unordered_map updatedOverlapCount; + typedef std::unordered_map::const_iterator + It; + for (It it = overlapCount.begin(); it != overlapCount.end(); ++it) { + updatedOverlapCount.insert(std::make_pair( + remap_no_supertriangle(it->first), it->second)); + } + overlapCount = updatedOverlapCount; + } + { // split edges mapping + std::unordered_map> updatedPieceToOriginals; + typedef std::unordered_map>::const_iterator It; + for (It it = pieceToOriginals.begin(); it != pieceToOriginals.end(); + ++it) { + std::vector ee = it->second; + for (std::vector::iterator eeIt = ee.begin(); eeIt != ee.end(); + ++eeIt) { + *eeIt = remap_no_supertriangle(*eeIt); + } + updatedPieceToOriginals.insert( + std::make_pair(remap_no_supertriangle(it->first), ee)); + } + pieceToOriginals = updatedPieceToOriginals; + } + } + // remove other triangles + remove_triangles(removedTriangles); + // adjust triangle vertices: account for removed super-triangle + if (m_superGeomType == super_geometry_type_t::SUPER_TRIANGLE) { + for (std::vector::iterator t = triangles.begin(); t != triangles.end(); + ++t) { + std::array& vv = t->vertices; + for (std::array::iterator v = vv.begin(); v != vv.end(); ++v) { + *v -= 3; + } + } + } +} + +template +void triangulator_t::initialise_with_custom_supergeometry() +{ + m_nearPtLocator.initialize(vertices); + m_nTargetVerts = vertices.size(); + m_superGeomType = super_geometry_type_t::CUSTOM; +} + +template +std::unordered_set triangulator_t::grow_to_boundary( + std::stack seeds) const +{ + std::unordered_set traversed; + while (!seeds.empty()) { + const std::uint32_t iT = seeds.top(); + seeds.pop(); + traversed.insert(iT); + const triangle_t& t = triangles[iT]; + for (std::uint32_t i(0); i < std::uint32_t(3); ++i) { + const edge_t opEdge(t.vertices[ccw(i)], t.vertices[cw(i)]); + if (fixedEdges.count(opEdge)) + continue; + const std::uint32_t iN = t.neighbors[get_opposite_neighbour_from_vertex(i)]; + if (iN != null_neighbour && traversed.count(iN) == 0) + seeds.push(iN); + } + } + return traversed; +} + +template +std::uint32_t triangulator_t::add_triangle(const triangle_t& t) +{ + if (m_dummyTris.empty()) { + triangles.push_back(t); + return std::uint32_t(triangles.size() - 1); + } + const std::uint32_t nxtDummy = m_dummyTris.back(); + m_dummyTris.pop_back(); + triangles[nxtDummy] = t; + return nxtDummy; +} + +template +std::uint32_t triangulator_t::add_triangle() +{ + if (m_dummyTris.empty()) { + const triangle_t dummy = { + { null_vertex, null_vertex, null_vertex }, + { null_neighbour, null_neighbour, null_neighbour } + }; + triangles.push_back(dummy); + return std::uint32_t(triangles.size() - 1); + } + const std::uint32_t nxtDummy = m_dummyTris.back(); + m_dummyTris.pop_back(); + return nxtDummy; +} + +template +void triangulator_t::insert_edges( + const std::vector& edges) +{ + insert_edges(edges.begin(), edges.end(), edge_get_v1, edge_get_v2); +} + +template +void triangulator_t::conform_to_edges( + const std::vector& edges) +{ + conform_to_edges(edges.begin(), edges.end(), edge_get_v1, edge_get_v2); +} + +namespace detail { + + template + T lerp(const T& a, const T& b, const T t) + { + return (T(1) - t) * a + t * b; + } + + // Precondition: ab and cd intersect normally + template + vec2_ get_intersection_point_coords( + const vec2_& a, + const vec2_& b, + const vec2_& c, + const vec2_& d) + { + // interpolate point on the shorter segment + if (get_square_distance(a, b) < get_square_distance(c, d)) { + // const T a_cd = orient2d(c.x(), c.y(), d.x(), d.y(), a.x(), a.y()); + // const T b_cd = orient2d(c.x(), c.y(), d.x(), d.y(), b.x(), b.y()); + const T a_cd = orient2d(c, d, a); + const T b_cd = orient2d(c, d, b); + const T t = a_cd / (a_cd - b_cd); + return vec2_::make(lerp(a.x(), b.x(), t), lerp(a.y(), b.y(), t)); + } else { + // const T c_ab = orient2d(a.x(), a.y(), b.x(), b.y(), c.x(), c.y()); + // const T d_ab = orient2d(a.x(), a.y(), b.x(), b.y(), d.x(), d.y()); + const T c_ab = orient2d(a, b, c); + const T d_ab = orient2d(a, b, d); + const T t = c_ab / (c_ab - d_ab); + return vec2_::make(lerp(c.x(), d.x(), t), lerp(c.y(), d.y(), t)); + } + } + +} // namespace detail + +template +void triangulator_t::insert_edge( + const edge_t edge, + const edge_t originalEdge) +{ + const std::uint32_t iA = edge.v1(); + std::uint32_t iB = edge.v2(); + if (iA == iB) // edge connects a vertex to itself + return; + const std::vector& aTris = vertTris[iA]; + const std::vector& bTris = vertTris[iB]; + const vec2_& a = vertices[iA]; + const vec2_& b = vertices[iB]; + if (check_vertices_share_edge(aTris, bTris)) { + fixEdge(edge, originalEdge); + return; + } + + const T distanceTolerance = m_minDistToConstraintEdge == T(0) + ? T(0) + : m_minDistToConstraintEdge * distance(a, b); + + std::uint32_t iT; + std::uint32_t iVleft, iVright; + std::tie(iT, iVleft, iVright) = get_intersected_triangle(iA, aTris, a, b, distanceTolerance); + // if one of the triangle vertices is on the edge, move edge start + if (iT == null_neighbour) { + const edge_t edgePart(iA, iVleft); + fixEdge(edgePart, originalEdge); + return insert_edge(edge_t(iVleft, iB), originalEdge); + } + std::vector intersected(1, iT); + std::vector ptsLeft(1, iVleft); + std::vector ptsRight(1, iVright); + std::uint32_t iV = iA; + triangle_t t = triangles[iT]; + while (std::find(t.vertices.begin(), t.vertices.end(), iB) == t.vertices.end()) { + const std::uint32_t iTopo = get_opposite_triangle_index(t, iV); + const triangle_t& tOpo = triangles[iTopo]; + const std::uint32_t iVopo = get_opposed_vertex_index(tOpo, iT); + const vec2_ vOpo = vertices[iVopo]; + + // RESOLVE intersection between two constraint edges if needed + if (m_intersectingEdgesStrategy == action_on_intersecting_constraint_edges_t::RESOLVE && fixedEdges.count(edge_t(iVleft, iVright))) { + const std::uint32_t iNewVert = static_cast(vertices.size()); + + // split constraint edge that already exists in triangulation + const edge_t splitEdge(iVleft, iVright); + const edge_t half1(iVleft, iNewVert); + const edge_t half2(iNewVert, iVright); + const boundary_overlap_count_t overlaps = overlapCount[splitEdge]; + // remove the edge that will be split + fixedEdges.erase(splitEdge); + overlapCount.erase(splitEdge); + // add split edge's halves + fixEdge(half1, overlaps); + fixEdge(half2, overlaps); + // maintain piece-to-original mapping + std::vector newOriginals(1, splitEdge); + const std::unordered_map>::const_iterator originalsIt = pieceToOriginals.find(splitEdge); + if (originalsIt != pieceToOriginals.end()) { // edge being split was split before: pass-through originals + newOriginals = originalsIt->second; + pieceToOriginals.erase(originalsIt); + } + detail::insert_unique(pieceToOriginals[half1], newOriginals); + detail::insert_unique(pieceToOriginals[half2], newOriginals); + + // add a new point at the intersection of two constraint edges + const vec2_ newV = detail::get_intersection_point_coords( + vertices[iA], + vertices[iB], + vertices[iVleft], + vertices[iVright]); + create_vertex(newV, std::vector()); + std::stack triStack = insert_point_on_edge(iNewVert, iT, iTopo); + enforce_delaunay_property_using_edge_flips(newV, iNewVert, triStack); + // TODO: is it's possible to re-use pseudo-polygons + // for inserting [iA, iNewVert] edge half? + insert_edge(edge_t(iA, iNewVert), originalEdge); + insert_edge(edge_t(iNewVert, iB), originalEdge); + return; + } + + intersected.push_back(iTopo); + iT = iTopo; + t = triangles[iT]; + + const point_to_line_location_t::Enum loc = locate_point_wrt_line(vOpo, a, b, distanceTolerance); + if (loc == point_to_line_location_t::LEFT_SIDE) { + ptsLeft.push_back(iVopo); + iV = iVleft; + iVleft = iVopo; + } else if (loc == point_to_line_location_t::RIGHT_SIDE) { + ptsRight.push_back(iVopo); + iV = iVright; + iVright = iVopo; + } else // encountered point on the edge + iB = iVopo; + } + // Remove intersected triangles + typedef std::vector::const_iterator TriIndCit; + for (TriIndCit it = intersected.begin(); it != intersected.end(); ++it) + make_dummies(*it); + // Triangulate pseudo-polygons on both sides + const std::uint32_t iTleft = triangulate_pseudo_polygon(iA, iB, ptsLeft.begin(), ptsLeft.end()); + std::reverse(ptsRight.begin(), ptsRight.end()); + const std::uint32_t iTright = triangulate_pseudo_polygon(iB, iA, ptsRight.begin(), ptsRight.end()); + change_neighbour(iTleft, null_neighbour, iTright); + change_neighbour(iTright, null_neighbour, iTleft); + + if (iB != edge.v2()) // encountered point on the edge + { + // fix edge part + const edge_t edgePart(iA, iB); + fixEdge(edgePart, originalEdge); + return insert_edge(edge_t(iB, edge.v2()), originalEdge); + } else { + fixEdge(edge, originalEdge); + } +} + +template +void triangulator_t::conform_to_edge( + const edge_t edge, + std::vector originalEdges, + const boundary_overlap_count_t overlaps) +{ + const std::uint32_t iA = edge.v1(); + std::uint32_t iB = edge.v2(); + if (iA == iB) // edge connects a vertex to itself + return; + const std::vector& aTris = vertTris[iA]; + const std::vector& bTris = vertTris[iB]; + const vec2_& a = vertices[iA]; + const vec2_& b = vertices[iB]; + if (check_vertices_share_edge(aTris, bTris)) { + overlaps > 0 ? fixEdge(edge, overlaps) : fixEdge(edge); + // avoid marking edge as a part of itself + if (!originalEdges.empty() && edge != originalEdges.front()) { + detail::insert_unique(pieceToOriginals[edge], originalEdges); + } + return; + } + + const T distanceTolerance = m_minDistToConstraintEdge == T(0) + ? T(0) + : m_minDistToConstraintEdge * distance(a, b); + std::uint32_t iT; + std::uint32_t iVleft, iVright; + std::tie(iT, iVleft, iVright) = get_intersected_triangle(iA, aTris, a, b, distanceTolerance); + // if one of the triangle vertices is on the edge, move edge start + if (iT == null_neighbour) { + const edge_t edgePart(iA, iVleft); + overlaps > 0 ? fixEdge(edgePart, overlaps) : fixEdge(edgePart); + detail::insert_unique(pieceToOriginals[edgePart], originalEdges); + return conform_to_edge(edge_t(iVleft, iB), originalEdges, overlaps); + } + + std::uint32_t iV = iA; + triangle_t t = triangles[iT]; + while (std::find(t.vertices.begin(), t.vertices.end(), iB) == t.vertices.end()) { + const std::uint32_t iTopo = get_opposite_triangle_index(t, iV); + const triangle_t& tOpo = triangles[iTopo]; + const std::uint32_t iVopo = get_opposed_vertex_index(tOpo, iT); + const vec2_ vOpo = vertices[iVopo]; + + // RESOLVE intersection between two constraint edges if needed + if (m_intersectingEdgesStrategy == action_on_intersecting_constraint_edges_t::RESOLVE && fixedEdges.count(edge_t(iVleft, iVright))) { + const std::uint32_t iNewVert = static_cast(vertices.size()); + + // split constraint edge that already exists in triangulation + const edge_t splitEdge(iVleft, iVright); + const edge_t half1(iVleft, iNewVert); + const edge_t half2(iNewVert, iVright); + const boundary_overlap_count_t overlaps = overlapCount[splitEdge]; + // remove the edge that will be split + fixedEdges.erase(splitEdge); + overlapCount.erase(splitEdge); + // add split edge's halves + fixEdge(half1, overlaps); + fixEdge(half2, overlaps); + // maintain piece-to-original mapping + std::vector newOriginals(1, splitEdge); + const std::unordered_map>::const_iterator originalsIt = pieceToOriginals.find(splitEdge); + if (originalsIt != pieceToOriginals.end()) { // edge being split was split before: pass-through originals + newOriginals = originalsIt->second; + pieceToOriginals.erase(originalsIt); + } + detail::insert_unique(pieceToOriginals[half1], newOriginals); + detail::insert_unique(pieceToOriginals[half2], newOriginals); + + // add a new point at the intersection of two constraint edges + const vec2_ newV = detail::get_intersection_point_coords( + vertices[iA], + vertices[iB], + vertices[iVleft], + vertices[iVright]); + create_vertex(newV, std::vector()); + std::stack triStack = insert_point_on_edge(iNewVert, iT, iTopo); + enforce_delaunay_property_using_edge_flips(newV, iNewVert, triStack); + conform_to_edge(edge_t(iA, iNewVert), originalEdges, overlaps); + conform_to_edge(edge_t(iNewVert, iB), originalEdges, overlaps); + return; + } + + iT = iTopo; + t = triangles[iT]; + + const point_to_line_location_t::Enum loc = locate_point_wrt_line(vOpo, a, b, distanceTolerance); + if (loc == point_to_line_location_t::LEFT_SIDE) { + iV = iVleft; + iVleft = iVopo; + } else if (loc == point_to_line_location_t::RIGHT_SIDE) { + iV = iVright; + iVright = iVopo; + } else // encountered point on the edge + iB = iVopo; + } + /**/ + + // add mid-point to triangulation + const std::uint32_t iMid = static_cast(vertices.size()); + const vec2_& start = vertices[iA]; + const vec2_& end = vertices[iB]; + create_vertex( + vec2_::make((start.x() + end.x()) / T(2), (start.y() + end.y()) / T(2)), + std::vector()); + const std::vector flippedFixedEdges = insert_vertex_and_flip_fixed_edges(iMid); + + conform_to_edge(edge_t(iA, iMid), originalEdges, overlaps); + conform_to_edge(edge_t(iMid, iB), originalEdges, overlaps); + // re-introduce fixed edges that were flipped + // and make sure overlap count is preserved + for (std::vector::const_iterator it = flippedFixedEdges.begin(); + it != flippedFixedEdges.end(); + ++it) { + fixedEdges.erase(*it); + + boundary_overlap_count_t prevOverlaps = 0; + const std::unordered_map::const_iterator + overlapsIt + = overlapCount.find(*it); + if (overlapsIt != overlapCount.end()) { + prevOverlaps = overlapsIt->second; + overlapCount.erase(overlapsIt); + } + // override overlapping boundaries count when re-inserting an edge + std::vector prevOriginals(1, *it); + const std::unordered_map>::const_iterator originalsIt = pieceToOriginals.find(*it); + if (originalsIt != pieceToOriginals.end()) { + prevOriginals = originalsIt->second; + } + conform_to_edge(*it, prevOriginals, prevOverlaps); + } + if (iB != edge.v2()) + conform_to_edge(edge_t(iB, edge.v2()), originalEdges, overlaps); +} + +template +void triangulator_t::add_super_triangle(const box2d_t& box) +{ + m_nTargetVerts = 3; + m_superGeomType = super_geometry_type_t::SUPER_TRIANGLE; + + const vec2_ center = { + (box.min.x() + box.max.x()) / T(2), (box.min.y() + box.max.y()) / T(2) + }; + const T w = box.max.x() - box.min.x(); + const T h = box.max.y() - box.min.y(); + T r = std::sqrt(w * w + h * h) / T(2); // incircle radius + r *= T(1.1); + const T R = T(2) * r; // excircle radius + const T shiftX = R * std::sqrt(T(3)) / T(2); // R * cos(30 deg) + const vec2_ posV1 = { center.x() - shiftX, center.y() - r }; + const vec2_ posV2 = { center.x() + shiftX, center.y() - r }; + const vec2_ posV3 = { center.x(), center.y() + R }; + create_vertex(posV1, std::vector(1, std::uint32_t(0))); + create_vertex(posV2, std::vector(1, std::uint32_t(0))); + create_vertex(posV3, std::vector(1, std::uint32_t(0))); + const triangle_t superTri = { + { std::uint32_t(0), std::uint32_t(1), std::uint32_t(2) }, + { null_neighbour, null_neighbour, null_neighbour } + }; + add_triangle(superTri); + m_nearPtLocator.initialize(vertices); +} + +template +void triangulator_t::create_vertex( + const vec2_& pos, + const std::vector& tris) +{ + vertices.push_back(pos); + vertTris.push_back(tris); +} + +template +std::vector +triangulator_t::insert_vertex_and_flip_fixed_edges( + const std::uint32_t iVert) +{ + std::vector flippedFixedEdges; + + const vec2_& v = vertices[iVert]; + std::array trisAt = walking_search_triangle_at(v); + std::stack triStack = trisAt[1] == null_neighbour + ? insert_point_in_triangle(iVert, trisAt[0]) + : insert_point_on_edge(iVert, trisAt[0], trisAt[1]); + while (!triStack.empty()) { + const std::uint32_t iT = triStack.top(); + triStack.pop(); + + const triangle_t& t = triangles[iT]; + const std::uint32_t iTopo = get_opposite_triangle_index(t, iVert); + if (iTopo == null_neighbour) + continue; + + /* + * v3 original edge: (v1, v3) + * /|\ flip-candidate edge: (v, v2) + * / | \ + * / | \ + * / | \ + * new vertex--> v | v2 + * \ | / + * \ | / + * \ | / + * \|/ + * v1 + */ + const triangle_t& tOpo = triangles[iTopo]; + const std::uint32_t i = get_opposite_vertex_index(tOpo, iT); + const std::uint32_t iV2 = tOpo.vertices[i]; + const std::uint32_t iV1 = tOpo.vertices[cw(i)]; + const std::uint32_t iV3 = tOpo.vertices[ccw(i)]; + + if (check_is_edgeflip_needed(v, iVert, iV1, iV2, iV3)) { + // if flipped edge is fixed, remember it + const edge_t flippedEdge(iV1, iV3); + if (fixedEdges.count(flippedEdge)) + flippedFixedEdges.push_back(flippedEdge); + + do_edgeflip(iT, iTopo); + triStack.push(iT); + triStack.push(iTopo); + } + } + + m_nearPtLocator.add_point(iVert, vertices); + return flippedFixedEdges; +} + +template +void triangulator_t::insert_vertex(const std::uint32_t iVert) +{ + const vec2_& v = vertices[iVert]; + + std::array trisAt = walking_search_triangle_at(v); + + std::stack triStack = (trisAt[1] == null_neighbour) + ? insert_point_in_triangle(iVert, trisAt[0]) + : insert_point_on_edge(iVert, trisAt[0], trisAt[1]); + + enforce_delaunay_property_using_edge_flips(v, iVert, triStack); + + m_nearPtLocator.add_point(iVert, vertices); +} + +template +void triangulator_t::enforce_delaunay_property_using_edge_flips( + const vec2_& v, + const std::uint32_t iVert, + std::stack& triStack) +{ + while (!triStack.empty()) { + + const std::uint32_t iT = triStack.top(); + triStack.pop(); + + const triangle_t& t = triangles[iT]; + const std::uint32_t iTopo = get_opposite_triangle_index(t, iVert); + + if (iTopo == null_neighbour) { + continue; + } + + if (check_is_edgeflip_needed(v, iT, iTopo, iVert)) { + + do_edgeflip(iT, iTopo); + + triStack.push(iT); + triStack.push(iTopo); + } + } +} + +/*! + * Handles super-triangle vertices. + * Super-tri points are not infinitely far and influence the input points + * Three cases are possible: + * 1. If one of the opposed vertices is super-tri: no flip needed + * 2. One of the shared vertices is super-tri: + * check if on point is same side of line formed by non-super-tri + * vertices as the non-super-tri shared vertex + * 3. None of the vertices are super-tri: normal circumcircle test + */ +/* + * v3 original edge: (v1, v3) + * /|\ flip-candidate edge: (v, v2) + * / | \ + * / | \ + * / | \ + * new vertex--> v | v2 + * \ | / + * \ | / + * \ | / + * \|/ + * v1 + */ +template +bool triangulator_t::check_is_edgeflip_needed( + const vec2_& v, + const std::uint32_t iV, + const std::uint32_t iV1, + const std::uint32_t iV2, + const std::uint32_t iV3) const +{ + const vec2_& v1 = vertices[iV1]; + const vec2_& v2 = vertices[iV2]; + const vec2_& v3 = vertices[iV3]; + if (m_superGeomType == super_geometry_type_t::SUPER_TRIANGLE) { + // If flip-candidate edge touches super-triangle in-circumference + // test has to be replaced with orient2d test against the line + // formed by two non-artificial vertices (that don't belong to + // super-triangle) + if (iV < 3) // flip-candidate edge touches super-triangle + { + // does original edge also touch super-triangle? + if (iV1 < 3) + return locate_point_wrt_line(v1, v2, v3) == locate_point_wrt_line(v, v2, v3); + if (iV3 < 3) + return locate_point_wrt_line(v3, v1, v2) == locate_point_wrt_line(v, v1, v2); + return false; // original edge does not touch super-triangle + } + if (iV2 < 3) // flip-candidate edge touches super-triangle + { + // does original edge also touch super-triangle? + if (iV1 < 3) + return locate_point_wrt_line(v1, v, v3) == locate_point_wrt_line(v2, v, v3); + if (iV3 < 3) + return locate_point_wrt_line(v3, v1, v) == locate_point_wrt_line(v2, v1, v); + return false; // original edge does not touch super-triangle + } + // flip-candidate edge does not touch super-triangle + if (iV1 < 3) + return locate_point_wrt_line(v1, v2, v3) == locate_point_wrt_line(v, v2, v3); + if (iV3 < 3) + return locate_point_wrt_line(v3, v1, v2) == locate_point_wrt_line(v, v1, v2); + } + return check_is_in_circumcircle(v, v1, v2, v3); +} + +template +bool triangulator_t::check_is_edgeflip_needed( + const vec2_& v, + const std::uint32_t iT, + const std::uint32_t iTopo, + const std::uint32_t iV) const +{ + /* + * v3 original edge: (v1, v3) + * /|\ flip-candidate edge: (v, v2) + * / | \ + * / | \ + * / | \ + * new vertex--> v | v2 + * \ | / + * \ | / + * \ | / + * \|/ + * v1 + */ + const triangle_t& tOpo = triangles[iTopo]; + const std::uint32_t i = get_opposite_vertex_index(tOpo, iT); + const std::uint32_t iV2 = tOpo.vertices[i]; + const std::uint32_t iV1 = tOpo.vertices[cw(i)]; + const std::uint32_t iV3 = tOpo.vertices[ccw(i)]; + + // flip not needed if the original edge is fixed + if (fixedEdges.count(edge_t(iV1, iV3))) + return false; + + return check_is_edgeflip_needed(v, iV, iV1, iV2, iV3); +} + +template +std::uint32_t triangulator_t::walk_triangles( + const std::uint32_t startVertex, + const vec2_& pos) const +{ + // begin walk in search of triangle at pos + std::uint32_t currTri = vertTris[startVertex][0]; + + std::unordered_set visited; + + bool found = false; + while (!found) { + + const triangle_t& t = triangles[currTri]; + found = true; + // stochastic offset to randomize which edge we check first + const std::uint32_t offset(detail::randGenerator() % 3); + + for (std::uint32_t i_(0); i_ < std::uint32_t(3); ++i_) { + + const std::uint32_t i((i_ + offset) % 3); + const vec2_& vStart = vertices[t.vertices[i]]; + const vec2_& vEnd = vertices[t.vertices[ccw(i)]]; + const point_to_line_location_t::Enum edgeCheck = locate_point_wrt_line(pos, vStart, vEnd); + + if (edgeCheck == point_to_line_location_t::RIGHT_SIDE && t.neighbors[i] != null_neighbour && visited.insert(t.neighbors[i]).second) { + found = false; + currTri = t.neighbors[i]; + break; + } + } + } + return currTri; +} + +/* Flip edge between T and Topo: + * + * v4 | - old edge + * /|\ ~ - new edge + * / | \ + * n3 / T' \ n4 + * / | \ + * / | \ + * T -> v1~~~~~~~~~v3 <- Topo + * \ | / + * \ | / + * n1 \Topo'/ n2 + * \ | / + * \|/ + * v2 + */ +template +void triangulator_t::do_edgeflip( + const std::uint32_t iT, + const std::uint32_t iTopo) +{ + triangle_t& t = triangles[iT]; + triangle_t& tOpo = triangles[iTopo]; + + const std::array& triNs = t.neighbors; + const std::array& triOpoNs = tOpo.neighbors; + const std::array& triVs = t.vertices; + const std::array& triOpoVs = tOpo.vertices; + + // find vertices and neighbors + std::uint32_t i = get_opposite_vertex_index(t, iTopo); + + const std::uint32_t v1 = triVs[i]; + const std::uint32_t v2 = triVs[ccw(i)]; + const std::uint32_t n1 = triNs[i]; + const std::uint32_t n3 = triNs[cw(i)]; + + i = get_opposite_vertex_index(tOpo, iT); + + const std::uint32_t v3 = triOpoVs[i]; + const std::uint32_t v4 = triOpoVs[ccw(i)]; + const std::uint32_t n4 = triOpoNs[i]; + const std::uint32_t n2 = triOpoNs[cw(i)]; + + // change vertices and neighbors + using detail::arr3; + + t = triangle_t::make(arr3(v4, v1, v3), arr3(n3, iTopo, n4)); + tOpo = triangle_t::make(arr3(v2, v3, v1), arr3(n2, iT, n1)); + + // adjust neighboring triangles and vertices + change_neighbour(n1, iT, iTopo); + change_neighbour(n4, iTopo, iT); + + // only adjust adjacent triangles if triangulation is not finalized: + // can happen when called from outside on an already finalized triangulation + if (!is_finalized()) { + + add_adjacent_triangle(v1, iTopo); + add_adjacent_triangle(v3, iT); + + remove_adjacent_triangle(v2, iT); + remove_adjacent_triangle(v4, iTopo); + } +} + +template +void triangulator_t::change_neighbour( + const std::uint32_t iT, + const std::uint32_t oldNeighbor, + const std::uint32_t newNeighbor) +{ + if (iT == null_neighbour) { + return; + } + + triangle_t& t = triangles[iT]; + + t.neighbors[get_neighbour_index(t, oldNeighbor)] = newNeighbor; +} + +template +void triangulator_t::add_adjacent_triangle( + const std::uint32_t iVertex, + const std::uint32_t iTriangle) +{ + vertTris[iVertex].push_back(iTriangle); +} + +template +void triangulator_t::add_adjacent_triangles( + const std::uint32_t iVertex, + const std::uint32_t iT1, + const std::uint32_t iT2, + const std::uint32_t iT3) +{ + std::vector& vTris = vertTris[iVertex]; + + vTris.reserve(vTris.size() + 3); + + vTris.push_back(iT1); + vTris.push_back(iT2); + vTris.push_back(iT3); +} + +template +void triangulator_t::add_adjacent_triangles( + const std::uint32_t iVertex, + const std::uint32_t iT1, + const std::uint32_t iT2, + const std::uint32_t iT3, + const std::uint32_t iT4) +{ + std::vector& vTris = vertTris[iVertex]; + + vTris.reserve(vTris.size() + 4); + + vTris.push_back(iT1); + vTris.push_back(iT2); + vTris.push_back(iT3); + vTris.push_back(iT4); +} + +template +void triangulator_t::remove_adjacent_triangle( + const std::uint32_t iVertex, + const std::uint32_t iTriangle) +{ + std::vector& tris = vertTris[iVertex]; + tris.erase(std::find(tris.begin(), tris.end(), iTriangle)); +} + +template +std::uint32_t triangulator_t::triangulate_pseudo_polygon( + const std::uint32_t ia, + const std::uint32_t ib, + const std::vector::const_iterator pointsFirst, + const std::vector::const_iterator pointsLast) +{ + if (pointsFirst == pointsLast) + return pseudo_polygon_outer_triangle(ia, ib); + + // Find delaunay point + const std::uint32_t ic = find_delaunay_point(ia, ib, pointsFirst, pointsLast); + // Find pseudopolygons split by the delaunay point + std::vector::const_iterator newLast = pointsFirst; + + while (*newLast != ic) { + ++newLast; + } + + const std::vector::const_iterator newFirst = newLast + 1; + // triangulate splitted pseudo-polygons + const std::uint32_t iT2 = triangulate_pseudo_polygon(ic, ib, newFirst, pointsLast); + const std::uint32_t iT1 = triangulate_pseudo_polygon(ia, ic, pointsFirst, newLast); + // add new triangle + const triangle_t t = { { ia, ib, ic }, { null_neighbour, iT2, iT1 } }; + const std::uint32_t iT = add_triangle(t); + + // adjust neighboring triangles and vertices + if (iT1 != null_neighbour) { + if (pointsFirst == newLast) { + change_neighbour(iT1, ia, ic, iT); + } else { + triangles[iT1].neighbors[0] = iT; + } + } + if (iT2 != null_neighbour) { + if (newFirst == pointsLast) { + change_neighbour(iT2, ic, ib, iT); + } else { + triangles[iT2].neighbors[0] = iT; + } + } + + add_adjacent_triangle(ia, iT); + add_adjacent_triangle(ib, iT); + add_adjacent_triangle(ic, iT); + + return iT; +} + +template +std::uint32_t triangulator_t::find_delaunay_point( + const std::uint32_t ia, + const std::uint32_t ib, + const std::vector::const_iterator pointsFirst, + const std::vector::const_iterator pointsLast) const +{ + MCUT_ASSERT(pointsFirst != pointsLast); + + const vec2_& a = vertices[ia]; + const vec2_& b = vertices[ib]; + + std::uint32_t ic = *pointsFirst; + vec2_ c = vertices[ic]; + + for (std::vector::const_iterator it = pointsFirst + 1; it != pointsLast; ++it) { + + const vec2_ v = vertices[*it]; + + if (!check_is_in_circumcircle(v, a, b, c)) + { + continue; + } + + ic = *it; + c = vertices[ic]; + } + return ic; +} + +template +std::uint32_t triangulator_t::pseudo_polygon_outer_triangle( + const std::uint32_t ia, + const std::uint32_t ib) const +{ + + const std::vector& aTris = vertTris[ia]; + const std::vector& bTris = vertTris[ib]; + + for (std::vector::const_iterator it = aTris.begin(); it != aTris.end(); ++it) + { + if (std::find(bTris.begin(), bTris.end(), *it) != bTris.end()) + { + return *it; + } + } + + return null_neighbour; +} + +template +void triangulator_t::insert_vertices( + const std::vector>& newVertices) +{ + return insert_vertices( + newVertices.begin(), newVertices.end(), get_x_coord_vec2d, get_y_coord_vec2d); +} + +template +bool triangulator_t::is_finalized() const +{ + return vertTris.empty() && !vertices.empty(); +} + +} // namespace cdt +#endif + +#endif // header-guard diff --git a/src/mcut/include/mcut/internal/cdt/utils.h b/src/mcut/include/mcut/internal/cdt/utils.h new file mode 100644 index 0000000000..3e1ba3270d --- /dev/null +++ b/src/mcut/include/mcut/internal/cdt/utils.h @@ -0,0 +1,486 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef _CDT_UTILITIES_H_ +#define _CDT_UTILITIES_H_ + +#include "mcut/internal/math.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace cdt { + +/// X- coordinate getter for vec2d_t +template +const T& get_x_coord_vec2d(const vec2_& v) +{ + return v.x(); +} + +/// Y-coordinate getter for vec2d_t +template +const T& get_y_coord_vec2d(const vec2_& v) +{ + return v.y(); +} + +/// If two 2D vectors are exactly equal +template +bool operator==(const vec2_& lhs, const vec2_& rhs) +{ + return lhs.x() == rhs.x() && lhs.y() == rhs.y(); +} + +/// Constant representing no valid neighbor for a triangle +const static std::uint32_t null_neighbour(std::numeric_limits::max()); +/// Constant representing no valid vertex for a triangle +const static std::uint32_t null_vertex(std::numeric_limits::max()); + +/// 2D bounding box +template +struct box2d_t { + vec2_ min; ///< min box corner + vec2_ max; ///< max box corner + + /// Envelop box around a point + void expand_with_point(const vec2_& p) + { + expand_with_point(p.x(), p.y()); + } + /// Envelop box around a point with given coordinates + void expand_with_point(const T x, const T y) + { + min.x() = std::min(x, min.x()); + max.x() = std::max(x, max.x()); + min.y() = std::min(y, min.y()); + max.y() = std::max(y, max.y()); + } +}; + +/// Bounding box of a collection of custom 2D points given coordinate getters +template < + typename T, + typename TVertexIter, + typename TGetVertexCoordX, + typename TGetVertexCoordY> +box2d_t expand_with_points( + TVertexIter first, + TVertexIter last, + TGetVertexCoordX get_x_coord, + TGetVertexCoordY get_y_coord) +{ + const T max = std::numeric_limits::max(); + box2d_t box = { { max, max }, { -max, -max } }; + + for (; first != last; ++first) { + box.expand_with_point(get_x_coord(*first), get_y_coord(*first)); + } + return box; +} + +/// Bounding box of a collection of 2D points +template +box2d_t expand_with_points(const std::vector>& vertices); + +/// edge_t connecting two vertices: vertex with smaller index is always first +/// \note: hash edge_t is specialized at the bottom +struct edge_t { + + edge_t(std::uint32_t iV1, std::uint32_t iV2) + : m_vertices( + iV1 < iV2 ? std::make_pair(iV1, iV2) : std::make_pair(iV2, iV1)) + { + } + + inline bool operator==(const edge_t& other) const + { + return m_vertices == other.m_vertices; + } + + inline bool operator!=(const edge_t& other) const + { + return !(this->operator==(other)); + } + + inline std::uint32_t v1() const + { + return m_vertices.first; + } + + inline std::uint32_t v2() const + { + return m_vertices.second; + } + + inline const std::pair& verts() const + { + return m_vertices; + } + +private: + std::pair m_vertices; +}; + +/// Get edge first vertex +inline std::uint32_t edge_get_v1(const edge_t& e) +{ + return e.v1(); +} + +/// Get edge second vertex +inline std::uint32_t edge_get_v2(const edge_t& e) +{ + return e.v2(); +} + +/// Get edge second vertex +inline edge_t edge_make(std::uint32_t iV1, std::uint32_t iV2) +{ + return edge_t(iV1, iV2); +} + +/// triangulator_t triangle (CCW winding) +/* Counter-clockwise winding: + v3 + /\ + n3/ \n2 + /____\ + v1 n1 v2 */ +struct triangle_t { + + std::array vertices; + std::array neighbors; + + /** + * Factory method + * @note needed for c++03 compatibility (no uniform initialization + * available) + */ + static triangle_t + make(const std::array& vertices, const std::array& neighbors) + { + triangle_t t = { vertices, neighbors }; + return t; + } +}; + +/// Location of point on a triangle +struct point_to_triangle_location_t { + /// Enum + enum Enum { + INSIDE, + OUTSIDE, + ON_1ST_EDGE, + ON_2ND_EDGE, + ON_3RD_EDGE, + }; +}; + +/// Relative location of point to a line +struct point_to_line_location_t { + /// Enum + enum Enum { + LEFT_SIDE, + RIGHT_SIDE, + COLLINEAR, + }; +}; + +} // namespace cdt + +#ifndef CDT_USE_AS_COMPILED_LIBRARY + +#include + +namespace cdt { + +//***************************************************************************** +// box2d_t +//***************************************************************************** +template +box2d_t expand_with_points(const std::vector>& vertices) +{ + return expand_with_points( + vertices.begin(), vertices.end(), get_x_coord_vec2d, get_y_coord_vec2d); +} + +//***************************************************************************** +// Utility functions +//***************************************************************************** + +/// Advance vertex or neighbor index counter-clockwise +inline std::uint32_t ccw(std::uint32_t i) +{ + return std::uint32_t((i + 1) % 3); +} + +/// Advance vertex or neighbor index clockwise +inline std::uint32_t cw(std::uint32_t i) +{ + return std::uint32_t((i + 2) % 3); +} + +/// Check if location is classified as on any of three edges +inline bool check_on_edge(const point_to_triangle_location_t::Enum location) +{ + return location > point_to_triangle_location_t::OUTSIDE; +} + +/// Neighbor index from a on-edge location +/// \note Call only if located on the edge! +inline std::uint32_t edge_neighbour(const point_to_triangle_location_t::Enum location) +{ + assert(location >= point_to_triangle_location_t::ON_1ST_EDGE); + return static_cast(location - point_to_triangle_location_t::ON_1ST_EDGE); +} + +#if 0 +/// Orient p against line v1-v2 2D: robust geometric predicate +template +T orient2D(const vec2_& p, const vec2_& v1, const vec2_& v2) +{ + return orient2d(v1.x(), v1.y(), v2.x(), v2.y(), p.x(), p.y()); +} +#endif + +/// Classify value of orient2d predicate +template +point_to_line_location_t::Enum +classify_orientation(const T orientation, const T orientationTolerance = T(0)) +{ + if (orientation < -orientationTolerance) + return point_to_line_location_t::RIGHT_SIDE; + if (orientation > orientationTolerance) + return point_to_line_location_t::LEFT_SIDE; + return point_to_line_location_t::COLLINEAR; +} + +/// Check if point lies to the left of, to the right of, or on a line +template +point_to_line_location_t::Enum locate_point_wrt_line( + const vec2_& p, + const vec2_& v1, + const vec2_& v2, + const T orientationTolerance = T(0)) +{ + return classify_orientation(orient2d(p, v1, v2), orientationTolerance); +} + +/// Check if point a lies inside of, outside of, or on an edge of a triangle +template +point_to_triangle_location_t::Enum locate_point_wrt_triangle( + const vec2_& p, + const vec2_& v1, + const vec2_& v2, + const vec2_& v3) +{ + point_to_triangle_location_t::Enum result = point_to_triangle_location_t::INSIDE; + point_to_line_location_t::Enum edgeCheck = locate_point_wrt_line(p, v1, v2); + if (edgeCheck == point_to_line_location_t::RIGHT_SIDE) + return point_to_triangle_location_t::OUTSIDE; + if (edgeCheck == point_to_line_location_t::COLLINEAR) + result = point_to_triangle_location_t::ON_1ST_EDGE; + edgeCheck = locate_point_wrt_line(p, v2, v3); + if (edgeCheck == point_to_line_location_t::RIGHT_SIDE) + return point_to_triangle_location_t::OUTSIDE; + if (edgeCheck == point_to_line_location_t::COLLINEAR) + result = point_to_triangle_location_t::ON_2ND_EDGE; + edgeCheck = locate_point_wrt_line(p, v3, v1); + if (edgeCheck == point_to_line_location_t::RIGHT_SIDE) + return point_to_triangle_location_t::OUTSIDE; + if (edgeCheck == point_to_line_location_t::COLLINEAR) + result = point_to_triangle_location_t::ON_3RD_EDGE; + return result; +} + +/// Opposed neighbor index from vertex index +inline std::uint32_t get_opposite_neighbour_from_vertex(const std::uint32_t vertIndex) +{ + MCUT_ASSERT(vertIndex < 3); + + if (vertIndex == std::uint32_t(0)) + return std::uint32_t(1); + if (vertIndex == std::uint32_t(1)) + return std::uint32_t(2); + if (vertIndex == std::uint32_t(2)) + return std::uint32_t(0); + throw std::runtime_error("Invalid vertex index"); +} +/// Opposed vertex index from neighbor index +inline std::uint32_t opposite_vertex_from_neighbour(const std::uint32_t neighborIndex) +{ + if (neighborIndex == std::uint32_t(0)) + return std::uint32_t(2); + if (neighborIndex == std::uint32_t(1)) + return std::uint32_t(0); + if (neighborIndex == std::uint32_t(2)) + return std::uint32_t(1); + throw std::runtime_error("Invalid neighbor index"); +} + +/// Index of triangle's neighbor opposed to a vertex +inline std::uint32_t +opposite_triangle_index(const triangle_t& tri, const std::uint32_t iVert) +{ + for (std::uint32_t vi = std::uint32_t(0); vi < std::uint32_t(3); ++vi) + if (iVert == tri.vertices[vi]) + return get_opposite_neighbour_from_vertex(vi); + throw std::runtime_error("Could not find opposed triangle index"); +} + +/// Index of triangle's neighbor opposed to an edge +inline std::uint32_t opposite_triangle_index( + const triangle_t& tri, + const std::uint32_t iVedge1, + const std::uint32_t iVedge2) +{ + for (std::uint32_t vi = std::uint32_t(0); vi < std::uint32_t(3); ++vi) { + const std::uint32_t iVert = tri.vertices[vi]; + if (iVert != iVedge1 && iVert != iVedge2) + return get_opposite_neighbour_from_vertex(vi); + } + throw std::runtime_error("Could not find opposed-to-edge triangle index"); +} + +/// Index of triangle's vertex opposed to a triangle +inline std::uint32_t +get_opposite_vertex_index(const triangle_t& tri, const std::uint32_t iTopo) +{ + for (std::uint32_t ni = std::uint32_t(0); ni < std::uint32_t(3); ++ni) + if (iTopo == tri.neighbors[ni]) + return opposite_vertex_from_neighbour(ni); + throw std::runtime_error("Could not find opposed vertex index"); +} + +/// If triangle has a given neighbor return neighbor-index, throw otherwise +inline std::uint32_t +get_neighbour_index(const triangle_t& tri, std::uint32_t iTnbr) +{ + for (std::uint32_t ni = std::uint32_t(0); ni < std::uint32_t(3); ++ni) + if (iTnbr == tri.neighbors[ni]) + return ni; + throw std::runtime_error("Could not find neighbor triangle index"); +} + +/// If triangle has a given vertex return vertex-index, throw otherwise +inline std::uint32_t get_vertex_index(const triangle_t& tri, const std::uint32_t iV) +{ + for (std::uint32_t i = std::uint32_t(0); i < std::uint32_t(3); ++i) + if (iV == tri.vertices[i]) + return i; + throw std::runtime_error("Could not find vertex index in triangle"); +} + +/// Given triangle and a vertex find opposed triangle +inline std::uint32_t +get_opposite_triangle_index(const triangle_t& tri, const std::uint32_t iVert) +{ + return tri.neighbors[opposite_triangle_index(tri, iVert)]; +} + +/// Given two triangles, return vertex of first triangle opposed to the second +inline std::uint32_t +get_opposed_vertex_index(const triangle_t& tri, std::uint32_t iTopo) +{ + return tri.vertices[get_opposite_vertex_index(tri, iTopo)]; +} + +/// Test if point lies in a circumscribed circle of a triangle +template +bool check_is_in_circumcircle( + const vec2_& p, + const vec2_& v1, + const vec2_& v2, + const vec2_& v3) +{ + const double p_[2] = { static_cast(p.x()), static_cast(p.y()) }; + const double v1_[2] = { static_cast(v1.x()), static_cast(v1.y()) }; + const double v2_[2] = { static_cast(v2.x()), static_cast(v2.y()) }; + const double v3_[2] = { static_cast(v3.x()), static_cast(v3.y()) }; + + return ::incircle(v1_, v2_, v3_, p_) > T(0); +} + +/// Test if two vertices share at least one common triangle +inline bool check_vertices_share_edge(const std::vector& aTris, const std::vector& bTris) +{ + for (std::vector::const_iterator it = aTris.begin(); it != aTris.end(); ++it) + if (std::find(bTris.begin(), bTris.end(), *it) != bTris.end()) + return true; + return false; +} + +template +T get_square_distance(const T ax, const T ay, const T bx, const T by) +{ + const T dx = bx - ax; + const T dy = by - ay; + return dx * dx + dy * dy; +} + +template +T distance(const T ax, const T ay, const T bx, const T by) +{ + return std::sqrt(get_square_distance(ax, ay, bx, by)); +} + +template +T distance(const vec2_& a, const vec2_& b) +{ + return distance(a.x(), a.y(), b.x(), b.y()); +} + +template +T get_square_distance(const vec2_& a, const vec2_& b) +{ + return get_square_distance(a.x(), a.y(), b.x(), b.y()); +} + +} // namespace cdt + +#endif + +//***************************************************************************** +// Specialize hash functions +//***************************************************************************** +namespace std { +/// edge_t hasher +template <> +struct hash { + /// Hash operator + std::size_t operator()(const cdt::edge_t& e) const + { + return get_hashed_edge_index(e); + } + +private: + static void combine_hash_values(std::size_t& seed, const std::uint32_t& key) + { + seed ^= std::hash()(key) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + } + static std::size_t get_hashed_edge_index(const cdt::edge_t& e) + { + const std::pair& vv = e.verts(); + std::size_t seed1(0); + combine_hash_values(seed1, vv.first); + combine_hash_values(seed1, vv.second); + std::size_t seed2(0); + combine_hash_values(seed2, vv.second); + combine_hash_values(seed2, vv.first); + return std::min(seed1, seed2); + } +}; +} // namespace std/boost + +#endif // header guard diff --git a/src/mcut/include/mcut/internal/frontend.h b/src/mcut/include/mcut/internal/frontend.h new file mode 100644 index 0000000000..bc27530e9a --- /dev/null +++ b/src/mcut/include/mcut/internal/frontend.h @@ -0,0 +1,751 @@ +/** + * 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: + * (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 + */ + +/** + * @file mcut.h + * @author Floyd M. Chitalu + * @date 11 July 2022 + * + * @brief API-function implementations. + * + * NOTE: This header file defines the pre- and post-cutting processing of mesh + * data, which includes any intermediate correctons/modifications to the user's + * input meshes like 'polygon partitioning'. + * + */ + +#ifndef _FRONTEND_H_ +#define _FRONTEND_H_ + +#include "mcut/mcut.h" + +#include +#include +#include +#include + +#include "mcut/internal/tpool.h" + +#include "mcut/internal/kernel.h" + +/* +std::invalid_argument: related to the input parameters +std::runtime_error: system runtime error e.g. out of memory +std::logic_error: a bug caught through an assertion failure +std::exception: unknown error source e.g. probably another bug +*/ +#define CATCH_POSSIBLE_EXCEPTIONS(logstr) \ + catch (std::invalid_argument & e0) \ + { \ + logstr = e0.what(); \ + return_value = McResult::MC_INVALID_VALUE; \ + } \ + catch (std::runtime_error & e1) \ + { \ + logstr = e1.what(); \ + return_value = McResult::MC_INVALID_OPERATION; \ + } \ + catch (std::logic_error & e2) \ + { \ + logstr = e2.what(); \ + return_value = McResult::MC_RESULT_MAX_ENUM; \ + } \ + catch (std::exception & e3) \ + { \ + logstr = e3.what(); \ + return_value = McResult::MC_RESULT_MAX_ENUM; \ + } + +extern thread_local std::string per_thread_api_log_str; // frontend.cpp + +extern "C" void create_context_impl( + McContext* pContext, McFlags flags, uint32_t num_helper_threads) noexcept(false); + +extern "C" void debug_message_callback_impl( + McContext context, + pfn_mcDebugOutput_CALLBACK cb, + const McVoid* userParam) noexcept(false); + +extern "C" void get_debug_message_log_impl(McContext context, + McUint32 count, McSize bufSize, + McDebugSource* sources, McDebugType* types, McDebugSeverity* severities, + McSize* lengths, McChar* messageLog, McUint32& numFetched); + +extern "C" void debug_message_control_impl( + McContext context, + McDebugSource source, + McDebugType type, + McDebugSeverity severity, + bool enabled) noexcept(false); + +extern "C" void get_info_impl( + const McContext context, + McFlags info, + McSize bytes, + McVoid* pMem, + McSize* pNumBytes) noexcept(false); + +extern "C" void create_user_event_impl(McEvent* event, McContext context); + +extern "C" void set_user_event_status_impl(McEvent event, McInt32 execution_status); + +extern "C" void get_event_info_impl( + const McEvent event, + McFlags info, + McSize bytes, + McVoid* pMem, + McSize* pNumBytes) noexcept(false); + +extern "C" void set_event_callback_impl( + McEvent eventHandle, + pfn_McEvent_CALLBACK eventCallback, + McVoid* data); + +extern "C" void wait_for_events_impl( + uint32_t numEventsInWaitlist, + const McEvent* pEventWaitList, + McResult& runtimeStatusFromAllPrecedingEvents) noexcept(false); + +extern "C" void dispatch_impl( + McContext context, + McFlags flags, + const McVoid* pSrcMeshVertices, + const uint32_t* pSrcMeshFaceIndices, + const uint32_t* pSrcMeshFaceSizes, + uint32_t numSrcMeshVertices, + uint32_t numSrcMeshFaces, + const McVoid* pCutMeshVertices, + const uint32_t* pCutMeshFaceIndices, + const uint32_t* pCutMeshFaceSizes, + uint32_t numCutMeshVertices, + uint32_t numCutMeshFaces, + uint32_t numEventsInWaitlist, + const McEvent* pEventWaitList, + McEvent* pEvent) noexcept(false); + +extern "C" void get_connected_components_impl( + const McContext context, + const McConnectedComponentType connectedComponentType, + const uint32_t numEntries, + McConnectedComponent* pConnComps, + uint32_t* numConnComps, + uint32_t numEventsInWaitlist, + const McEvent* pEventWaitList, + McEvent* pEvent) noexcept(false); + +extern "C" void get_connected_component_data_impl( + const McContext context, + const McConnectedComponent connCompId, + McFlags flags, + McSize bytes, + McVoid* pMem, + McSize* pNumBytes, + uint32_t numEventsInWaitlist, + const McEvent* pEventWaitList, + McEvent* pEvent) noexcept(false); + +extern "C" void release_connected_components_impl( + const McContext context, + uint32_t numConnComps, + const McConnectedComponent* pConnComps) noexcept(false); + +extern "C" void release_context_impl( + McContext context) noexcept(false); + +extern "C" void release_events_impl(uint32_t numEvents, const McEvent* pEvents); + +// base struct from which other structs represent connected components inherit +struct connected_component_t { + virtual ~connected_component_t() {}; + McConnectedComponentType type = (McConnectedComponentType)0; + McConnectedComponent m_user_handle = MC_NULL_HANDLE; + // array_mesh_t indexArrayMesh; + // hmesh_t mesh; + std::shared_ptr kernel_hmesh_data; + + // + std::shared_ptr< // + std::unordered_map< // + fd_t /*child face*/, + fd_t /*parent face in the [user-provided] source mesh*/ + > // + > + source_hmesh_child_to_usermesh_birth_face; // fpPartitionChildFaceToCorrespondingInputSrcMeshFace + std::shared_ptr< // + std::unordered_map< // + fd_t /*child face*/, + fd_t /*parent face in the [user-provided] cut mesh*/ + >> + cut_hmesh_child_to_usermesh_birth_face; // fpPartitionChildFaceToCorrespondingInputCutMeshFace + // descriptors and coordinates of new vertices that are added into an input mesh (source mesh or cut mesh) + // in order to carry out partitioning + std::shared_ptr> source_hmesh_new_poly_partition_vertices; // addedFpPartitioningVerticesOnCorrespondingInputSrcMesh + std::shared_ptr> cut_hmesh_new_poly_partition_vertices; // addedFpPartitioningVerticesOnCorrespondingInputCutMesh + uint32_t internal_sourcemesh_vertex_count; // init from source_hmesh.number_of_vertices() + uint32_t client_sourcemesh_vertex_count; // init from numSrcMeshVertices + uint32_t internal_sourcemesh_face_count; // init from source_hmesh.number_of_faces() + uint32_t client_sourcemesh_face_count; // init from source_hmesh_face_count OR numSrcMeshFaces + // Stores the contiguous array of unsigned integers that define + // a triangulation of all [non-triangle faces] of the connected component. + // This vector is only populated if client invokes mcGetConnectedComponnentData + // with flag MC_CONNECTED_COMPONENT_DATA_FACE_TRIANGULATION and has the effect of + // triangulating every non-triangle face in the connected component. + std::vector cdt_index_cache; + bool cdt_index_cache_initialized = false; + // stores the mapping between a CDT triangle in the connected component and + // the original "birth-face" in an input mesh (source mesh or cut mesh) + std::vector cdt_face_map_cache; + bool cdt_face_map_cache_initialized = false; +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + // Stores the number of vertices per face of CC. This is an optimization + // because there is a possibility that face-sizes may (at-minimum) be queried + // twice by user. The first case is during the populating (i.e. second) call to the API + // mcGetConnectedComponentData(..., MC_CONNECTED_COMPONENT_DATA_FACE_SIZE, ...); + // The second case is during the populating (i.e. second) call to the API + // mcGetConnectedComponentData(..., MC_CONNECTED_COMPONENT_DATA_FACE, ...);. + // The key detail here is that the second case requires knowledge of the + // number of vertices in each face in order to know how to schedule parallel + // work with prefix-sums etc.. Thus, the optimization is useful only if + // building MCUT with multi-threading + std::vector face_sizes_cache; + bool face_sizes_cache_initialized = false; + // see documentation of face_sizes_cache above + // Similar concepts but applied to MC_CONNECTED_COMPONENT_DATA_FACE_ADJACENT_FACE + std::vector face_adjacent_faces_size_cache; + bool face_adjacent_faces_size_cache_initialized = false; +#endif // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) +}; + +// struct representing a fragment +struct fragment_cc_t : public connected_component_t { + McFragmentLocation fragmentLocation = (McFragmentLocation)0; + McFragmentSealType srcMeshSealType = (McFragmentSealType)0; + McPatchLocation patchLocation = (McPatchLocation)0; +}; + +// struct representing a patch +struct patch_cc_t : public connected_component_t { + McPatchLocation patchLocation = (McPatchLocation)0; +}; + +// struct representing a seam +struct seam_cc_t : public connected_component_t { + McSeamOrigin origin = (McSeamOrigin)0; +}; + +// struct representing an input (user provided mesh) +struct input_cc_t : public connected_component_t { + McInputOrigin origin = (McInputOrigin)0; +}; + +struct event_t { + std::future m_future; // used to wait on event + // used to synchronise access to variables associated with the callback + // function. + // This also also allows us to overcome the edgecase that mcSetEventCallback + // is called after the task associated with an event has been completed, + // in which case the new callback will be invoked immediately. + // see "set_callback_data()" below + std::mutex m_callback_mutex; + struct { + // optional user callback, which is invoked when associated task is finished + pfn_McEvent_CALLBACK m_fn_ptr; + // pointer passed to user provided callback function + McVoid* m_data_ptr; + // atomic boolean flag indicating whether the callback associated with event + // object has been called + std::atomic m_invoked; + } m_callback_info; + // atomic boolean flag indicating whether the task associated with event + // object has completed running + std::atomic m_finished; + McEvent m_user_handle; // handle used by client app to reference this event object + // the Manager thread which was assigned the task of managing the task associated with this event object. + std::atomic m_responsible_thread_id; + std::atomic m_runtime_exec_status; // API return code associated with respective task (for user to query) + std::atomic m_timestamp_submit; + std::atomic m_timestamp_start; + std::atomic m_timestamp_end; + std::atomic m_command_exec_status; + bool m_profiling_enabled; + McCommandType m_command_type; + // A callable object that also holds an std::future. Its purpose is to emulate + // the internal representation of an API task, where this time the task is actually + // a user command since this pointer is define ONLY for user events. + // The std::future object of the packaged task is used to initialize m_future + // when this event is a user event. This our internal mechanism allowing for + // API command to be able to effectively wait on user events. + std::unique_ptr> m_user_API_command_task_emulator; + McContext m_context; + event_t() + : m_user_handle(MC_NULL_HANDLE) + , m_responsible_thread_id(UINT32_MAX) + , m_runtime_exec_status(MC_NO_ERROR) + , m_timestamp_submit(0) + , m_timestamp_start(0) + , m_timestamp_end(0) + , m_command_exec_status(MC_RESULT_MAX_ENUM) + , m_profiling_enabled(true) + , m_command_type(MC_COMMAND_UKNOWN) + , m_user_API_command_task_emulator(nullptr) + , m_context(nullptr) + { + log_msg("[MCUT] Create event " << this); + + m_callback_info.m_fn_ptr = nullptr; + m_callback_info.m_data_ptr = nullptr; + m_finished.store(false); + m_callback_info.m_invoked.store(true); // so that we do not call a null pointer/needless invoke the callback in the destructor + } + + ~event_t() + { + + if (m_callback_info.m_invoked.load() == false && m_callback_info.m_fn_ptr != nullptr && m_runtime_exec_status.load() == MC_NO_ERROR) { + MCUT_ASSERT(m_user_handle != MC_NULL_HANDLE); + (*(m_callback_info.m_fn_ptr))(m_user_handle, m_callback_info.m_data_ptr); + } + + log_msg("[MCUT] Destroy event " << this << "(" << m_user_handle << ")"); + } + + inline std::size_t get_time_since_epoch() + { + return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + } + + inline void log_submit_time() + { + if (m_profiling_enabled) { + this->m_timestamp_submit.store(get_time_since_epoch()); + } + // TODO: use specific acquire-release semantics + // see e.g.: https://stackoverflow.com/questions/13632344/understanding-c11-memory-fences + m_command_exec_status.store(McEventCommandExecStatus::MC_SUBMITTED); + } + + inline void log_start_time() + { + if (m_profiling_enabled) { + this->m_timestamp_start.store(get_time_since_epoch()); + } + m_command_exec_status = McEventCommandExecStatus::MC_RUNNING; + } + + // logs time and sets m_command_exec_status to MC_COMPLETE + inline void log_end_time() + { + if (m_profiling_enabled) { + this->m_timestamp_end.store(get_time_since_epoch()); + } + m_command_exec_status.store(McEventCommandExecStatus::MC_COMPLETE); + } + + // thread-safe function to set the callback function for an event object + void set_callback_data(McEvent handle, pfn_McEvent_CALLBACK fn_ptr, McVoid* data_ptr) + { + std::lock_guard lock(m_callback_mutex); // exclusive access + + m_user_handle = handle; + m_callback_info.m_fn_ptr = fn_ptr; + m_callback_info.m_data_ptr = data_ptr; + m_callback_info.m_invoked.store(false); + + if (m_finished.load() == true) { // see mutex documentation + // immediately invoke the callback + (*(m_callback_info.m_fn_ptr))(m_user_handle, m_callback_info.m_data_ptr); + m_callback_info.m_invoked.store(true); + } + } + + // update the status of the event object to "finished" + void notify_task_complete(McResult exec_status) + { + m_finished = true; + m_runtime_exec_status = exec_status; + + std::lock_guard lock(m_callback_mutex); + if (m_callback_info.m_invoked.load() == false && m_callback_info.m_fn_ptr != nullptr) { + MCUT_ASSERT(m_user_handle != MC_NULL_HANDLE); + (*(m_callback_info.m_fn_ptr))(m_user_handle, m_callback_info.m_data_ptr); + m_callback_info.m_invoked.store(true); + } + } +}; + +// init in frontened.cpp +extern threadsafe_list> g_events; +extern std::atomic g_objects_counter; // a counter that is used to assign a unique value to a McObject handle that will be returned to the user +extern std::once_flag g_objects_counter_init_flag; + +// our custome deleter function for std::unique_ptr variable of an array type +template +void fn_delete_cc(connected_component_t* p) +{ + log_msg("[MCUT] Destroy connected component " << p->m_user_handle); + delete dynamic_cast(p); +} + +// struct defining the state of a context object +struct context_t { +private: + std::atomic m_done; + std::vector> m_queues; + // Master/Manager thread(s) which are responsible for running the API calls + // When a user of MCUT calls one of the APIs (e.g. mcEnqueueDispatch) the task + // of actually executing everything related to that task will be handled by + // one Manager thread. This manager thread itself will be involved in + // computing some/all part of the respective task (think of it as the "main" + // thread insofar as the API task is concerned). Some API tasks contain code + // sections that run in parallel, which is where the Manager thread will also + // submit tasks to the shared compute threadpool ("m_compute_threadpool"). + // NOTE: must be declared after "thread_pool_terminate" and "work_queues" + std::vector m_api_threads; + // join_threads m_joiner; + +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + // A pool of threads that is shared by manager threads to execute e.g. parallel + // section of MCUT API tasks (see e.g. frontend.cpp and kernel.cpp) + std::unique_ptr m_compute_threadpool; +#endif + + // The state and flag variable current used to configure the next dispatch call + McFlags m_flags = (McFlags)0; + + void api_thread_main(uint32_t thread_id) + { + log_msg("[MCUT] Launch API thread " << std::this_thread::get_id() << " (" << thread_id << ")"); + + do { + function_wrapper task; + + // We try_pop() first in case the task "producer" (API) thread + // already invoked cond_var.notify_one() of "m_queues[thread_id]"" + // BEFORE current thread first-entered this function. + if (!m_queues[thread_id].try_pop(task)) { + m_queues[thread_id].wait_and_pop(task); + } + + if (m_done) { + break; + } + + task(); + + } while (true); + + log_msg("[MCUT] Shutdown API thread " << std::this_thread::get_id() << " (" << thread_id << ")"); + } + +public: + context_t(McContext handle, McFlags flags +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + , + uint32_t num_compute_threads +#endif + ) + : m_done(false) + // , m_joiner(m_api_threads) + , m_flags(flags) + , m_user_handle(handle) + , dbgCallbackBitfieldSource(0) + , dbgCallbackBitfieldType(0) + , dbgCallbackBitfieldSeverity(0) + { + log_msg("\n[MCUT] Create context " << m_user_handle); + + try { + const uint32_t manager_thread_count = (flags & MC_OUT_OF_ORDER_EXEC_MODE_ENABLE) ? 2 : 1; + + m_queues = std::vector>(manager_thread_count); + + for (uint32_t i = 0; i < manager_thread_count; ++i) { + m_queues[i].set_done_ptr(&m_done); + m_api_threads.push_back(std::thread(&context_t::api_thread_main, this, i)); + } +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + // create the pool of compute threads. These are the worker threads that + // can be tasked with work from any manager-thread. Thus, manager threads + // share the available/user-specified compute threads. + m_compute_threadpool = std::unique_ptr(new thread_pool(num_compute_threads, manager_thread_count)); +#endif + } catch (...) { + shutdown(); + + log_msg("[MCUT] Destroy context due to exception" << m_user_handle); + throw; + } + } + + ~context_t() + { + shutdown(); + log_msg("[MCUT] Destroy context " << m_user_handle); + } + + void shutdown() + { + m_done = true; +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + m_compute_threadpool.reset(); +#endif + for (int i = (int)m_api_threads.size() - 1; i >= 0; --i) { + m_queues[i].disrupt_wait_for_data(); + if (m_api_threads[i].joinable()) { + m_api_threads[i].join(); + } + } + } + + McContext m_user_handle; + + // returns the flags which determine the runtime configuration of this context + const McFlags& get_flags() const + { + return this->m_flags; + } + +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + thread_pool& get_shared_compute_threadpool() + { + return m_compute_threadpool.get()[0]; + } +#endif + + template + McEvent prepare_and_submit_API_task(McCommandType cmdType, uint32_t numEventsInWaitlist, const McEvent* pEventWaitList, FunctionType api_fn) + { + // + // create the event object associated with the enqueued task + // + + std::shared_ptr event_ptr = std::shared_ptr(new event_t); + + MCUT_ASSERT(event_ptr != nullptr); + + g_events.push_front(event_ptr); + + event_ptr->m_user_handle = reinterpret_cast(g_objects_counter++); + event_ptr->m_profiling_enabled = (this->m_flags & MC_PROFILING_ENABLE) != 0; + event_ptr->m_command_type = cmdType; + + event_ptr->log_submit_time(); + + // List of events the enqueued task depends on + // + // local copy that will be captured by-value (user permitted to re-use pEventWaitList) + const std::vector event_waitlist(pEventWaitList, pEventWaitList + numEventsInWaitlist); + + // + // Determine which manager thread to assign the task + // + + // the id of manager thread that will be assigned the current task + uint32_t responsible_thread_id = UINT32_MAX; + + for (std::vector::const_iterator waitlist_iter = event_waitlist.cbegin(); waitlist_iter != event_waitlist.cend(); ++waitlist_iter) { + const McEvent& parent_task_event_handle = *waitlist_iter; + + const std::shared_ptr parent_task_event_ptr = g_events.find_first_if([=](std::shared_ptr e) { return e->m_user_handle == parent_task_event_handle; }); + + MCUT_ASSERT(parent_task_event_ptr != nullptr); + + const bool parent_task_is_not_finished = parent_task_event_ptr->m_finished.load() == false; + + if (parent_task_is_not_finished && parent_task_event_ptr->m_command_type != McCommandType::MC_COMMAND_USER) { + // id of manager thread, which was assigned the parent task + responsible_thread_id = parent_task_event_ptr->m_responsible_thread_id.load(); + + MCUT_ASSERT(responsible_thread_id != UINT32_MAX); + MCUT_ASSERT(responsible_thread_id < (uint32_t)m_api_threads.size()); + + break; + } + } + + const bool have_responsible_thread = responsible_thread_id != UINT32_MAX; + + if (!have_responsible_thread) { + uint32_t thread_with_empty_queue = UINT32_MAX; + + for (uint32_t i = 0; i < (uint32_t)m_api_threads.size(); ++i) { + if (m_queues[i].empty() == true) { + thread_with_empty_queue = i; + break; + } + } + + if (thread_with_empty_queue != UINT32_MAX) { + responsible_thread_id = thread_with_empty_queue; + } else { // all threads have work to do + responsible_thread_id = 0; // just pick thread 0 + } + } + + // + // Package-up the task as a synchronised operation that will wait for + // other tasks in the event_waitlist, compute the operation, and finally update + // the respective event state with the completion status. + // + + std::weak_ptr event_weak_ptr(event_ptr); + + std::packaged_task task( + [=]() { + McResult runtime_status_from_all_preceding_events = McResult::MC_NO_ERROR; + + if (!event_waitlist.empty()) { + wait_for_events_impl((uint32_t)event_waitlist.size(), &event_waitlist[0], runtime_status_from_all_preceding_events); // block until events are done + } + + // if any previous event failed then we cannot proceed with this task. + // i.e. no-Op + if (runtime_status_from_all_preceding_events != McResult::MC_NO_ERROR) { + return; + } + + MCUT_ASSERT(!event_weak_ptr.expired()); + + { + std::shared_ptr event = event_weak_ptr.lock(); + + MCUT_ASSERT(event != nullptr); + + { + McResult return_value = McResult::MC_NO_ERROR; + per_thread_api_log_str.clear(); + + event->log_start_time(); + + try { + api_fn(); // execute the API function. + } + CATCH_POSSIBLE_EXCEPTIONS(per_thread_api_log_str); // exceptions may be thrown due to runtime errors, which must be reported back to user + + if (!per_thread_api_log_str.empty()) { + + std::fprintf(stderr, "%s(...) -> %s (EventID=%p)\n", __FUNCTION__, per_thread_api_log_str.c_str(), event == nullptr ? (McEvent)0 : event->m_user_handle); + + if (return_value == McResult::MC_NO_ERROR) // i.e. problem with basic local parameter checks + { + return_value = McResult::MC_INVALID_VALUE; + } + } + + event->notify_task_complete(return_value); // updated event state to indicate task completion (lock-based) + event->log_end_time(); + } + } + }); + + event_ptr->m_future = task.get_future(); // the future we can later wait on via mcWaitForEVents + event_ptr->m_responsible_thread_id = responsible_thread_id; + + m_queues[responsible_thread_id].push(std::move(task)); // enqueue task to be executed when responsible API thread is free + + return event_ptr->m_user_handle; + } + + // the current set of connected components associated with context + threadsafe_list> connected_components; + + // McFlags dispatchFlags = (McFlags)0; + + // client/user debugging variable + // ------------------------------ + + // function pointer to user-define callback function for status/erro reporting + pfn_mcDebugOutput_CALLBACK debugCallback = nullptr; + // user provided data for callback + const McVoid* debugCallbackUserParam = nullptr; + + std::mutex debugCallbackMutex; + // controller for permmited messages based on the source of message + std::atomic dbgCallbackBitfieldSource; + // controller for permmited messages based on the type of message + std::atomic dbgCallbackBitfieldType; + // controller for permmited messages based on the severity of message + std::atomic dbgCallbackBitfieldSeverity; + bool dbgCallbackAllowAsyncCalls = true; + + void set_debug_callback_data(pfn_mcDebugOutput_CALLBACK cb, const McVoid* data_ptr) + { + std::lock_guard lguard(debugCallbackMutex); + debugCallback = cb; + debugCallbackUserParam = data_ptr; + } + + struct debug_log_msg_t { + McDebugSource source; + McDebugType type; + McDebugSeverity severity; + std::string str; + }; + + std::vector m_debug_logs; + + // function to invoke the user-provided debug call back + void dbg_cb(McDebugSource source, + McDebugType type, + unsigned int id, // unused + McDebugSeverity severity, + const std::string& message) + { + if (this->m_flags & McContextCreationFlags::MC_DEBUG) // information logged only during debug mode + { + std::unique_lock ulock(debugCallbackMutex, std::defer_lock); + + if (!dbgCallbackAllowAsyncCalls) { + ulock.lock(); + } // otherwise the callback will be invoked asynchronously + + // can we log this type of message? (based on user preferences via mcDebugMessageControl) + const bool canLog = ((uint32_t)source & dbgCallbackBitfieldSource.load(std::memory_order_acquire)) && // + ((uint32_t)type & dbgCallbackBitfieldType.load(std::memory_order_acquire)) && // + ((uint32_t)severity & dbgCallbackBitfieldSeverity.load(std::memory_order_acquire)); + + if (canLog) { + + if (debugCallback != nullptr) { // user gave us a callback function pointer + + (*debugCallback)(source, type, id, severity, message.length(), message.c_str(), debugCallbackUserParam); + + } else // write to the internal log + { + m_debug_logs.emplace_back(debug_log_msg_t()); + + debug_log_msg_t& dbg_log = m_debug_logs.back(); + + dbg_log.source = source; + dbg_log.type = type; + dbg_log.severity = severity; + dbg_log.str = message; + } + } + } + } +}; + +// list of contexts created by client/user +extern "C" threadsafe_list> g_contexts; + +#endif // #ifndef _FRONTEND_H_ \ No newline at end of file diff --git a/src/mcut/include/mcut/internal/hmesh.h b/src/mcut/include/mcut/internal/hmesh.h new file mode 100644 index 0000000000..24c742c8c2 --- /dev/null +++ b/src/mcut/include/mcut/internal/hmesh.h @@ -0,0 +1,639 @@ +/** + * 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: + * (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 + */ + +#ifndef MCUT_HALFEDGE_MESH_H_ +#define MCUT_HALFEDGE_MESH_H_ + +#include "mcut/internal/math.h" +#include "mcut/internal/utils.h" + +#include +#include +#include +#include +#include + +template +class descriptor_t_ { +public: + typedef unsigned int index_type; + descriptor_t_() { } + virtual ~descriptor_t_() { } + explicit descriptor_t_(index_type i = (std::numeric_limits::max)()) + : m_value(i) + { + } + + operator index_type() const { return m_value; } + + void reset() { m_value = (std::numeric_limits::max)(); } + + bool is_valid() const + { + index_type inf = (std::numeric_limits::max)(); + return m_value != inf; + } + + descriptor_t_& operator=(const index_type& _rhs) + { + m_value = _rhs; + return *this; + } + + descriptor_t_& operator=(const T& _rhs) const + { + m_value = _rhs.m_value; + return *this; + } + + bool operator==(const T& _rhs) const + { + return m_value == _rhs.m_value; + } + + bool operator!=(const T& _rhs) const + { + return m_value != _rhs.m_value; + } + + bool operator<(const T& _rhs) const + { + return m_value < _rhs.m_value; + } + + descriptor_t_& operator++() + { + ++m_value; + return *this; + } + + descriptor_t_& operator--() + { + --m_value; + return *this; + } + + descriptor_t_ operator++(int) + { + descriptor_t_ tmp(*this); + ++m_value; + return tmp; + } + + descriptor_t_ operator--(int) + { + descriptor_t_ tmp(*this); + --m_value; + return tmp; + } + + descriptor_t_& operator+=(std::ptrdiff_t n) + { + m_value = (unsigned int)(m_value + n); + return *this; + } + +protected: + unsigned int m_value; +}; + +class halfedge_descriptor_t : public descriptor_t_ { +public: + halfedge_descriptor_t() + : descriptor_t_(std::numeric_limits::max()) + { + } + + explicit halfedge_descriptor_t(descriptor_t_::index_type idx) + : descriptor_t_(idx) + { + } + + virtual ~halfedge_descriptor_t() + { + } +}; + +class edge_descriptor_t : public descriptor_t_ { +public: + edge_descriptor_t() + : descriptor_t_((std::numeric_limits::max)()) + { + } + + explicit edge_descriptor_t(descriptor_t_::index_type idx) + : descriptor_t_(idx) + { + } + + virtual ~edge_descriptor_t() + { + } +}; + +class face_descriptor_t : public descriptor_t_ { +public: + face_descriptor_t() + : descriptor_t_((std::numeric_limits::max)()) + { + } + + explicit face_descriptor_t(descriptor_t_::index_type idx) + : descriptor_t_(idx) + { + } + + virtual ~face_descriptor_t() + { + } +}; + +class vertex_descriptor_t : public descriptor_t_ { +public: + vertex_descriptor_t() + : descriptor_t_((std::numeric_limits::max)()) + { + } + + explicit vertex_descriptor_t(descriptor_t_::index_type idx) + : descriptor_t_(idx) + { + } + + virtual ~vertex_descriptor_t() + { + } +}; + +template +struct id_ { + typedef T type; +}; + +template +class array_iterator_t; + +struct halfedge_data_t : id_ { + vertex_descriptor_t t; // target vertex + face_descriptor_t f; // face + halfedge_descriptor_t o; // opposite halfedge + halfedge_descriptor_t n; // next halfedge + halfedge_descriptor_t p; // previous halfedge + edge_descriptor_t e; // edge + + + halfedge_data_t() + //: o(null_halfedge()), n(null_halfedge()), p(null_halfedge()), t(null_vertex()), e(null_edge()), f(null_face()) + { + } +}; + +struct edge_data_t : id_ { + halfedge_descriptor_t h; // primary halfedge (even idx) +}; + +struct face_data_t : id_ { + std::vector m_halfedges; +}; + +struct vertex_data_t : id_ { + vec3 p; // geometry coordinates + //std::vector m_faces; // ... incident to vertex // TODO: this is not needed (can be inferred from "m_halfedges") + std::vector m_halfedges; // ... which point to vertex (note: can be used to infer edges too) +}; + +typedef std::vector vertex_array_t; +typedef std::vector edge_array_t; +typedef std::vector halfedge_array_t; +typedef std::vector face_array_t; + +typedef array_iterator_t face_array_iterator_t; +typedef array_iterator_t vertex_array_iterator_t; +typedef array_iterator_t edge_array_iterator_t; +typedef array_iterator_t halfedge_array_iterator_t; + +/* + Internal mesh data structure used for cutting meshes + + Memory Management + + Memory management is semi-automatic. Memory grows as more elements are added to the structure but does not shrink when elements are removed. + When you add elements and the capacity of the underlying vector is exhausted, the vector reallocates memory. + As descriptors are basically indices, they refer to the same element after a reallocation. + When you remove an element it is only marked as removed. + Internally it is put in a free list, and when you add elements to the surface mesh, they are taken from the free list in case it is not empty. + + For all elements there is a function to obtain the number of used elements, as well as the number of used [and] removed elements. + For vertices the functions are hmesh_t::number_of_vertices() and hmesh_t::number_of_internal_vertices(), respectively. + The first function is slightly different from the free function num_vertices(const G&) of the BGL package. + + Iterators such as hmesh_t::vertex_iterator_t only enumerate elements that are not marked as deleted. +*/ +class hmesh_t { +public: + hmesh_t(); + ~hmesh_t(); + + // static member functions + // ----------------------- + + static vertex_descriptor_t null_vertex(); + static halfedge_descriptor_t null_halfedge(); + static edge_descriptor_t null_edge(); + static face_descriptor_t null_face(); + + // regular member functions + // ------------------------ + + // excluding removed elements + int number_of_vertices() const; + int number_of_edges() const; + int number_of_halfedges() const; + int number_of_faces() const; + + vertex_descriptor_t source(const halfedge_descriptor_t& h) const; + vertex_descriptor_t target(const halfedge_descriptor_t& h) const; + halfedge_descriptor_t opposite(const halfedge_descriptor_t& h) const; + halfedge_descriptor_t prev(const halfedge_descriptor_t& h) const; + halfedge_descriptor_t next(const halfedge_descriptor_t& h) const; + + void set_next(const halfedge_descriptor_t& h, const halfedge_descriptor_t& nxt); + void set_previous(const halfedge_descriptor_t& h, const halfedge_descriptor_t& prev); + + edge_descriptor_t edge(const halfedge_descriptor_t& h) const; + face_descriptor_t face(const halfedge_descriptor_t& h) const; + + vertex_descriptor_t vertex(const edge_descriptor_t e, const int v) const; + + bool is_border(const halfedge_descriptor_t h); + bool is_border(const edge_descriptor_t e); + + halfedge_descriptor_t halfedge(const edge_descriptor_t e, const int i) const; + // finds a halfedge between two vertices. Returns a default constructed halfedge descriptor, if source and target are not connected. + halfedge_descriptor_t halfedge(const vertex_descriptor_t s, const vertex_descriptor_t t, bool strict_check = false) const; + // finds an edge between two vertices. Returns a default constructed halfedge descriptor, if source and target are not connected. + edge_descriptor_t edge(const vertex_descriptor_t s, const vertex_descriptor_t t, bool strict_check = false) const; + + vertex_descriptor_t add_vertex(const vec3& point); + + vertex_descriptor_t add_vertex(const double& x, const double& y, const double& z); + // adds an edges into the mesh data structure, creating incident halfedges, and returns the + // halfedge whole target is "v1" + halfedge_descriptor_t add_edge(const vertex_descriptor_t v0, const vertex_descriptor_t v1); + face_descriptor_t add_face(const std::vector& vi); + // checks whether adding this face will violate 2-manifoldness (i.e. halfedge + // construction rules) which would lead to creating a non-manifold edge + // (one that is referenced by more than 2 faces which is illegal). + bool is_insertable(const std::vector &vi) const; + + // also disassociates (not remove) any halfedges(s) and vertices incident to face + void remove_face(const face_descriptor_t f); + // also disassociates (not remove) the halfedges(s) and vertex incident to this halfedge + void remove_halfedge(halfedge_descriptor_t h); + // also disassociates (not remove) any face(s) incident to edge via its halfedges, and also disassociates the halfedges + void remove_edge(const edge_descriptor_t e, bool remove_halfedges = true); + void remove_vertex(const vertex_descriptor_t v); + void remove_elements(); + + void reset(); + + int number_of_internal_faces() const; + int number_of_internal_edges() const; + int number_of_internal_halfedges() const; + int number_of_internal_vertices() const; + + int number_of_vertices_removed() const; + int number_of_edges_removed() const; + int number_of_halfedges_removed() const; + int number_of_faces_removed() const; + + bool is_removed(face_descriptor_t f) const; + bool is_removed(edge_descriptor_t e) const; + bool is_removed(halfedge_descriptor_t h) const; + bool is_removed(vertex_descriptor_t v) const; + + void reserve_for_additional_vertices(std::uint32_t n); + void reserve_for_additional_edges(std::uint32_t n); + void reserve_for_additional_halfedges(std::uint32_t n); + void reserve_for_additional_faces(std::uint32_t n); + void reserve_for_additional_elements(std::uint32_t additional_vertices); + + /// + + template + I get_removed_elements(id_) + { + return I(); // unused + } + + const std::vector& get_removed_elements(id_>) const; + const std::vector& get_removed_elements(id_>) const; + const std::vector& get_removed_elements(id_>) const; + const std::vector& get_removed_elements(id_>) const; + + // + template + I elements_begin_(id_) + { + return I(); // unused + } + + const vertex_array_iterator_t elements_begin_(id_, bool account_for_removed_elems = true) const; + const edge_array_iterator_t elements_begin_(id_, bool account_for_removed_elems = true) const; + const halfedge_array_iterator_t elements_begin_(id_, bool account_for_removed_elems = true) const; + const face_array_iterator_t elements_begin_(id_, bool account_for_removed_elems = true) const; + + // returns the number of removed mesh elements (vertices, edges, faces or halfedges) between [start, end) + template + uint32_t count_removed_elements_in_range(const array_iterator_t& start, const array_iterator_t& end) const + { + const long long N = (uint32_t)(end - start); // length including removed elements + MCUT_ASSERT(N >= 0); + if (N == 0) { + return 0; + } + + // raw starting ptr offset + const uint32_t start_ = (std::uint32_t)(start - elements_begin_(id_> {}, false)); + uint32_t n = 0; + + for (auto elem_descr : get_removed_elements(id_> {})) { + const uint32_t descr = (uint32_t)elem_descr; + + if (descr >= start_ && (descr <= (start_ + (uint32_t)(N - 1)))) { + ++n; + } + } + return n; + } + + const vec3& vertex(const vertex_descriptor_t& vd) const; + // returns vector of halfedges which point to vertex (i.e. "v" is their target) + const std::vector& get_halfedges_around_vertex(const vertex_descriptor_t v) const; + std::vector get_vertices_around_face(const face_descriptor_t f, uint32_t prepend_offset = 0) const; + void get_vertices_around_face(std::vector& vertex_descriptors, const face_descriptor_t f, uint32_t prepend_offset=0) const; + std::vector get_vertices_around_vertex(const vertex_descriptor_t v) const; + void get_vertices_around_vertex(std::vector& vertices_around_vertex, const vertex_descriptor_t v) const; + uint32_t get_num_vertices_around_face(const face_descriptor_t f) const; + const std::vector& get_halfedges_around_face(const face_descriptor_t f) const; + const std::vector get_faces_around_face(const face_descriptor_t f, const std::vector* halfedges_around_face_ = nullptr) const; + void get_faces_around_face( std::vector& faces_around_face, const face_descriptor_t f, const std::vector* halfedges_around_face_ = nullptr) const; + uint32_t get_num_faces_around_face(const face_descriptor_t f, const std::vector* halfedges_around_face_ = nullptr) const; + + // iterators + // --------- + + vertex_array_iterator_t vertices_begin(bool account_for_removed_elems = true) const; + vertex_array_iterator_t vertices_end() const; + edge_array_iterator_t edges_begin(bool account_for_removed_elems = true) const; + edge_array_iterator_t edges_end() const; + halfedge_array_iterator_t halfedges_begin(bool account_for_removed_elems = true) const; + halfedge_array_iterator_t halfedges_end() const; + face_array_iterator_t faces_begin(bool account_for_removed_elems = true) const; + face_array_iterator_t faces_end() const; + + const std::vector& get_removed_vertices() const; + const std::vector& get_removed_edges() const; + const std::vector& get_removed_halfedges() const; + const std::vector& get_removed_faces() const; + +private: + // member variables + // ---------------- + + std::vector m_vertices; + std::vector m_edges; + std::vector m_halfedges; + std::vector m_faces; + + // NOTE: I use std::vector because we'll have very few (typically zero) + // elements removed at a given time. In fact removal only happens during + // input-mesh face-partitioning to resolve floating polygons, which is + // rare. Maybe in the future things change... + std::vector m_faces_removed; + std::vector m_edges_removed; + std::vector m_halfedges_removed; + std::vector m_vertices_removed; + +}; // class hmesh_t { + +typedef vertex_descriptor_t vd_t; +typedef halfedge_descriptor_t hd_t; +typedef edge_descriptor_t ed_t; +typedef face_descriptor_t fd_t; + +void write_off(const char* fpath, const hmesh_t& mesh); +void read_off(hmesh_t& mesh, const char* fpath); + +template +class array_iterator_t : public V::const_iterator { + const hmesh_t* mesh_ptr; + typedef typename V::const_iterator std_iterator_base_class; + typedef typename V::value_type::type element_descriptor_type; + typename V::value_type* operator->() = delete; + +public: + array_iterator_t() + : V::const_iterator() + , mesh_ptr(nullptr) {}; + array_iterator_t(typename V::const_iterator it_, const hmesh_t* const mesh) + : V::const_iterator(it_) + , mesh_ptr(mesh) + { + } + + const hmesh_t* get_mesh_ptr() const + { + return mesh_ptr; + } + + typename V::value_type::type operator*() + { + size_t raw_index = (*this) - cbegin<>(false); + element_descriptor_type d((std::uint32_t)raw_index); + return d; + } + + // prefix increment (++i) + // increment pointer to the next valid element (i.e. we skip removed elements). + array_iterator_t& operator++() + { + bool cur_elem_is_removed = false; + bool reached_end = false; + do { + V::const_iterator::operator++(); + reached_end = (*this) == cend<>(); + cur_elem_is_removed = false; + + if (!reached_end) { + const std::size_t diff = ((*this) - cbegin>(false)); + element_descriptor_type raw_descriptor((std::uint32_t)diff); // std::distance(cbegin>(false), (*this)); // O(1) ?? + cur_elem_is_removed = mesh_ptr->is_removed(raw_descriptor); + if (!cur_elem_is_removed) { + break; + } + } + + // keep iterating until the value pointed to after the (++i) operator is a valid element + // i.e. one that is not marked removed! + + } while (cur_elem_is_removed && !reached_end); + + return (*this); + } + + // we provide this overide to ensure that stl functions like std::advance, work properly + // by accounting for removed elements + array_iterator_t& operator+=(std::ptrdiff_t n) + { + V::const_iterator::operator+=(n); // raw ptr shift (i.e. ignoring that there may be removed elements) + + bool cur_elem_is_removed = false; + bool reached_end = (*this) == cend<>(); + cur_elem_is_removed = mesh_ptr->is_removed(*(*this)); + while (!reached_end && cur_elem_is_removed) { + V::const_iterator::operator++(); //++(*this); + size_t raw_descriptor = *(*this); // (*this) - cbegin>(false); //std::distance(cbegin>(false), (*this)); // O(1) ?? + cur_elem_is_removed = mesh_ptr->is_removed(element_descriptor_type((std::uint32_t)raw_descriptor)); + if (!cur_elem_is_removed) { + break; + } + + reached_end = (*this) == cend<>(); + } + return *this; + } + + // The following are helper functions which are specialised (via type-deduction) + // for the type of mesh elements that *this* iterator walks over in "mesh_ptr" + // e.g. faces. These functions are used to determine when *this* iterator has + // reached the end of the respective std::map data structure over which we are + // iterating. + + template > + I cend() + { + return cend(id_()); // https://stackoverflow.com/questions/3052579/explicit-specialization-in-non-namespace-scope + } + + template > + I cbegin(bool account_for_removed_elems) + { + return cbegin(account_for_removed_elems, id_()); + } + +private: + // postfix increment (i++) + // NOTE: This overide is private to simplify implementation, and we don't need it + array_iterator_t operator++(int) + { + MCUT_ASSERT(false); + return cend<>(); + } + + template > + I cend(id_) + { + return I(); // unused stub + } + + template > + I cbegin(bool account_for_removed_elems, id_) + { + return I(account_for_removed_elems); // unused stub + } + + vertex_array_iterator_t cbegin(bool account_for_removed_elems, id_ = {}); + vertex_array_iterator_t cend(id_); + + edge_array_iterator_t cbegin(bool account_for_removed_elems, id_ = {}); + edge_array_iterator_t cend(id_); + + halfedge_array_iterator_t cbegin(bool account_for_removed_elems, id_ = {}); + halfedge_array_iterator_t cend(id_); + + face_array_iterator_t cbegin(bool account_for_removed_elems, id_ = {}); + face_array_iterator_t cend(id_); +}; // class array_iterator_t : public V::const_iterator + +namespace std { +#if 1 +template <> +inline typename edge_array_iterator_t::difference_type distance( + edge_array_iterator_t first, + edge_array_iterator_t last) +{ + MCUT_ASSERT(first.get_mesh_ptr() == last.get_mesh_ptr()); + edge_array_iterator_t it = first; + edge_array_iterator_t::difference_type dist = last - first; + + uint32_t r = it.get_mesh_ptr()->count_removed_elements_in_range(first, last); + if (r > 0) { + dist = dist - r; + } + + MCUT_ASSERT(dist >= 0); + + return dist; +} +#endif +#if 0 + template <> + void advance( + hmesh_t::array_iterator_t &iter, + typename std::iterator_traits>::difference_type n); +#endif + +template <> +struct hash { + std::size_t operator()(const vertex_descriptor_t& k) const + { + return std::hash()(static_cast(k)); + } +}; + +template <> +struct hash { + std::size_t operator()(const edge_descriptor_t& k) const + { + return std::hash()(static_cast(k)); + } +}; + +template <> +struct hash { + std::size_t operator()(const halfedge_descriptor_t& k) const + { + return std::hash()(static_cast(k)); + } +}; + +template <> +struct hash { + std::size_t operator()(const face_descriptor_t& k) const + { + return std::hash()(static_cast(k)); + } +}; +} + +#endif // #ifndef MCUT_HALFEDGE_MESH_H_ diff --git a/src/mcut/include/mcut/internal/kernel.h b/src/mcut/include/mcut/internal/kernel.h new file mode 100644 index 0000000000..2e217ace4d --- /dev/null +++ b/src/mcut/include/mcut/internal/kernel.h @@ -0,0 +1,233 @@ +/** + * 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: + * (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 + */ + +#ifndef MCUT_KERNEL_H +#define MCUT_KERNEL_H +#include +#include +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) +#include +#endif + +#include +#include +#include + +// +// final execution states (i.e. did anything go wrong..?) +// +enum class status_t { + SUCCESS = 0, + // mesh is malformed + // * vertices less than 3 + // * no faces + // * non-manifold + // * contains more than one connected component + INVALID_SRC_MESH = -1, // TODO: these error flags should be generated in mcut.cpp not the kernel + INVALID_CUT_MESH = -2, + // EDGE_EDGE_INTERSECTION = -3, // Found an edge-edge intersection. + // FACE_VERTEX_INTERSECTION = -4, // Found an face-vertex intersection. + + // there exists no edge in the input mesh which intersects cut-surface polygon + INVALID_MESH_INTERSECTION = -3, + + // The bounding volume heirarchies of the input mesh and cut surface do not overlap + // INVALID_BVH_INTERSECTION = -6, + /* + MCUT is formulated for inputs in general position. Here the notion of general position is defined with + respect to the orientation predicate (as evaluated on the intersecting polygons). Thus, a set of points + is in general position if no three points are collinear and also no four points are coplanar. + + MCUT uses the "GENERAL_POSITION_VIOLATION" flag to inform of when to use perturbation (of the + cut-mesh) so as to bring the input into general position. In such cases, the idea is to solve the cutting + problem not on the given input, but on a nearby input. The nearby input is obtained by perturbing the given + input. The perturbed input will then be in general position and, since it is near the original input, + the result for the perturbed input will hopefully still be useful. This is justified by the fact that + the task of MCUT is not to decide whether the input is in general position but rather to make perturbation + on the input (if) necessary within the available precision of the computing device. +*/ + GENERAL_POSITION_VIOLATION = -4, + /* + TODO: add documentation +*/ + DETECTED_FLOATING_POLYGON = -5 +}; + +// +// Position of a cut surface patch with respect to the input mesh +// +enum class cm_patch_location_t : unsigned char { + INSIDE, // + : The patch is located inside the input mesh volume (i.e. it is used to seal holes) + OUTSIDE, // - : The patch is located outside the input mesh volume (boolean union). + UNDEFINED // ~ : The notion of INSIDE/OUTSIDE is not applicable because input mesh is non-watertight +}; + +// +// Position of a connected component (CC) relative to cut-surface +// +enum class sm_frag_location_t : unsigned char { + ABOVE, // + : The CC is on positive side of the cut-surface (normal direction) + BELOW, // - : The CC is on negative side of the cut-surface (normal direction) + UNDEFINED // ~ : The notion of ABOVE/BELOW is not applicable because the CC has [partially] cut +}; + +// +// The winding order of the polygons of a cut surface patch +// +enum class cm_patch_winding_order_t : unsigned char { + DEFAULT, // + : The polygons of the patch have the [same] winding order as the cut-surface (e.g. CCW) + REVERSE, // - : The polygons of the patch have the [opposite] winding order as the cut-surface (e.g. CW) +}; + +struct floating_polygon_info_t { + // normal of polygon + vec3 polygon_normal; + int polygon_normal_largest_component = -1; + // the positions of the vertices of the floating polygon (order implies connectivity i.e. two points next to each other share a vertex) + std::vector polygon_vertices; +}; + +// +// settings for how to execute the function "dispatch(...)" +// +struct input_t { +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + thread_pool* scheduler = nullptr; +#endif + /*const*/ std::shared_ptr src_mesh = nullptr; + /*const*/ std::shared_ptr cut_mesh = nullptr; + // NOTE: we use std::map because it is beneficial that keys are sorted when + // extracting edge-face intersection pairs + const std::map>* ps_face_to_potentially_intersecting_others = nullptr; +#if defined(USE_OIBVH) + const std::vector>* source_hmesh_face_aabb_array_ptr = nullptr; + const std::vector>* cut_hmesh_face_aabb_array_ptr = nullptr; +#else + BoundingVolumeHierarchy* source_hmesh_BVH; + BoundingVolumeHierarchy* cut_hmesh_BVH; +#endif + bool verbose = true; + // bool keep_partially_sealed_connected_components = false; + bool require_looped_cutpaths = false; // ... i.e. bail on partial cuts (any!) + bool populate_vertex_maps = false; // compute data relating vertices in cc to original input mesh + bool populate_face_maps = false; // compute data relating face in cc to original input mesh + bool enforce_general_position = false; + // counts how many times we have perturbed the cut-mesh to enforce general-position + int general_position_enforcement_count = 0; + + // NOTE TO SELF: if the user simply wants seams, then kernel should not have to proceed to stitching!!! + bool keep_srcmesh_seam = false; + bool keep_cutmesh_seam = false; + // + bool keep_unsealed_fragments = false; + // + bool keep_inside_patches = false; + bool keep_outside_patches = false; + // + bool keep_fragments_below_cutmesh = false; + bool keep_fragments_above_cutmesh = false; + // + bool keep_fragments_partially_cut = false; // TODO: remove + bool keep_fragments_sealed_inside = false; + bool keep_fragments_sealed_outside = false; + // bool include_fragment_sealed_partial = false; // See: variable above "keep_partially_sealed_connected_components" + bool keep_fragments_sealed_inside_exhaustive = false; // TODO remove + bool keep_fragments_sealed_outside_exhaustive = false; // TODO remove + // NOTE TO SELF: if the user simply wants patches, then kernel should not have to proceed to stitching!!! +}; + +struct output_mesh_data_maps_t { + // std::map< + // vd_t, // vertex descriptor in connected component + // vd_t // vertex descriptor in input-mesh e.g. source mesh or cut mesh. ("null_vertex()" if vertex is an intersection point) + // > + std::vector vertex_map; + // std::map< + // fd_t, // face descriptor in connected component + // fd_t // face descriptor in input-mesh e.g. source mesh or cut mesh. (new polygons resulting from clipping are mapped to the same input mesh face) + // > + std::vector face_map; +}; + +struct output_mesh_info_t { + std::shared_ptr mesh; + std::vector seam_vertices; + output_mesh_data_maps_t data_maps; +}; + +// +// the output returned from the function "dispatch" +// +struct output_t { + +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + std::atomic status; +#else + status_t status = status_t::SUCCESS; +#endif + + logger_t logger; + // fragments + std::map>>> connected_components; + std::map>> unsealed_cc; // connected components before hole-filling + // patches + std::map>> inside_patches; // .. between neigbouring connected ccsponents (cs-sealing patches) + std::map>> outside_patches; + // the input meshes which also include the edges that define the cut path + // NOTE: not always defined (depending on the arising cutpath configurations) + std::shared_ptr seamed_src_mesh; + std::shared_ptr seamed_cut_mesh; + + // floating polygon handling + std::map< + // the face of the origin-mesh on which floating polygon(s) are discovered + // NOTE: this is a descriptor into the polygon soup. Thus, we'll need to + // subtract the number of source-mesh faces if this face belongs to the cut + // mesh. + fd_t, + // info about floating polygons contained on ps-face + std::vector> + detected_floating_polygons; +}; + +// internal main +void dispatch(output_t& out, const input_t& in); + +int find_connected_components( +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + thread_pool& scheduler, +#endif + std::vector& fccmap, const hmesh_t& mesh, std::vector& cc_to_vertex_count, + std::vector& cc_to_face_count); + +// return true if point p lies on the plane of every three vertices of f +bool point_on_face_plane(const hmesh_t& m, const fd_t& f, const vec3& p, int& fv_count); + +// +// returns string equivalent value (e.g. for printing) +// +std::string to_string(const sm_frag_location_t&); +std::string to_string(const cm_patch_location_t&); +std::string to_string(const status_t&); +std::string to_string(const cm_patch_winding_order_t&); + +#endif // #ifndef MCUT_KERNEL_H diff --git a/src/mcut/include/mcut/internal/math.h b/src/mcut/include/mcut/internal/math.h new file mode 100644 index 0000000000..8a00e63a62 --- /dev/null +++ b/src/mcut/include/mcut/internal/math.h @@ -0,0 +1,695 @@ +/** + * 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: + * (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 + */ + +#ifndef MCUT_MATH_H_ +#define MCUT_MATH_H_ + +#include + +#include +#include +#include + +#include + +#include "mcut/internal/utils.h" + +enum sign_t { + ON_NEGATIVE_SIDE = -1, // left + ON_ORIENTED_BOUNDARY = 0, // on boundary + ON_POSITIVE_SIDE = 1, // right + // + NEGATIVE = ON_NEGATIVE_SIDE, + ZERO = ON_ORIENTED_BOUNDARY, + POSITIVE = ON_POSITIVE_SIDE, +}; + +template +class vec2_ { +public: + typedef T element_type; + vec2_() + : m_x(0.0) + , m_y(0.0) + { + } + + vec2_(const T& value) + : m_x(value) + , m_y(value) + { + } + + vec2_(const T& x, const T& y) + : m_x(x) + , m_y(y) + { + } + + virtual ~vec2_() + { + } + + static vec2_ make(const T x, const T y) + { + return vec2_(x, y); + } + + static int cardinality() + { + return 2; + } + + bool operator==(const vec2_& other) const + { + return this->m_x == other.x() && this->m_y == other.y(); + } + + const T& operator[](int index) const + { + MCUT_ASSERT(index >= 0 && index <= 1); + + if (index == 0) { + return m_x; + } else { + return m_y; + } + } + + T& operator[](int index) + { + MCUT_ASSERT(index >= 0 && index <= 1); + + if (index == 0) { + return m_x; + } + + return m_y; + + } + + const vec2_ operator-(const vec2_& other) const + { + return vec2_(m_x - other.m_x, m_y - other.m_y); + } + + const vec2_ operator+(const vec2_& other) const + { + return vec2_(m_x + other.m_x, m_y + other.m_y); + } + + const vec2_ operator/(const T& number) const + { + return vec2_(m_x / number, m_y / number); + } + + const vec2_ operator*(const T& number) const + { + return vec2_(m_x * number, m_y * number); + } + + const T& x() const + { + return m_x; + } + + const T& y() const + { + return m_y; + } + + T& x() + { + return m_x; + } + + T& y() + { + return m_y; + } + +protected: + T m_x, m_y; +}; // vec2_ + +typedef vec2_<> vec2; + +template +class vec3_ : public vec2_ { +public: + vec3_() + : vec2_(0.0, 0.0) + , m_z(0.0) + { + } + + vec3_(const T& value) + : vec2_(value, value) + , m_z(value) + { + } + + vec3_(const T& x, const T& y, const T& z) + : vec2_(x, y) + , m_z(z) + { + } + ~vec3_() + { + } + + static int cardinality() + { + return 3; + } + + const T& operator[](int index) const + { + MCUT_ASSERT(index >= 0 && index <= 2); + if (index == 0) { + return this->m_x; + } else if (index == 1) { + return this->m_y; + } else { + return this->m_z; + } + } + + T& operator[](int index) + { + MCUT_ASSERT(index >= 0 && index <= 2); + if (index == 0) { + return this->m_x; + } else if (index == 1) { + return this->m_y; + } else { + return this->m_z; + } + } + + vec3_ operator-(const vec3_& other) const + { + return vec3_(this->m_x - other.m_x, this->m_y - other.m_y, this->m_z - other.m_z); + } + + vec3_ operator+(const vec3_& other) const + { + return vec3_(this->m_x + other.m_x, this->m_y + other.m_y, this->m_z + other.m_z); + } + + const vec3_ operator/(const T& number) const + { + return vec3_(this->m_x / number, this->m_y / number, this->m_z / number); + } + + const vec3_ operator*(const T& number) const + { + return vec3_(this->m_x * number, this->m_y * number, this->m_z * number); + } + + const T& z() const + { + return m_z; + } + +protected: + T m_z; +}; // vec3_ + +typedef vec3_<> vec3; + +template +class matrix_t { +public: + matrix_t() + : m_rows(-1) + , m_cols(-1) + { + } + + matrix_t(unsigned int rows, unsigned int cols) + : m_rows(rows) + , m_cols(cols) + , m_entries(std::vector((size_t)rows * cols, T(0.0))) // zeroes + { + } + + T& operator()(unsigned int row, unsigned int col) + { + return m_entries[(size_t)row * m_cols + col]; + } + + T operator()(unsigned int row, unsigned int col) const + { + return m_entries[(size_t)row * m_cols + col]; + } + + // multiply + matrix_t operator*(const matrix_t& other) const + { + matrix_t result(this->rows(), other.cols()); + + for (int i = 0; i < this->rows(); ++i) { + for (int j = 0; j < other.cols(); ++j) { + for (int k = 0; k < this->cols(); ++k) { + result(i, j) += (*this)(i, k) * other(k, j); + } + } + } + + return result; + } + + matrix_t operator*(const double& s) const + { + matrix_t result(m_rows, m_cols); + + for (int i = 0; i < m_rows; ++i) { + for (int j = 0; j < m_cols; ++j) { + result(i, j) = (*this)(i, j) * s; + } + } + + return result; + } + + matrix_t operator/(const double& s) const + { + matrix_t result(m_rows, m_cols); + + for (int i = 0; i < m_rows; ++i) { + for (int j = 0; j < m_cols; ++j) { + result(i, j) = (*this)(i, j) / s; + } + } + + return result; + } + + matrix_t operator-(const matrix_t& m) const + { + MCUT_ASSERT(m.rows() == this->rows()); + MCUT_ASSERT(m.cols() == this->cols()); + + matrix_t result(m_rows, m_cols); + + for (int i = 0; i < m_rows; ++i) { + for (int j = 0; j < m_cols; ++j) { + result(i, j) = (*this)(i, j) - m(i, j); + } + } + + return result; + } + + // 2x3 matrix times 3x1 vector + vec2 operator*(const vec3& v) const + { + MCUT_ASSERT(this->cols() == vec3::cardinality()); + vec2 result(double(0.0)); + MCUT_ASSERT(this->rows() == vec2::cardinality()); + + for (int col = 0; col < this->cols(); ++col) { + for (int row = 0; row < vec2::cardinality(); ++row) { + result[row] = result[row] + ((*this)(row, col) * v[col]); + } + } +#if 0 + // columns + const Vec c0((*this)(0, 0), (*this)(1, 0), (*this)(2, 0)); + const Vec c1((*this)(0, 1), (*this)(1, 1), (*this)(2, 1)); + + result = c0 * v[0] + c1 * v[1]; + + if (this->rows() == 3 && (this->cols() == 3) + { + const Vec c2((*this)(0, 2), (*this)(1, 2), (*this)(2, 2)); + result = result + c2 * v[2]; + } +#endif + return result; + } + + inline int rows() const + { + return m_rows; + } + + inline int cols() const + { + return m_cols; + } + +private: + int m_rows; + int m_cols; + std::vector m_entries; +}; + +extern double square_root(const double& number); +extern double absolute_value(const double& number); +extern sign_t sign(const double& number); +extern std::ostream& operator<<(std::ostream& os, const vec3& v); + +template +std::ostream& operator<<(std::ostream& os, const matrix_t& m) +{ + for (int i = 0; i < m.rows(); i++) { + for (int j = 0; j < m.cols(); j++) { + os << m(i, j) << ", "; + } + os << "\n"; + } + return os; +} + +template +const T& min(const T& a, const T& b) +{ + return ((b < a) ? b : a); +} +template +const T& max(const T& a, const T& b) +{ + return ((a < b) ? b : a); +} + +extern bool operator==(const vec3& a, const vec3& b); + +template +vec2_ compwise_min(const vec2_& a, const vec2_& b) +{ + return vec2_(min(a.x(), b.x()), min(a.y(), b.y())); +} + +template +vec2_ compwise_max(const vec2_& a, const vec2_& b) +{ + return vec2_(max(a.x(), b.x()), max(a.y(), b.y())); +} + +template +vec3_ compwise_min(const vec3_& a, const vec3_& b) +{ + return vec3_(min(a.x(), b.x()), min(a.y(), b.y()), min(a.z(), b.z())); +} + +template +vec3_ compwise_max(const vec3_& a, const vec3_& b) +{ + return vec3_(max(a.x(), b.x()), max(a.y(), b.y()), max(a.z(), b.z())); +} + +extern vec3 cross_product(const vec3& a, const vec3& b); + +template +double dot_product(const vector_type& a, const vector_type& b) +{ + double out(0.0); + for (int i = 0; i < vector_type::cardinality(); ++i) { + out += (a[i] * b[i]); + } + return out; +} + +// compute a*b^T, which is a matrix +template +matrix_t outer_product(const vector_type& a, const vector_type& b) +{ + matrix_t out(vector_type::cardinality(), vector_type::cardinality()); + const vector_type c0 = a * b[0]; // colmuns + const vector_type c1 = a * b[1]; + + if (vector_type::cardinality() == 3) { + const vector_type c2 = a * b[2]; + + out(0, 0) = c0[0]; + out(1, 0) = c0[1]; + out(2, 0) = c0[2]; + + out(0, 1) = c1[0]; + out(1, 1) = c1[1]; + out(2, 1) = c1[2]; + + out(0, 2) = c2[0]; + out(1, 2) = c2[1]; + out(2, 2) = c2[2]; + } else { + out(0, 0) = c0[0]; + out(1, 0) = c0[1]; + + out(0, 1) = c1[0]; + out(1, 1) = c1[1]; + } + + return out; +} + +template +double squared_length(const vector_type& v) +{ + return dot_product(v, v); +} + +template +double length(const vector_type& v) +{ + return square_root(squared_length(v)); +} + +template +vector_type normalize(const vector_type& v) +{ + return v / length(v); +} + +double orient2d(const vec2& pa, const vec2& pb, const vec2& pc); +double orient3d(const vec3& pa, const vec3& pb, const vec3& pc, + const vec3& pd); + +// Compute a polygon's plane coefficients (i.e. normal and d parameters). +// The computed normal is not normalized. This function returns the largest component of the normal. +int compute_polygon_plane_coefficients(vec3& normal, double& d_coeff, + const vec3* polygon_vertices, const int polygon_vertex_count); + +// Compute the intersection point between a line (not a segment) and a plane defined by a polygon. +// +// Parameters: +// 'p' : output intersection point (computed if line does indeed intersect the plane) +// 'q' : first point defining your line +// 'r' : second point defining your line +// 'polygon_vertices' : the vertices of the polygon defineing the plane (assumed to not be degenerate) +// 'polygon_vertex_count' : number of olygon vertices +// 'polygon_normal_max_comp' : largest component of polygon normal. +// 'polygon_plane_normal' : normal of the given polygon +// 'polygon_plane_d_coeff' : the distance coefficient of the plane equation corresponding to the polygon's plane +// +// Return values: +// '0': line is parallel to plane or polygon is degenerate (within available precision) +// '1': an intersection exists. +// 'p': q and r lie in the plane (technically they are parallel to the plane too). +char compute_line_plane_intersection(vec3& p, // intersection point + const vec3& q, const vec3& r, const vec3* polygon_vertices, + const int polygon_vertex_count, const int polygon_normal_max_comp, + const vec3& polygon_plane_normal); + +// Test if a line segment intersects with a plane, and yeild the intersection point if so. +// +// Return values: +// 'p': The segment lies wholly within the plane. +// 'q': The(first) q endpoint is on the plane (but not 'p'). +// 'r' : The(second) r endpoint is on the plane (but not 'p'). +// '0' : The segment lies strictly to one side or the other of the plane. +// '1': The segment intersects the plane, and none of {p, q, r} hold. +char compute_segment_plane_intersection(vec3& p, const vec3& normal, const double& d_coeff, + const vec3& q, const vec3& r); + +// Similar to "compute_segment_plane_intersection" but simply checks the [type] of intersection using +// exact arithmetic +// +// Return values: +// 'p': The segment lies wholly within the plane. +// 'q': The(first) q endpoint is on the plane (but not 'p'). +// 'r' : The(second) r endpoint is on the plane (but not 'p'). +// '0' : The segment lies strictly to one side or the other of the plane. +// '1': The segment intersects the plane, and none of {p, q, r} hold. +char compute_segment_plane_intersection_type(const vec3& q, const vec3& r, + const std::vector& polygon_vertices, + const vec3& polygon_normal, const int polygon_normal_largest_component); + +// Test if a point 'q' (in 2D) lies inside or outside a given polygon (count the number ray crossings). +// +// Return values: +// 'i': q is strictly interior +// 'o': q is strictly exterior (outside). +// 'e': q is on an edge, but not an endpoint. +// 'v': q is a vertex. +char compute_point_in_polygon_test(const vec2& q, const std::vector& polygon_vertices); + +// Test if a point 'q' (in 3D) lies inside or outside a given polygon (count the number ray crossings). +// +// Return values: +// 'i': q is strictly interior +// 'o': q is strictly exterior (outside). +// 'e': q is on an edge, but not an endpoint. +// 'v': q is a vertex. +char compute_point_in_polygon_test(const vec3& p, const std::vector& polygon_vertices, + const vec3& polygon_normal, const int polygon_normal_largest_component); + +// project a 3d polygon to 3d by eliminating the largest component of its normal +void project_to_2d(std::vector& out, const std::vector& polygon_vertices, + const vec3& polygon_normal, const int polygon_normal_largest_component); + +void project_to_2d(std::vector& out, const std::vector& polygon_vertices, + const vec3& polygon_normal); + +bool coplaner(const vec3& pa, const vec3& pb, const vec3& pc, + const vec3& pd); + +bool collinear(const vec2& a, const vec2& b, const vec2& c, double& predResult); + +bool collinear(const vec2& a, const vec2& b, const vec2& c); + +/* +Compute the intersection of two line segments. Can also be used to calculate where the respective lines intersect. + +Parameters: +'a' and 'b': end points of first segment +'c' and 'd': end points of second segment +'p': the intersection point +'s': the parameter for parametric equation of segment a,b (0..1) +'t': the parameter for parametric equation of segment c,d (0..1) + +Return values: + +'e': The segments collinearly overlap, sharing a point; 'e' stands for 'edge.' +'v': An endpoint of one segment is on the other segment, but 'e' doesn't hold; 'v' stands for 'vertex.' +'1': The segments intersect properly (i.e., they share a point and neither 'v' nor 'e' holds); '1' stands for TRUE. +'0': The segments do not intersect (i.e., they share no points); '0' stands for FALSE +*/ +char compute_segment_intersection(const vec2& a, const vec2& b, const vec2& c, const vec2& d, + vec2& p, double& s, double& t); + +template +struct bounding_box_t { + + vector_type m_minimum; + vector_type m_maximum; + + bounding_box_t(const vector_type& minimum, const vector_type& maximum) + { + m_minimum = minimum; + m_maximum = maximum; + } + + bounding_box_t() + { + m_minimum = vector_type(std::numeric_limits::max()); + m_maximum = vector_type(-std::numeric_limits::max()); + } + + inline const vector_type& minimum() const + { + return m_minimum; + } + + inline const vector_type& maximum() const + { + return m_maximum; + } + + inline void expand(const vector_type& point) + { + m_maximum = compwise_max(m_maximum, point); + m_minimum = compwise_min(m_minimum, point); + } + + inline void expand(const bounding_box_t& bbox) + { + m_maximum = compwise_max(m_maximum, bbox.maximum()); + m_minimum = compwise_min(m_minimum, bbox.minimum()); + } + + inline void enlarge(const typename vector_type::element_type& eps_) + { + m_maximum = m_maximum + eps_; + m_minimum = m_minimum - eps_; + } + + float SurfaceArea() const + { + vector_type d = m_maximum - m_minimum; + return typename vector_type::element_type(2.0) * (d.x() * d.y() + d.x() * d.z() + d.y() * d.z()); + } + + int MaximumExtent() const + { + vector_type diag = m_maximum - m_minimum; + if (diag.x() > diag.y() && diag.x() > diag.z()) + return 0; + else if (diag.y() > diag.z()) + return 1; + else + return 2; + } +}; + + +template +inline bool intersect_bounding_boxes(const bounding_box_t>& a, const bounding_box_t>& b) +{ + const vec3_& amin = a.minimum(); + const vec3_& amax = a.maximum(); + const vec3_& bmin = b.minimum(); + const vec3_& bmax = b.maximum(); + return (amin.x() <= bmax.x() && amax.x() >= bmin.x()) && // + (amin.y() <= bmax.y() && amax.y() >= bmin.y()) && // + (amin.z() <= bmax.z() && amax.z() >= bmin.z()); +} + +bool point_in_bounding_box(const vec2& point, const bounding_box_t& bbox); + +bool point_in_bounding_box(const vec3& point, const bounding_box_t& bbox); + +template +void make_bbox(bounding_box_t& bbox, const vector_type* vertices, const int num_vertices) +{ + MCUT_ASSERT(vertices != nullptr); + MCUT_ASSERT(num_vertices >= 3); + + for (int i = 0; i < num_vertices; ++i) { + const vector_type& vertex = vertices[i]; + bbox.expand(vertex); + } +} + +// Shewchuk predicates : shewchuk.c +extern "C" { +// void exactinit(); +double orient2d(const double* pa, const double* pb, const double* pc); +double orient3d(const double* pa, const double* pb, const double* pc, const double* pd); +double orient3dfast(const double* pa, const double* pb, const double* pc, const double* pd); +double incircle(const double* pa, const double* pb, const double* pc, const double* pd); +double insphere(const double* pa, const double* pb, const double* pc, const double* pd, const double* pe); +} + +#endif // MCUT_MATH_H_ diff --git a/src/mcut/include/mcut/internal/preproc.h b/src/mcut/include/mcut/internal/preproc.h new file mode 100644 index 0000000000..b7129aa94f --- /dev/null +++ b/src/mcut/include/mcut/internal/preproc.h @@ -0,0 +1,55 @@ +/** + * 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: + * (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 + */ + +/** + * @file mcut.h + * @author Floyd M. Chitalu + * @date 22 July 2022 + * + * @brief API-function implementations. + * + * NOTE: This header file defines the frontend implementation of mcDispatch, + * which handles mesh preparation (BVH building, traversal, polygon partitioning + * etc). + * + */ + +#ifndef _FRONTEND_INTERSECT_H_ +#define _FRONTEND_INTERSECT_H_ + +#include "mcut/internal/frontend.h" + +extern "C" void preproc( + std::shared_ptr context_uptr, + McFlags dispatchFlags, + const void* pSrcMeshVertices, + const uint32_t* pSrcMeshFaceIndices, + const uint32_t* pSrcMeshFaceSizes, + uint32_t numSrcMeshVertices, + uint32_t numSrcMeshFaces, + const void* pCutMeshVertices, + const uint32_t* pCutMeshFaceIndices, + const uint32_t* pCutMeshFaceSizes, + uint32_t numCutMeshVertices, + uint32_t numCutMeshFaces) noexcept(false); + +#endif // #ifndef _FRONTEND_INTERSECT_H_ \ No newline at end of file diff --git a/src/mcut/include/mcut/internal/timer.h b/src/mcut/include/mcut/internal/timer.h new file mode 100644 index 0000000000..bce3dcd4a1 --- /dev/null +++ b/src/mcut/include/mcut/internal/timer.h @@ -0,0 +1,87 @@ +/** + * 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: + * (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 + */ +#ifndef MCUT_TIMER_H_ +#define MCUT_TIMER_H_ + +#include +#include +#include +#include +#include + +#include "mcut/internal/utils.h" +#include "mcut/internal/tpool.h" + +//#define PROFILING_BUILD + +class mini_timer { + std::chrono::time_point m_start; + const std::string m_name; + bool m_valid = true; + +public: + mini_timer(const std::string& name) + : m_start(std::chrono::steady_clock::now()) + , m_name(name) + { + } + + ~mini_timer() + { + if (m_valid) { + const std::chrono::time_point now = std::chrono::steady_clock::now(); + const std::chrono::milliseconds elapsed = std::chrono::duration_cast(now - m_start); + unsigned long long elapsed_ = elapsed.count(); + log_msg("[MCUT][PROF:" << std::this_thread::get_id() << "]: \"" << m_name << "\" ("<< elapsed_ << "ms)"); + } + } + void set_invalid() + { + m_valid = false; + } +}; + +extern thread_local std::stack> g_thrd_loc_timerstack; + +#if defined(PROFILING_BUILD) + +#define TIMESTACK_PUSH(name) \ + g_thrd_loc_timerstack.push(std::unique_ptr(new mini_timer(name))) +#define TIMESTACK_POP() \ + g_thrd_loc_timerstack.pop() +#define TIMESTACK_RESET() \ + while (!g_thrd_loc_timerstack.empty()) { \ + g_thrd_loc_timerstack.top()->set_invalid(); \ + g_thrd_loc_timerstack.pop(); \ + } +#define SCOPED_TIMER(name) \ + mini_timer _1mt(name) +#else +#define SCOPED_TIMER(name) +#define TIMESTACK_PUSH(name) +#define TIMESTACK_POP() +#define TIMESTACK_RESET() +#endif + + + +#endif \ No newline at end of file diff --git a/src/mcut/include/mcut/internal/tpool.h b/src/mcut/include/mcut/internal/tpool.h new file mode 100644 index 0000000000..9626d8f1cf --- /dev/null +++ b/src/mcut/include/mcut/internal/tpool.h @@ -0,0 +1,1010 @@ +/** + * 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: + * (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 + */ + +#ifndef MCUT_SCHEDULER_H_ +#define MCUT_SCHEDULER_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mcut/internal/utils.h" + +class function_wrapper { +private: + struct impl_base { + virtual void call() = 0; + virtual ~impl_base() { } + }; + + std::unique_ptr impl; + + template + struct impl_type : impl_base { + F f; + impl_type(F&& f_) + : f(std::move(f_)) + { + } + void call() { return f(); } + }; + +public: + template + function_wrapper(F&& f) + : impl(new impl_type(std::move(f))) + { + } + + void operator()() { impl->call(); } + + function_wrapper() = default; + + function_wrapper(function_wrapper&& other) noexcept + : impl(std::move(other.impl)) + { + } + + function_wrapper& operator=(function_wrapper&& other) noexcept + { + impl = std::move(other.impl); + return *this; + } + + function_wrapper(const function_wrapper&) = delete; + function_wrapper(function_wrapper&) = delete; + function_wrapper& operator=(const function_wrapper&) = delete; +}; + +template +class thread_safe_queue { +private: + std::atomic* m_done; + struct node { + std::shared_ptr data; + std::unique_ptr next; + }; + + std::mutex head_mutex; + std::unique_ptr head; + std::mutex tail_mutex; + node* tail; + std::condition_variable data_cond; + + std::unique_ptr try_pop_head(T& value) + { + std::lock_guard head_lock(head_mutex); + if (head.get() == get_tail()) { + return std::unique_ptr(nullptr); + } + value = std::move(*head->data); + return pop_head(); + } + + node* get_tail() + { + std::lock_guard tail_lock(tail_mutex); + return tail; + } + + // dont call directly + std::unique_ptr pop_head() + { + std::unique_ptr old_head = std::move(head); + head = std::move(old_head->next); + return (old_head); + } + +#if _WIN32 // on windows + // there is a C26115 warning here, which is by design. The warning is caused by + // the fact that our function here has the side effect of locking the mutex "head_mutex" + // via the construction of the unique_lock. + // The warning is removed by annotating the function with "_Acquires_lock_(...)". + // See here: https://developercommunity.visualstudio.com/t/unexpected-warning-c26115-for-returning-a-unique-l/1077322 + _Acquires_lock_(return) +#endif + std::unique_lock wait_for_data() + { + std::unique_lock head_lock(head_mutex); + auto until = [&]() { return m_done->load() || head.get() != get_tail(); }; + data_cond.wait(head_lock, until); + return head_lock; + } + + std::unique_ptr wait_pop_head(T& value) + { + std::unique_lock head_lock(wait_for_data()); + if (m_done->load() == false) { + value = std::move(*head->data); + return pop_head(); + } else { + return std::unique_ptr(nullptr); + } + } + +public: + thread_safe_queue() + : head(new node) + , tail(head.get()) /*, can_wait_for_data(true)*/ + { + } + thread_safe_queue(const thread_safe_queue& other) = delete; + thread_safe_queue& operator=(const thread_safe_queue& other) = delete; + + void set_done_ptr(std::atomic* done) + { + m_done = done; + } + + void disrupt_wait_for_data() + { + // https://stackoverflow.com/questions/60658842/condition-variable-doesnt-get-notified-to-wake-up-even-with-a-predicate + // + // Need to this to prevent data race, which can happen when user-app thread + // is too slow to reach this function i.e. that an API/context/device thread + // waits on the condition variable AFTER the client thread calls notify_one() + std::unique_lock lock(head_mutex); + data_cond.notify_one(); + } + + void notify_one() + { + data_cond.notify_one(); + } + + void push(T new_value) + { + std::shared_ptr new_data(std::make_shared(std::move(new_value))); + std::unique_ptr p(new node); + { + std::lock_guard tail_lock(tail_mutex); + tail->data = new_data; + node* const new_tail = p.get(); + tail->next = std::move(p); + tail = new_tail; + } + data_cond.notify_one(); + } + + void wait_and_pop(T& value) + { + std::unique_ptr const old_head = wait_pop_head(value); + } + + bool try_pop(T& value) + { + std::unique_ptr const old_head = try_pop_head(value); + return !(!(old_head)); // https://stackoverflow.com/questions/30521849/error-on-implicit-cast-from-stdunique-ptr-to-bool + } + + bool empty() + { + std::lock_guard head_lock(head_mutex); + return (head.get() == get_tail()); + } +}; + +class join_threads { + std::vector& threads; + +public: + explicit join_threads(std::vector& threads_) + : threads(threads_) + { + } + ~join_threads() + { + for (unsigned long i = 0; i < threads.size(); ++i) { + if (threads[i].joinable()) + threads[i].join(); + } + } +}; + +class thread_pool { + std::atomic m_done; + std::vector> work_queues; + + std::vector threads; // NOTE: must be declared after "thread_pool_terminate" and "work_queues" + join_threads joiner; + + void worker_thread(int thread_id) + { + log_msg("[MCUT] Launch helper thread " << std::this_thread::get_id() << " (" << thread_id << ")"); + + do { + function_wrapper task; + + if (!(work_queues[thread_id].try_pop(task))) { + work_queues[thread_id].wait_and_pop(task); + } + + if (m_done) { + break; // finished (i.e. MCUT context was destroyed) + } + + task(); // run the task + + } while (true); + + log_msg("[MCUT] Shutdown helper thread " << std::this_thread::get_id() << " (" << thread_id << ")"); + } + + uint32_t machine_thread_count; + +public: + thread_pool(uint32_t nthreads, uint32_t used_cores) + : m_done(false), + + joiner(threads) + , machine_thread_count(0) + { + log_msg("[MCUT] Create threadpool " << this); + machine_thread_count = (uint32_t)std::thread::hardware_concurrency(); + uint32_t const pool_thread_count = std::min( + nthreads, + // prevents over-subscription given that "N=used_cores" API threads are already in use + machine_thread_count - used_cores); + + try { + + work_queues = std::vector>(pool_thread_count); + + for (unsigned i = 0; i < pool_thread_count; ++i) { + work_queues[i].set_done_ptr(&m_done); + threads.push_back(std::thread(&thread_pool::worker_thread, this, i)); + } + } catch (...) { + m_done.store(true); + wakeup_and_shutdown(); + throw; + } + } + + ~thread_pool() + { + log_msg("[MCUT] Destroy threadpool " << this); + + m_done.store(true); + wakeup_and_shutdown(); + } + + // submit empty task so that worker threads can wake up + // with a valid (but redundant) task to then exit + void wakeup_and_shutdown() + { + for (unsigned i = 0; i < get_num_threads(); ++i) { + work_queues[i].disrupt_wait_for_data(); + } + } + +public: + /* + The thread pool takes care of the exception safety too. Any exception thrown by the + task gets propagated through the std::future returned from submit() , and if the function + exits with an exception, the thread pool destructor abandons any not-yet-completed + tasks and waits for the pool threads to finish. +*/ + template + std::future::type> submit(uint32_t worker_thread_id, FunctionType f) + { + typedef typename std::result_of::type result_type; + + std::packaged_task task(std::move(f)); + std::future res(task.get_future()); + + work_queues[worker_thread_id].push(std::move(task)); + + return res; + } + + size_t get_num_threads() const + { + return threads.size(); + } + + uint32_t get_num_hardware_threads() + { + return machine_thread_count; + } +}; + +static void get_scheduling_parameters( + // the number of thread that will actually do some computation (including master) + uint32_t& num_threads, + // maximum possible number of threads that can be scheduled to perform the task. + uint32_t& max_threads, + // the size of each block of elements assigned to a thread (note: last thread, + // which is the master thread might have less) + uint32_t& block_size, + // number of data elements to process + const uint32_t length, + const uint32_t available_threads, // number of worker threads and master master thread + // minimum number of element assigned to a thread (below this threshold and we + // run just one thread) + const uint32_t min_per_thread = (1 << 10)) +{ + // maximum possible number of threads that can be scheduled to perform the task. + max_threads = (length + min_per_thread - 1) / min_per_thread; + + // the number of thread that will actually do some computation (including master) + num_threads = std::min(available_threads != 0 ? available_threads : 2, max_threads); + + // the size of each block of elements assigned to a thread (note: last thread, + // which is the master thread might have less) + block_size = length / num_threads; + + // printf("max_threads=%u num_threads=%u block_size=%u\n", max_threads, num_threads, block_size); + + if (num_threads == 0 || num_threads > available_threads) { + throw std::runtime_error("invalid number of threads (" + std::to_string(num_threads) + ")"); + } + + if (max_threads == 0) { + throw std::runtime_error("invalid maximum posible number of threads (" + std::to_string(max_threads) + ")"); + } + + if (block_size == 0) { + throw std::runtime_error("invalid work block-size per thread (" + std::to_string(block_size) + ")"); + } +} + +class barrier_t { + unsigned const count; + std::atomic spaces; + std::atomic generation; + +public: + explicit barrier_t(unsigned count_) + : count(count_) + , spaces(count) + , generation(0) + { + } + void wait() + { + unsigned const my_generation = generation; + if (!--spaces) { + spaces = count; + ++generation; + } else { + while (generation == my_generation) + std::this_thread::yield(); + } + } +}; + +template +void parallel_for( + thread_pool& pool, + // start of data elements to be processed in parallel + const InputStorageIteratorType& first, + // end of of data elements to be processed in parallel (e.g. std::map::end()) + const InputStorageIteratorType& last, + // the function that is executed on a sub-block of element within the range [first, last) + FunctionType& task_func, + // the part of the result/output that is computed by the master thread (i.e. the one that is scheduling) + // NOTE: this result must be merged which the output computed for the other + // sub-block in the input ranges. This other data is accessed from the std::futures + OutputStorageType& master_thread_output, + // Future promises of data (to be merged) that is computed by worker threads + std::vector>& futures, + // minimum number of element assigned to a thread (below this threshold and we + // run just one thread) + const uint32_t min_per_thread = (1 << 10)) +{ + uint32_t const length_ = std::distance(first, last); + + MCUT_ASSERT(length_ != 0); + uint32_t block_size = 0; + + uint32_t max_threads = 0; + const uint32_t available_threads = pool.get_num_threads() + 1; // workers and master (+1) + uint32_t num_threads = 0; + + get_scheduling_parameters( + num_threads, + max_threads, + block_size, + length_, + available_threads, + min_per_thread); + + futures.resize(num_threads - 1); + InputStorageIteratorType block_start = first; + + for (uint32_t i = 0; i < (num_threads - 1); ++i) { + InputStorageIteratorType block_end = block_start; + + std::advance(block_end, block_size); + + futures[i] = pool.submit(i, + [&, block_start, block_end]() -> OutputStorageType { + return task_func(block_start, block_end); + }); + + block_start = block_end; + } + + master_thread_output = task_func(block_start, last); +} + +template +void parallel_for( + thread_pool& pool, + // start of data elements to be processed in parallel + const InputStorageIteratorType& first, + // end of of data elements to be processed in parallel (e.g. std::map::end()) + const InputStorageIteratorType& last, + // the function that is executed on a sub-block of element within the range [first, last) + // return void + FunctionType& task_func, + // minimum number of element assigned to a thread (below this threshold and we + // run just one thread) + const uint32_t min_per_thread = (1 << 10)) +{ + uint32_t const length_ = std::distance(first, last); + + MCUT_ASSERT(length_ != 0); + + uint32_t block_size; + + uint32_t max_threads = 0; + const uint32_t available_threads = pool.get_num_threads() + 1; // workers and master (+1) + uint32_t num_threads = 0; + + get_scheduling_parameters( + num_threads, + max_threads, + block_size, + length_, + available_threads, + min_per_thread); + + std::vector> futures; + futures.resize(num_threads - 1); + InputStorageIteratorType block_start = first; + + for (uint32_t i = 0; i < (num_threads - 1); ++i) { + InputStorageIteratorType block_end = block_start; + + std::advance(block_end, block_size); + + futures[i] = pool.submit(i, + [&, block_start, block_end]() { + task_func(block_start, block_end); + }); + + block_start = block_end; + } + + task_func(block_start, last); // master thread work + + for (uint32_t i = 0; i < (uint32_t)futures.size(); ++i) { + futures[i].wait(); + } +} + +template +void partial_sum(Iterator first, Iterator last, Iterator d_first) +{ + // std::vector::iterator first = cc_uptr->face_sizes_cache.begin(); + // std::vector::iterator last = cc_uptr->face_sizes_cache.end(); + // Iterator d_first = first; + + if (first != last) { + + uint32_t sum = *first; + *d_first = sum; + + while (++first != last) { + sum = sum + *first; + *++d_first = sum; + } + + ++d_first; + } +} + +template +void parallel_partial_sum(thread_pool& pool, Iterator first, Iterator last) +{ + typedef typename Iterator::value_type value_type; + struct process_chunk { + void operator()(Iterator begin, Iterator last, + std::future* previous_end_value, + std::promise* end_value) + { + try { + Iterator end = last; + ++end; + partial_sum(begin, end, begin); + if (previous_end_value) { + const value_type addend = previous_end_value->get(); + *last += addend; + if (end_value) { + end_value->set_value(*last); + } + std::for_each(begin, last, [addend](value_type& item) { + item += addend; + }); + } else if (end_value) { + end_value->set_value(*last); + } + } catch (...) { + if (end_value) { + end_value->set_exception(std::current_exception()); + } else { + throw; + } + } + } + }; + + // number of elements in range + unsigned long const length = std::distance(first, last); + + if (!length) + return; + + uint32_t max_threads = 0; + const uint32_t available_threads = pool.get_num_threads() + 1; // workers and master (+1) + uint32_t num_threads = 0; + uint32_t block_size = 0; + + get_scheduling_parameters( + num_threads, + max_threads, + block_size, + length, + available_threads); + + typedef typename Iterator::value_type value_type; + + // promises holding the value of the last element of a block. Only the + // first N-1 blocks (i.e. threads) will have to update this value. + std::vector> end_values(num_threads - 1); + // futures that are used to wait for the end value (the last element) of + // previous block. Only the last N-1 blocks (i.e. threads) will have to wait + // on this value. That is, the first thread has no "previous" block to wait + // on, + std::vector> previous_end_values; + previous_end_values.reserve(num_threads - 1); + std::vector> futures; + futures.resize(num_threads - 1); + + Iterator block_start = first; + + // for each pool-thread + for (unsigned long i = 0; i < (num_threads - 1); ++i) { + Iterator block_last = block_start; + std::advance(block_last, block_size - 1); + + // process_chunk()" handles all synchronisation + futures[i] = pool.submit(i, + [i /*NOTE: capture by-value*/, &previous_end_values, &end_values, block_start, block_last]() { + process_chunk()(block_start, block_last, (i != 0) ? &previous_end_values[i - 1] : 0, &end_values[i]); + }); + + block_start = block_last; + ++block_start; + previous_end_values.push_back(end_values[i].get_future()); // for next thread to wait on + } + + Iterator final_element = block_start; + std::advance(final_element, std::distance(block_start, last) - 1); + + // NOTE: this is a blocking call since the master thread has to wait + // for worker-threads assigned to preceeding chunks to finish before it can + // process its own chunk. + process_chunk()(block_start, final_element, + (num_threads > 1) ? &previous_end_values.back() : 0, + 0); +} + +template +void find_element(Iterator begin, Iterator end, + MatchType match, + std::promise* result, + std::atomic* done_flag) +{ + try { + for (; (begin != end) && !done_flag->load(); ++begin) { + if (*begin == match) { + result->set_value(begin); + done_flag->store(true); + return; + } + } + } catch (...) { + try { + result->set_exception(std::current_exception()); + done_flag->store(true); + } catch (...) { + } + } +} + +template +Iterator parallel_find(thread_pool& pool, Iterator first, Iterator last, MatchType match) +{ + unsigned long const length = std::distance(first, last); + + if (!length) + return last; + + uint32_t max_threads = 0; + const uint32_t available_threads = pool.get_num_threads() + 1; // workers and master (+1) + uint32_t num_threads = 0; + uint32_t block_size = 0; + + get_scheduling_parameters( + num_threads, + max_threads, + block_size, + length, + available_threads); + + std::promise result; + std::atomic done_flag(false); + std::vector> futures; + futures.resize(num_threads - 1); + + { + Iterator block_start = first; + for (unsigned long i = 0; i < (num_threads - 1); ++i) { + Iterator block_end = block_start; + std::advance(block_end, block_size); + + // find_element()" handles all synchronisation + futures[i] = pool.submit(i, + [&result, &done_flag, block_start, block_end, &match]() { + find_element, + block_start, block_end, match, + &result, &done_flag; + }); + block_start = block_end; + } + find_element(block_start, last, match, &result, &done_flag); + } + if (!done_flag.load()) { + return last; // if nothing found (by any thread), return "end" + } + return result.get_future().get(); +} + +template +void find_map_element_by_key(Iterator begin, Iterator end, + KeyType match, + std::promise* result, + std::atomic* done_flag, + barrier_t* barrier_ptr) + +{ + try { + for (; (begin != end) && !done_flag->load(); ++begin) { + if (begin->first == match) { + result->set_value(begin); + done_flag->store(true); + break; // return; + } + } + } catch (...) { + try { + result->set_exception(std::current_exception()); + done_flag->store(true); + } catch (...) { + } + } + barrier_ptr->wait(); +} + +template +Iterator parallel_find_in_map_by_key(thread_pool& pool, Iterator first, Iterator last, KeyType match) +{ + unsigned long const length = std::distance(first, last); + + if (!length) + return last; + + uint32_t max_threads = 0; + const uint32_t available_threads = pool.get_num_threads() + 1; // workers and master (+1) + uint32_t num_threads = 0; + uint32_t block_size = 0; + + get_scheduling_parameters( + num_threads, + max_threads, + block_size, + length, + available_threads); + + barrier_t barrier(num_threads); + + std::promise result; + std::atomic done_flag(false); + + { + std::vector> futures; + futures.resize(num_threads - 1); + + Iterator block_start = first; + for (unsigned long i = 0; i < (num_threads - 1); ++i) { + Iterator block_end = block_start; + std::advance(block_end, block_size); + + futures[i] = pool.submit(i, + [&result, &done_flag, block_start, block_end, &match, &barrier]() { + find_map_element_by_key( + block_start, block_end, match, + &result, &done_flag, &barrier); + }); + block_start = block_end; + } + find_map_element_by_key(block_start, last, match, &result, &done_flag, &barrier); + + for (uint32_t i = 0; i < (uint32_t)futures.size(); ++i) { + futures[i].wait(); + } + } + + if (!done_flag.load()) { + return last; // if nothing found (by any thread), return "end" + } + return result.get_future().get(); +} + +template +void find_element_with_pred(Iterator begin, Iterator end, + UnaryPredicate pred, + std::promise* result, + std::atomic* done_flag, + barrier_t* barrier_ptr) +{ + try { + for (; (begin != end) && !done_flag->load(); ++begin) { + const bool found = pred(*begin); + if (found) { + result->set_value(begin); + done_flag->store(true); + break; // return; + } + } + } catch (...) { + try { + result->set_exception(std::current_exception()); + done_flag->store(true); + } catch (...) { + } + } + barrier_ptr->wait(); +} + +template +Iterator parallel_find_if(thread_pool& pool, Iterator first, Iterator last, UnaryPredicate predicate) +{ + unsigned long const length = std::distance(first, last); + + if (!length) + return last; + + uint32_t max_threads = 0; + const uint32_t available_threads = pool.get_num_threads() + 1; // workers and master (+1) + uint32_t num_threads = 0; + uint32_t block_size = 0; + + get_scheduling_parameters( + num_threads, + max_threads, + block_size, + length, + available_threads); + + barrier_t barrier(num_threads); + + std::promise result; + std::atomic done_flag(false); + std::vector> futures; + futures.resize(num_threads - 1); + + Iterator block_start = first; + for (uint32_t i = 0; i < (num_threads - 1); ++i) { + Iterator block_end = block_start; + std::advance(block_end, block_size); + + // barrier handles all synchronisation + futures[i] = pool.submit(i, + [&result, &done_flag, block_start, block_end, &predicate, &barrier]() { + find_element_with_pred( + block_start, block_end, predicate, + &result, &done_flag, &barrier); + }); + block_start = block_end; + } + find_element_with_pred(block_start, last, predicate, &result, &done_flag, &barrier); + + if (!done_flag.load()) { + return last; // if nothing found (by any thread), return "end" + } + return result.get_future().get(); +} + +template +class threadsafe_list +{ + struct node + { + std::mutex m; + T data; + std::unique_ptr next; + + node(): + next() + {} + + node(T const& value): + data(value) + {} + }; + + node head; + +public: + threadsafe_list() + {} + + ~threadsafe_list() + { + remove_if([](T const&){return true;}); + } + + threadsafe_list(threadsafe_list const& other)=delete; + threadsafe_list& operator=(threadsafe_list const& other)=delete; + + void push_front(T /*const&*/ value) + { + std::unique_ptr new_node(new node(value)); + std::lock_guard lk(head.m); + new_node->next=std::move(head.next); + head.next=std::move(new_node); + } + + template + void for_each(Function f) + { + node* current=&head; + std::unique_lock lk(head.m); + while(node* const next=current->next.get()) + { + std::unique_lock next_lk(next->m); + lk.unlock(); + f(next->data); + current=next; + lk=std::move(next_lk); + } + } + + template + T find_first_if(Predicate p) + { + node* current=&head; + std::unique_lock lk(head.m); + while(node* const next=current->next.get()) + { + std::unique_lock next_lk(next->m); + lk.unlock(); + if(p(next->data)) + { + return next->data; + } + current=next; + lk=std::move(next_lk); + } + return T(); + } + + template + void remove_if(Predicate p) + { + node* current=&head; + std::unique_lock lk(head.m); + while(node* const next=current->next.get()) + { + std::unique_lock next_lk(next->m); + if(p(next->data)) + { + std::unique_ptr old_next=std::move(current->next); + current->next=std::move(next->next); + next_lk.unlock(); + } + else + { + lk.unlock(); + current=next; + lk=std::move(next_lk); + } + } + } +}; + + +struct empty_stack: std::exception +{ + const char* what() const throw() + { + return "empty stack"; + } +}; + +template +class threadsafe_stack +{ +private: + std::stack data; + mutable std::mutex m; +public: + threadsafe_stack(){} + threadsafe_stack(const threadsafe_stack& other) + { + std::lock_guard lock(other.m); + data=other.data; + } + threadsafe_stack& operator=(const threadsafe_stack&) = delete; + + void push(T new_value) + { + std::lock_guard lock(m); + data.push(std::move(new_value)); + } + std::shared_ptr pop() + { + std::lock_guard lock(m); + if(data.empty()) throw empty_stack(); + std::shared_ptr const res( + std::make_shared(std::move(data.top()))); + data.pop(); + return res; + } + void pop(T& value) + { + std::lock_guard lock(m); + if(data.empty()) throw empty_stack(); + value=std::move(data.top()); + data.pop(); + } + bool empty() const + { + std::lock_guard lock(m); + return data.empty(); + } +}; + +#endif // MCUT_SCHEDULER_H_ \ No newline at end of file diff --git a/src/mcut/include/mcut/internal/utils.h b/src/mcut/include/mcut/internal/utils.h new file mode 100644 index 0000000000..c4f7d19689 --- /dev/null +++ b/src/mcut/include/mcut/internal/utils.h @@ -0,0 +1,260 @@ +/** + * 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: + * (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 + */ + +#ifndef MCUT_UTILS_H_ +#define MCUT_UTILS_H_ + +// check if c++11 is supported +#if __cplusplus >= 201103L || (defined(_MSC_VER) && _MSC_VER >= 1900) +#define MCUT_CXX11_IS_SUPPORTED +#elif !defined(__cplusplus) && !defined(_MSC_VER) +typedef char couldnt_parse_cxx_standard[-1]; ///< Error: couldn't parse standard +#endif + +#if defined(_WIN64) || defined(_WIN32) +#define MCUT_BUILD_WINDOWS 1 +#elif defined(__APPLE__) +#define MCUT_BUILD_APPLE 1 +#elif defined(__linux__) || defined(__unix__) +#define MCUT_BUILD_LINUX 1 +#endif // #if defined(_WIN64) || defined(_WIN32) + +#define MCUT_MAKE_STRING__(s) #s +#define MCUT_MAKE_STRING_(s) MCUT_MAKE_STRING__(s) + +#ifndef NDEBUG +#define MCUT_DEBUG_BUILD 1 +#endif + +// debug macros +#if defined(MCUT_DEBUG_BUILD) +// +// MCUT_DEBUG_BREAKPOINT +// +#if defined(MCUT_BUILD_WINDOWS) +#define MCUT_DEBUG_BREAKPOINT_() __debugbreak() +#else // #if defined(MCUT_BUILD_WINDOWS) +#define MCUT_DEBUG_BREAKPOINT_() std::abort() +#endif // #if defined(MCUT_BUILD_WINDOWS) + +// +// MCUT_ASSERT +// +#define MCUT_ASSERT(a) \ + do { \ + if (false == (a)) { \ + std::fprintf(stderr, \ + "Assertion failed: %s, " \ + "%d at \'%s\'\n", \ + __FILE__, \ + __LINE__, \ + MCUT_MAKE_STRING_(a)); \ + MCUT_DEBUG_BREAKPOINT_(); \ + } \ + } while (0) + +#define DEBUG_CODE_MASK(code) code +#else // #if defined(MCUT_DEBUG_BUILD) +// +// MCUT_ASSERT +// +#define MCUT_ASSERT(a) // do nothing +#define DEBUG_CODE_MASK(code) // do nothing +#endif // #if defined(MCUT_DEBUG_BUILD) + +#include +#include +#include + +#if MCUT_BUILD_WINDOWS +#define EXCEPTION_THROWN throw() +#else +#define EXCEPTION_THROWN +#endif + +#define PEDANTIC_SUBSCRIPT_ACCESS 1 + +#if defined(PEDANTIC_SUBSCRIPT_ACCESS) +#define SAFE_ACCESS(var, i) var.at(i) +#else +#define SAFE_ACCESS(var, i) var[i] +#endif + + + +static inline int wrap_integer(int x, const int lo, const int hi) +{ + const int range_size = hi - lo + 1; + + if (x < lo) { + x += range_size * ((lo - x) / range_size + 1); + } + + return lo + (x - lo) % range_size; +} + +class logger_t { + + std::stringstream m_buffer; + bool m_verbose; + std::string m_prepend; + std::string m_reason_for_failure; + +public: + typedef std::ostream& (*ManipFn)(std::ostream&); + typedef std::ios_base& (*FlagsFn)(std::ios_base&); + + logger_t() + : m_buffer() + , m_verbose(false) + , m_prepend() + , m_reason_for_failure() + { + } + logger_t(const logger_t& other) = delete; + logger_t& operator=(const logger_t& other) = delete; + + ~logger_t() + { + } + + std::string get_log_string() + { + return m_buffer.str(); + } + + void set_reason_for_failure(const std::string& msg) + { + if (m_reason_for_failure.empty()) // NOTE + m_reason_for_failure = msg; + } + + std::string get_reason_for_failure() + { + std::string s(m_reason_for_failure); // copy + return s; + } + + inline bool verbose() + { + return m_verbose; + } + + inline void set_verbose(bool b) + { + m_verbose = b; + } + + inline void reset() + { + m_prepend.clear(); + } + + inline void indent() + { + if (!verbose()) { + return; + } + m_prepend.append(" "); + } + + inline void unindent() + { + if (!verbose()) { + return; + } + m_prepend.pop_back(); + m_prepend.pop_back(); + } + + template // int, double, strings, etc + inline logger_t& operator<<(const T& output) + { + if (verbose()) { + m_buffer << output; + } + return *this; + } + + inline logger_t& operator<<(ManipFn manip) /// endl, flush, setw, setfill, etc. + { + if (verbose()) { + manip(m_buffer); + + if (manip == static_cast(std::flush) || manip == static_cast(std::endl)) { + this->flush(); + } + } + return *this; + } + + inline logger_t& operator<<(FlagsFn manip) /// setiosflags, resetiosflags + { + if (verbose()) { + manip(m_buffer); + } + return *this; + } + + inline void flush() + { + if (!(verbose())) { + return; + } + +#if 0 // dump log to terminal [immediately] + std::cout << m_prepend << "::" << m_buffer.str(); + m_buffer.str(std::string()); + m_buffer.clear(); +#endif + } +}; +template +struct pair : std::pair { + pair(const T a, const T b) + : std::pair(a < b ? a : b, a < b ? b : a) + { + } +}; + +template +pair make_pair(const T a, const T b) +{ + return pair(a, b); +} + + + +// Threadsafe logging to console which prevents std::cerr from mixing strings when +// concatenating with the operator<< multiple time per string, across multiple +// threads. +#define log_msg(msg_str) \ + { \ + std::stringstream ss; \ + ss << msg_str << std::endl; \ + std::cerr << ss.str(); \ + } + +// used to marked/label unused function parameters to prevent warnings +#define UNUSED(x) [&x] {}() + +#endif // MCUT_UTILS_H_ diff --git a/src/mcut/include/mcut/mcut.h b/src/mcut/include/mcut/mcut.h new file mode 100644 index 0000000000..7e9c1f2d47 --- /dev/null +++ b/src/mcut/include/mcut/mcut.h @@ -0,0 +1,1265 @@ +/** + * 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: + * (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 + */ + +/** + * @file mcut.h + * @author Floyd M. Chitalu + * @date 1 Jan 2021 + * + * @brief File containing the MCUT applications programming interface (API). + * + * NOTE: This header file defines all the functionality and accessible features of MCUT. + * The interface is a standard C API. + * + */ + +#ifndef MCUT_API_H_ +#define MCUT_API_H_ + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +#include "platform.h" + +/** @file */ + +/** Macro to encode MCUT version */ +#define MC_MAKE_VERSION(major, minor, patch) \ + (((major) << 22) | ((minor) << 12) | (patch)) + +/** MCUT 1.0 version number */ +#define MC_API_VERSION_1_0 MC_MAKE_VERSION(1, 0, 0) // Patch version should always be set to 0 + +/** Macro to decode MCUT version (MAJOR) from MC_HEADER_VERSION_COMPLETE */ +#define MC_VERSION_MAJOR(version) ((uint32_t)(version) >> 22) + +/** Macro to decode MCUT version (MINOR) from MC_HEADER_VERSION_COMPLETE */ +#define MC_VERSION_MINOR(version) (((uint32_t)(version) >> 12) & 0x3ff) + +/** Macro to decode MCUT version (PATCH) from MC_HEADER_VERSION_COMPLETE */ +#define MC_VERSION_PATCH(version) ((uint32_t)(version)&0xfff) + +/** Version of this file */ +#define MC_HEADER_VERSION 100 + +/** Complete version of this file */ +#define MC_HEADER_VERSION_COMPLETE MC_MAKE_VERSION(1, 0, MC_HEADER_VERSION) + +/** Constant value assigned to null variables and parameters */ +#define MC_NULL_HANDLE 0 + +/** Helper-macro to define opaque handles */ +#define MC_DEFINE_HANDLE(object) typedef struct object##_T* object; + +/** A garbage value that is assigned to undefined index values */ +#define MC_UNDEFINED_VALUE UINT32_MAX + +/** + * @brief Connected component handle. + * + * Opaque type referencing a connected component which the client/user must use to access mesh data after a dispatch call. + */ +typedef struct McConnectedComponent_T* McConnectedComponent; + +/** + * @brief Context handle. + * + * Opaque type referencing a working state (e.g. independent thread) which the client/user must use to initialise, dispatch, and access data. + */ +typedef struct McContext_T* McContext; + +/** + * @brief Event handle. + * + * Opaque type that can be used to identify async tasks. + */ +typedef struct McEvent_T* McEvent; + +typedef void McVoid; + +/** + * @brief 8 bit signed char. + * + * Integral type representing a 8-bit signed char. + */ +typedef int8_t McChar; + +/** + * @brief 32 bit signed integer. + * + * Integral type representing a 32-bit signed integer. + */ +typedef int32_t McInt32; + +/** + * @brief 32 bit unsigned integer. + * + * Integral type representing a 32-bit usigned integer. + */ +typedef uint32_t McUint32; + +/** + * @brief Bitfield type. + * + * Integral type representing a 32-bit bitfield for storing parameter values. + */ +typedef uint32_t McFlags; + +/** + * @brief Byte size type. + * + * Unsigned integer type of the result of the sizeof operator as well as the sizeof... operator and the alignof operator. + */ +typedef size_t McSize; + +/** + * @brief 32-bit unsigned type. + * + * Integral type representing an index value. + */ +typedef uint32_t McIndex; + +/** + * @brief 32-bit floating point type. + * + * Floating point type type representing a 32-bit real number value. + */ +typedef float McFloat; + +/** + * @brief 64-bit floating point type. + * + * Floating point type type representing a 64-bit real number value. + */ +typedef double McDouble; + +/** + * @brief 32-bit type. + * + * Integral type representing a boolean value (MC_TRUE or MC_FALSE). + */ +typedef uint32_t McBool; + +/** + * @brief Boolean constant for "true". + * + * Integral constant representing a boolean value evaluating to true. + */ +#define MC_TRUE (1) + +/** + * @brief Boolean constant for "false". + * + * Integral constant representing a boolean value evaluating to false. + */ +#define MC_FALSE (0) + +/** + * \enum McResult + * @brief API return codes + * + * This enum structure defines the possible return values of API functions (integer). The values identify whether a function executed successfully or returned with an error. + */ +typedef enum McResult { + MC_NO_ERROR = 0, /**< The function was successfully executed. */ + MC_INVALID_OPERATION = -(1 << 1), /**< An internal operation could not be executed successively. */ + MC_INVALID_VALUE = -(1 << 2), /**< An invalid value has been passed to the API. */ + MC_OUT_OF_MEMORY = -(1 << 3), /**< Memory allocation operation cannot allocate memory. */ + MC_RESULT_MAX_ENUM = 0xFFFFFFFF /**< Wildcard (match all) . */ +} McResult; + +/** + * \enum McConnectedComponentType + * @brief The possible types of connected components. + * + * This enum structure defines the possible types of connected components which can be queried from the API after a dispatch call. + */ +typedef enum McConnectedComponentType { + MC_CONNECTED_COMPONENT_TYPE_FRAGMENT = (1 << 0), /**< A connected component that originates from the source-mesh. */ + MC_CONNECTED_COMPONENT_TYPE_PATCH = (1 << 2), /**< A connected component that is originates from the cut-mesh. */ + MC_CONNECTED_COMPONENT_TYPE_SEAM = (1 << 3), /**< A connected component representing an input mesh (source-mesh or cut-mesh), but with additional vertices and edges that are introduced as as a result of the cut (i.e. the intersection contour/curve). */ + MC_CONNECTED_COMPONENT_TYPE_INPUT = (1 << 4), /**< A connected component that is copy of an input mesh (source-mesh or cut-mesh). Such a connected component may contain new faces and vertices, which will happen if MCUT internally performs polygon partitioning. Polygon partitioning occurs when an input mesh intersects the other without severing at least one edge. An example is splitting a tetrahedron (source-mesh) in two parts using one large triangle (cut-mesh): in this case, the large triangle would be partitioned into two faces to ensure that at least one of this cut-mesh are severed by the tetrahedron. This is what allows MCUT to reconnect topology after the cut. */ + MC_CONNECTED_COMPONENT_TYPE_ALL = 0xFFFFFFFF /**< Wildcard (match all) . */ +} McConnectedComponentType; + +/** + * \enum McFragmentLocation + * @brief The possible geometrical locations of a fragment (connected component), which are defined with-respect-to the cut-mesh. + * + * This enum structure defines the possible locations where a fragment can be relative to the cut-mesh. Note that the labels of 'above' or 'below' here are defined with-respect-to the winding-order (and hence, normal orientation) of the cut-mesh. + */ +typedef enum McFragmentLocation { + MC_FRAGMENT_LOCATION_ABOVE = 1 << 0, /**< Fragment is located above the cut-mesh. */ + MC_FRAGMENT_LOCATION_BELOW = 1 << 1, /**< Fragment is located below the cut-mesh. */ + MC_FRAGMENT_LOCATION_UNDEFINED = 1 << 2, /**< Fragment is located neither above nor below the cut-mesh. That is, it was produced due to a partial cut intersection. */ + MC_FRAGMENT_LOCATION_ALL = 0xFFFFFFFF /**< Wildcard (match all) . */ +} McFragmentLocation; + +/** + * \enum McFragmentSealType + * @brief Topological configurations of a fragment as defined with-respect-to hole-filling/sealing. + * + * This enum structure defines the possible configurations that a fragment will be in regarding the hole-filling process. Here, hole-filling refers to the stage/phase when holes produced by a cut are filled with a subset of polygons of the cut-mesh. + */ +typedef enum McFragmentSealType { + MC_FRAGMENT_SEAL_TYPE_COMPLETE = 1 << 0, /**< Holes are completely sealed (watertight). */ + MC_FRAGMENT_SEAL_TYPE_NONE = 1 << 2, /**< Holes are not sealed (gaping hole). */ + MC_FRAGMENT_SEAL_TYPE_ALL = 0xFFFFFFFF /**< Wildcard (match all) . */ +} McFragmentSealType; + +/** + * \enum McPatchLocation + * @brief The possible geometrical locations of a patch as defined with-respect-to the source-mesh. + * + * This enum structure defines the possible locations where a patch can be relative to the source-mesh. Note that the labels of 'inside' or 'outside' here are defined with-respect-to the winding-order (and hence, normal orientation) of the source-mesh. + */ +typedef enum McPatchLocation { + MC_PATCH_LOCATION_INSIDE = 1 << 0, /**< Patch is located on the interior of the source-mesh (used to seal holes). */ + MC_PATCH_LOCATION_OUTSIDE = 1 << 1, /**< Patch is located on the exterior of the source-mesh. Rather than hole-filling these patches seal from the outside so-as to extrude the cut.*/ + MC_PATCH_LOCATION_UNDEFINED = 1 << 2, /**< Patch is located neither on the interior nor exterior of the source-mesh. */ + MC_PATCH_LOCATION_ALL = 0xFFFFFFFF /**< Wildcard (match all) . */ +} McPatchLocation; + +/** + * \enum McSeamOrigin + * @brief The input mesh from which a seam is derived. + * + * This enum structure defines the possible origins of a seam connected component, which can be either the source-mesh or the cut-mesh. + */ +typedef enum McSeamOrigin { + MC_SEAM_ORIGIN_SRCMESH = 1 << 0, /**< Seam connected component is from the input source-mesh. */ + MC_SEAM_ORIGIN_CUTMESH = 1 << 1, /**< Seam connected component is from the input cut-mesh. */ + MC_SEAM_ORIGIN_ALL = 0xFFFFFFFF /**< Wildcard (match all) . */ +} McSeamOrigin; + +/** + * \enum McInputOrigin + * @brief The user-provided input mesh from which an input connected component is derived. + * + * This enum structure defines the possible origins of an input connected component, which can be either the source-mesh or the cut-mesh. + * Note: the number of elements (faces and vertices) in an input connected component will be the same [or greater] than the corresponding user-provided input mesh from which the respective connected component came from. The input connect component will contain more elements if MCUT detected an intersection configuration where one input mesh will create a hole in a face of the other input mesh but without severing it edges (and vice versa). + */ +typedef enum McInputOrigin { + MC_INPUT_ORIGIN_SRCMESH = 1 << 0, /**< Input connected component from the input source mesh.*/ + MC_INPUT_ORIGIN_CUTMESH = 1 << 1, /**< Input connected component from the input cut mesh. */ + MC_INPUT_ORIGIN_ALL = 0xFFFFFFFF /**< Wildcard (match all) . */ +} McInputOrigin; + +/** + * \enum McConnectedComponentData + * @brief Data that can be queried about a connected component. + * + * This enum structure defines the different types of data that are associated with a connected component and can be queried from the API after a dispatch call. + */ +typedef enum McConnectedComponentData { + // MC_CONNECTED_COMPONENT_DATA_VERTEX_COUNT = (1 << 0), /**< Number of vertices. */ + MC_CONNECTED_COMPONENT_DATA_VERTEX_FLOAT = (1 << 1), /**< List of vertex coordinates as an array of 32 bit floating-point numbers. */ + MC_CONNECTED_COMPONENT_DATA_VERTEX_DOUBLE = (1 << 2), /**< List of vertex coordinates as an array of 64 bit floating-point numbers. */ + // MC_CONNECTED_COMPONENT_DATA_FACE_COUNT = (1 << 4), /**< Number of faces. */ + MC_CONNECTED_COMPONENT_DATA_FACE = (1 << 5), /**< List of faces as an array of indices. Each face can also be understood as a "planar straight line graph" (PSLG), which is a collection of vertices and segments that lie on the same plane. Segments are edges whose endpoints are vertices in the PSLG.*/ + MC_CONNECTED_COMPONENT_DATA_FACE_SIZE = (1 << 6), /**< List of face sizes (vertices per face) as an array. */ + // MC_CONNECTED_COMPONENT_DATA_EDGE_COUNT = (1 << 7), /**< Number of edges. */ + MC_CONNECTED_COMPONENT_DATA_EDGE = (1 << 8), /**< List of edges as an array of indices. */ + MC_CONNECTED_COMPONENT_DATA_TYPE = (1 << 9), /**< The type of a connected component (See also: ::McConnectedComponentType.). */ + MC_CONNECTED_COMPONENT_DATA_FRAGMENT_LOCATION = (1 << 10), /**< The location of a fragment connected component with respect to the cut mesh (See also: ::McFragmentLocation). */ + MC_CONNECTED_COMPONENT_DATA_PATCH_LOCATION = (1 << 11), /**< The location of a patch with respect to the source mesh (See also: ::McPatchLocation).*/ + MC_CONNECTED_COMPONENT_DATA_FRAGMENT_SEAL_TYPE = (1 << 12), /**< The Hole-filling configuration of a fragment connected component (See also: ::McFragmentSealType). */ + MC_CONNECTED_COMPONENT_DATA_SEAM_VERTEX = (1 << 13), /**< List of unordered vertices as an array of indices. These vertices are introduced as a result of the cut and represent the intersection seam/path/contour. */ + MC_CONNECTED_COMPONENT_DATA_ORIGIN = (1 << 14), /**< The input mesh (source- or cut-mesh) from which a "seam" is derived (See also: ::McSeamOrigin). */ + MC_CONNECTED_COMPONENT_DATA_VERTEX_MAP = (1 << 15), /**< List of a subset of vertex indices from one of the input meshes (source-mesh or the cut-mesh). Each value will be the index of an input mesh vertex or MC_UNDEFINED_VALUE. This index-value corresponds to the connected component vertex at the accessed index. The value at index 0 of the queried array is the index of the vertex in the original input mesh. In order to clearly distinguish indices of the cut mesh from those of the source mesh, this index value corresponds to a cut mesh vertex index if it is great-than-or-equal-to the number of source-mesh vertices. Intersection points are mapped to MC_UNDEFINED_VALUE. The input mesh (i.e. source- or cut-mesh) will be deduced by the user from the type of connected component with which the information is queried. The input connected component (source-mesh or cut-mesh) that is referred to must be one stored internally by MCUT (i.e. a connected component queried from the API via ::McInputOrigin), to ensure consistency with any modification done internally by MCUT. */ + MC_CONNECTED_COMPONENT_DATA_FACE_MAP = (1 << 16), /**< List of a subset of face indices from one of the input meshes (source-mesh or the cut-mesh). Each value will be the index of an input mesh face. This index-value corresponds to the connected component face at the accessed index. Example: the value at index 0 of the queried array is the index of the face in the original input mesh. Note that all faces are mapped to a defined value. In order to clearly distinguish indices of the cut mesh from those of the source mesh, an input-mesh face index value corresponds to a cut-mesh vertex-index if it is great-than-or-equal-to the number of source-mesh faces. The input connected component (source-mesh or cut-mesh) that is referred to must be one stored internally by MCUT (i.e. a connected component queried from the API via ::McInputOrigin), to ensure consistency with any modification done internally by MCUT. */ + // incidence and adjacency information + MC_CONNECTED_COMPONENT_DATA_FACE_ADJACENT_FACE = (1 << 17), /**< List of adjacent faces (their indices) per face.*/ + MC_CONNECTED_COMPONENT_DATA_FACE_ADJACENT_FACE_SIZE = (1 << 18), /**< List of adjacent-face-list sizes (number of adjacent faces per face).*/ + MC_CONNECTED_COMPONENT_DATA_FACE_TRIANGULATION = (1 << 19), /**< List of 3*N triangulated face indices, where N is the number of triangles that are produced using a [Constrained] Delaunay triangulation. Such a triangulation is similar to a Delaunay triangulation, but each (non-triangulated) face segment is present as a single edge in the triangulation. A constrained Delaunay triangulation is not truly a Delaunay triangulation. Some of its triangles might not be Delaunay, but they are all constrained Delaunay. */ + MC_CONNECTED_COMPONENT_DATA_FACE_TRIANGULATION_MAP = (1 << 20), /**< List of a subset of face indices from one of the input meshes (source-mesh or the cut-mesh). Each value will be the index of an input mesh face. This index-value corresponds to the connected-component face at the accessed index. Example: the value at index 0 of the queried array is the index of the face in the original input mesh. Note that all triangulated-faces are mapped to a defined value. In order to clearly distinguish indices of the cut mesh from those of the source mesh, an input-mesh face index value corresponds to a cut-mesh vertex-index if it is great-than-or-equal-to the number of source-mesh faces. The input connected component (source-mesh or cut-mesh) that is referred to must be one stored internally by MCUT (i.e. a connected component queried from the API via ::McInputOrigin), to ensure consistency with any modification done internally by MCUT. */ + // TODO MC_CONNECTED_COMPONENT_DATA_FACE_TRIANGULATION_CDT = (1<<20) /**< List of 3*N triangulated face indices, where N is the number of triangles that are produced using a [Conforming] Delaunay triangulation. A conforming Delaunay triangulation (CDT) of a PSLG (i.e. a face of in the given connected component) is a true Delaunay triangulation in which each PSLG segment/edge may have been subdivided into several edges by the insertion of additional vertices, called Steiner points. Steiner points are necessary to allow the segments to exist in the mesh while maintaining the Delaunay property. This Delaunay property follows from the definition of a "Delaunay triangulation": the Delaunay triangulation is the triangulation such that, if you circumscribe a circle around every triangle, none of those circles will contain any other points. Alternatively, the Delaunay triangulation is the triangulation that “maximizes the minimum angle in all of the triangles.”. Steiner points are not inserted to meet constraints on the minimum angle and maximum triangle area. MCUT computes a CDT of a given connected component starting only from the faces that have more than three vertices. Neighbouring (triangle) faces will also be processed (i.e. refined) if their incident edges are split as a result of performing CDT on the current face. */ +} McConnectedComponentData; + +/** + * \enum McDebugSource + * @brief Source of a debug log message. + * + * This enum structure defines the sources from which a message in a debug log may originate. + */ +typedef enum McDebugSource { + MC_DEBUG_SOURCE_API = 1 << 0, /**< messages generated by usage of the MCUT API. */ + MC_DEBUG_SOURCE_KERNEL = 1 << 1, /**< messages generated by internal logic implementing the kernel, which is responsible for resolving mesh intersections and connectivity. */ + MC_DEBUG_SOURCE_FRONTEND = 1 << 2, /**< messages generated by internal logic implementing the front-end, which is responsible for pre- and post-processing of meshes and connected components. */ + MC_DEBUG_SOURCE_IGNORE = 1 << 3, /**< This flag is passed to the API function ::mcDebugMessageControl (second parameter). Its effect is to ignore the instruction to enable/disable the corresponding variant of debug output w.r.t. being logged into the provided callback*/ + MC_DEBUG_SOURCE_ALL = 0xFFFFFFFF /**< Wildcard (match all) . */ +} McDebugSource; + +/** + * \enum McDebugType + * @brief Type of debug messages. + * + * This enum structure defines the types of debug a message relating to an error. + */ +typedef enum McDebugType { + MC_DEBUG_TYPE_ERROR = 1 << 0, /**< Explicit error message.*/ + MC_DEBUG_TYPE_DEPRECATED_BEHAVIOR = 1 << 1, /**< Attempted use of deprecated features.*/ + MC_DEBUG_TYPE_OTHER = 1 << 2, /**< Other types of messages,.*/ + MC_DEBUG_TYPE_IGNORE = 1 << 3, /**< This flag is passed to the API function ::mcDebugMessageControl (third parameter). Its effect is to ignore the instruction to enable/disable the corresponding variant of debug output w.r.t. being logged into the provided callback*/ + MC_DEBUG_TYPE_ALL = 0xFFFFFFFF /**< Wildcard (match all) . */ +} McDebugType; + +/** + * \enum McDebugSeverity + * @brief Severity levels of messages. + * + * This enum structure defines the different severities of error: low, medium or high severity messages. + */ +typedef enum McDebugSeverity { + MC_DEBUG_SEVERITY_HIGH = 1 << 0, /**< All MCUT Errors, mesh conversion/parsing errors, or undefined behavior.*/ + MC_DEBUG_SEVERITY_MEDIUM = 1 << 1, /**< Major performance warnings, debugging warnings, or the use of deprecated functionality.*/ + MC_DEBUG_SEVERITY_LOW = 1 << 2, /**< Redundant state change, or unimportant undefined behavior.*/ + MC_DEBUG_SEVERITY_NOTIFICATION = 1 << 3, /**< Anything that isn't an error or performance issue.*/ + MC_DEBUG_SEVERITY_IGNORE = 1 << 3, /**< This flag is passed to the API function ::mcDebugMessageControl (forth parameter). Its effect is to ignore the instruction to enable/disable the corresponding variant of debug output w.r.t. being logged into the provided callback*/ + MC_DEBUG_SEVERITY_ALL = 0xFFFFFFFF /**< Match all (wildcard).*/ +} McDebugSeverity; + +/** + * \enum McContextCreationFlags + * @brief Context creation flags. + * + * This enum structure defines the flags with which a context can be created. + */ +typedef enum McContextCreationFlags { + MC_DEBUG = (1 << 0), /**< Enable debug mode (message logging etc.).*/ + MC_OUT_OF_ORDER_EXEC_MODE_ENABLE = (1 << 1), /**< Determines whether the commands queued in the context-queue are executed in-order or out-of-order. If set, the commands in the context-queue are executed out-of-order. Otherwise, commands are executed in-order..*/ + MC_PROFILING_ENABLE = (1 << 2) /**< Enable or disable profiling of commands in the context-queue. If set, the profiling of commands is enabled. Otherwise profiling of commands is disabled. See ::mcGetEventProfilingInfo for more information. */ +} McContextCreationFlags; + +/** + * \enum McDispatchFlags + * @brief Dispatch configuration flags. + * + * This enum structure defines the flags indicating MCUT is to interprete input data, and execute the cutting pipeline. + */ +typedef enum McDispatchFlags { + MC_DISPATCH_VERTEX_ARRAY_FLOAT = (1 << 0), /**< Interpret the input mesh vertices as arrays of 32-bit floating-point numbers.*/ + MC_DISPATCH_VERTEX_ARRAY_DOUBLE = (1 << 1), /**< Interpret the input mesh vertices as arrays of 64-bit floating-point numbers.*/ + MC_DISPATCH_REQUIRE_THROUGH_CUTS = (1 << 2), /**< Require that all intersection paths/curves/contours partition the source-mesh into two (or more) fully disjoint parts. Otherwise, ::mcDispatch is a no-op. This flag enforces the requirement that only through-cuts are valid cuts i.e it disallows partial cuts. NOTE: This flag may not be used with ::MC_DISPATCH_FILTER_FRAGMENT_LOCATION_UNDEFINED.*/ + MC_DISPATCH_INCLUDE_VERTEX_MAP = (1 << 3), /**< Compute connected-component-to-input mesh vertex-id maps. See also: ::MC_CONNECTED_COMPONENT_DATA_VERTEX_MAP */ + MC_DISPATCH_INCLUDE_FACE_MAP = (1 << 4), /**< Compute connected-component-to-input mesh face-id maps. . See also: ::MC_CONNECTED_COMPONENT_DATA_FACE_MAP and :MC_CONNECTED_COMPONENT_DATA_FACE_TRIANGULATION_MAP*/ + // + MC_DISPATCH_FILTER_FRAGMENT_LOCATION_ABOVE = (1 << 5), /**< Compute fragments that are above the cut-mesh.*/ + MC_DISPATCH_FILTER_FRAGMENT_LOCATION_BELOW = (1 << 6), /**< Compute fragments that are below the cut-mesh.*/ + MC_DISPATCH_FILTER_FRAGMENT_LOCATION_UNDEFINED = (1 << 7), /**< Compute fragments that are partially cut i.e. neither above nor below the cut-mesh. NOTE: This flag may not be used with ::MC_DISPATCH_REQUIRE_THROUGH_CUTS. */ + // + MC_DISPATCH_FILTER_FRAGMENT_SEALING_INSIDE = (1 << 8), /**< Compute fragments that are fully sealed (hole-filled) on the interior. */ + MC_DISPATCH_FILTER_FRAGMENT_SEALING_OUTSIDE = (1 << 9), /**< Compute fragments that are fully sealed (hole-filled) on the exterior. */ + // + MC_DISPATCH_FILTER_FRAGMENT_SEALING_NONE = (1 << 10), /**< Compute fragments that are not sealed (holes not filled).*/ + // + MC_DISPATCH_FILTER_PATCH_INSIDE = (1 << 11), /**< Compute patches on the inside of the source mesh (those used to fill holes).*/ + MC_DISPATCH_FILTER_PATCH_OUTSIDE = (1 << 12), /**< Compute patches on the outside of the source mesh.*/ + // + MC_DISPATCH_FILTER_SEAM_SRCMESH = (1 << 13), /**< Compute the seam which is the same as the source-mesh but with new edges placed along the cut path. Note: a seam from the source-mesh will only be computed if the dispatch operation computes a complete (through) cut.*/ + MC_DISPATCH_FILTER_SEAM_CUTMESH = (1 << 14), /**< Compute the seam which is the same as the cut-mesh but with new edges placed along the cut path. Note: a seam from the cut-mesh will only be computed if the dispatch operation computes a complete (through) cut.*/ + // + MC_DISPATCH_FILTER_ALL = ( // + MC_DISPATCH_FILTER_FRAGMENT_LOCATION_ABOVE | // + MC_DISPATCH_FILTER_FRAGMENT_LOCATION_BELOW | // + MC_DISPATCH_FILTER_FRAGMENT_LOCATION_UNDEFINED | // + MC_DISPATCH_FILTER_FRAGMENT_SEALING_INSIDE | // + MC_DISPATCH_FILTER_FRAGMENT_SEALING_OUTSIDE | // + MC_DISPATCH_FILTER_FRAGMENT_SEALING_NONE | // + MC_DISPATCH_FILTER_PATCH_INSIDE | // + MC_DISPATCH_FILTER_PATCH_OUTSIDE | // + MC_DISPATCH_FILTER_SEAM_SRCMESH | // + MC_DISPATCH_FILTER_SEAM_CUTMESH), /**< Keep all connected components resulting from the dispatched cut. */ + /** + * Allow MCUT to perturb the cut-mesh if the inputs are not in general position. + * + * MCUT is formulated for inputs in general position. Here the notion of general position is defined with + respect to the orientation predicate (as evaluated on the intersecting polygons). Thus, a set of points + is in general position if no three points are collinear and also no four points are coplanar. + + MCUT uses the "GENERAL_POSITION_VIOLATION" flag to inform of when to use perturbation (of the + cut-mesh) so as to bring the input into general position. In such cases, the idea is to solve the cutting + problem not on the given input, but on a nearby input. The nearby input is obtained by perturbing the given + input. The perturbed input will then be in general position and, since it is near the original input, + the result for the perturbed input will hopefully still be useful. This is justified by the fact that + the task of MCUT is not to decide whether the input is in general position but rather to make perturbation + on the input (if) necessary within the available precision of the computing device. */ + MC_DISPATCH_ENFORCE_GENERAL_POSITION = (1 << 15) +} McDispatchFlags; + +/** + * \enum McEventOperationExevStatus + * @brief Flags for querying the operation status. + * + * This enum structure defines the flags which are used for querying the execution status of an operation associated with an event. + */ +typedef enum McEventCommandExecStatus { + MC_SUBMITTED = 1 << 0, /**< enqueued operation has been submitted by the client thread to the internal "device". */ + MC_RUNNING = 1 << 1, /**< Operation is currently running. */ + MC_COMPLETE = 1 << 2 /**< The operation has completed. */ +} McEventCommandExecStatus; + +/** + * \enum McCommandType + * @brief Flags for identifying event commands. + * + * This enum structure defines the flags which are used for identifying the MCUT command associated with an event. + */ +typedef enum McCommandType { + MC_COMMAND_DISPATCH = 1 << 0, /**< From McEnqueueDispatch. */ + MC_COMMAND_GET_CONNECTED_COMPONENTS = 1 << 1, /**< From McEnqueueGetConnectedComponents. */ + MC_COMMAND_GET_CONNECTED_COMPONENT_DATA = 1 << 2, /**< From McEnqueueGetConnectedComponentData. */ + MC_COMMAND_USER = 1 << 3, /**< From user application. */ + MC_COMMAND_UKNOWN +} McCommandType; + +/** + * \enum McQueryFlags + * @brief Flags for querying fixed API state. + * + * This enum structure defines the flags which are used to query for specific information about the state of the API and/or a given context. + */ +typedef enum McQueryFlags { + MC_CONTEXT_FLAGS = 1 << 0, /**< Flags used to create a context.*/ + MC_DONT_CARE = 1 << 1, /**< wildcard.*/ + MC_EVENT_RUNTIME_EXECUTION_STATUS = 1 << 2, /**< Error/status code associated with the runtime of the asynchronous/non-blocking part of the associated task. See also ::McResult */ + MC_EVENT_TIMESTAMP_SUBMIT = 1 << 3, /**< An unsigned 64-bit value that describes the current internal time counter in nanoseconds when the MCUT API function identified by event that has been enqueued is submitted by the internal scheduler for execution.*/ + MC_EVENT_TIMESTAMP_START = 1 << 4, /**< An unsigned 64-bit value that describes the current internal time counter in nanoseconds when the MCUT API function identified by event starts execution.*/ + MC_EVENT_TIMESTAMP_END = 1 << 5, /**< An unsigned 64-bit value that describes the current internal time counter in nanoseconds when the MCUT API function identified by event has finished execution. */ + MC_EVENT_COMMAND_EXECUTION_STATUS = 1 << 6, /**< the execution status of the command identified by event. See also ::McEventCommandExecStatus */ + MC_EVENT_CONTEXT = 1 << 7, /**< The context associated with event. */ + MC_EVENT_COMMAND_TYPE = 1 << 8, /**< The command associated with event. Can be one of the values in :: */ + MC_MAX_DEBUG_MESSAGE_LENGTH = 1 << 9 +} McQueryFlags; + +/** + * + * @brief Debug callback function signature type. + * + * The callback function should have this prototype (in C), or be otherwise compatible with such a prototype. + */ +typedef void(MCAPI_PTR* pfn_mcDebugOutput_CALLBACK)( + McDebugSource source, + McDebugType type, + unsigned int id, + McDebugSeverity severity, + size_t length, + const char* message, + const McVoid* userParam); + +/** + * + * @brief Event callback function signature type. + * + * The callback function should have this prototype (in C), or be otherwise compatible with such a prototype. + */ +typedef void(MCAPI_PTR* pfn_McEvent_CALLBACK)(McEvent event, McVoid* data); + +/** @brief Create an MCUT context. + * + * This method creates a context object, which is a handle used by a client application to control the API state and access data. + * + * @param [out] pContext a pointer to the allocated context handle + * @param [in] flags bitfield containing the context creation flags + * + * An example of usage: + * @code + * McContext myContext = MC_NULL_HANDLE; + * McResult err = mcCreateContext(&myContext, MC_NULL_HANDLE); + * if(err != MC_NO_ERROR) + * { + * // deal with error + * } + * @endcode + * + * @return Error code. + * + * Error codes + * - MC_NO_ERROR + * -# proper exit + * - MC_INVALID_VALUE + * -# \p pContext is NULL + * -# Failure to allocate resources + * -# \p flags defines an invalid bitfield. + */ +extern MCAPI_ATTR McResult MCAPI_CALL mcCreateContext( + McContext* pContext, McFlags flags); + +/** @brief Create an MCUT context object with N helper threads. + * + * This method creates a context object with an additonal number of threads that + * assist the internal scheduler with compute workloads. The returned a handle + * shall be used by a client applications to control the API state and access data. + * + * Unless otherwise stated, MCUT runs asynchronously with the user application. + * Non-blocking commands issued with a given context run on a conceptual "device", + * which executes independently of the client application. This device is + * associated with one logical thread by default. Two logical threads will be + * associated with the device if MC_OUT_OF_ORDER_EXEC_MODE_ENABLE is provided as + * a flag. + * + * Having one logical device thread means that MCUT commands shall execute in + * the order that they are provided by a client application. And if two logical + * threads are associated with the device then multiple API commands may also run + * concurrently subject to their dependency list. + * + * Concurrent commands share the pool of N helper threads. + * + * @param [out] pContext a pointer to the allocated context handle + * @param [in] flags bitfield containing the context creation flags + * @param [in] helperThreadCount Number of helper-threads to assist device-threads with parallel work. + * + * An example of usage: + * @code + * McContext myContext = MC_NULL_HANDLE; + * McResult err = mcCreateContextWithHelpers(&myContext, MC_OUT_OF_ORDER_EXEC_MODE_ENABLE, 2); + * if(err != MC_NO_ERROR) + * { + * // deal with error + * } + * @endcode + * + * @return Error code. + * + * Error codes + * - MC_NO_ERROR + * -# proper exit + * - MC_INVALID_VALUE + * -# \p pContext is NULL + * -# Failure to allocate resources + * -# \p flags defines an invalid bitfield. + */ +extern MCAPI_ATTR McResult MCAPI_CALL mcCreateContextWithHelpers( + McContext* pOutContext, McFlags contextFlags, uint32_t helperThreadCount); + +/** @brief Specify a callback to receive debugging messages from the MCUT library. + * + * ::mcDebugMessageCallback sets the current debug output callback function to the function whose address is + * given in callback. + * + * This function is defined to have the same calling convention as the MCUT API functions. In most cases + * this is defined as MCAPI_ATTR, although it will vary depending on platform, language and compiler. + * + * Each time a debug message is generated the debug callback function will be invoked with source, type, + * and severity associated with the message, and length set to the length of debug message whose + * character string is in the array pointed to by message userParam will be set to the value passed in + * the userParam parameter to the most recent call to mcDebugMessageCallback. + * + * @param[in] context The context handle that was created by a previous call to mcCreateContext. + * @param[in] cb The address of a callback function that will be called when a debug message is generated. + * @param[in] userParam A user supplied pointer that will be passed on each invocation of callback. + * + * An example of usage: + * @code + * // define my callback (with type pfn_mcDebugOutput_CALLBACK) + * void mcDebugOutput(McDebugSource source, McDebugType type, unsigned int id, McDebugSeverity severity,size_t length, const char* message,const McVoid* userParam) + * { + * // do stuff + * } + * + * // ... + * + * McVoid* someData = NULL; + * McResult err = mcDebugMessageCallback(myContext, mcDebugOutput, someData); + * if(err != MC_NO_ERROR) + * { + * // deal with error + * } + * @endcode + * + * @return Error code. + * + * Error codes + * - MC_NO_ERROR + * -# proper exit + * - MC_INVALID_VALUE + * -# \p pContext is NULL or \p pContext is not an existing context. + * -# \p cb is NULL. + * + */ +extern MCAPI_ATTR McResult MCAPI_CALL mcDebugMessageCallback( + McContext context, + pfn_mcDebugOutput_CALLBACK cb, + const McVoid* userParam); + +/** + * @brief Returns messages stored in an internal log. + * + * @param context The context handle that was created by a previous call to @see ::mcCreateContext. + * @param count The number of debug messages to retrieve from the log. + * @param bufSize The size of the buffer whose address is given by \p messageLog. + * @param sources The address of an array of variables to receive the sources of the retrieved messages. + * @param types The address of an array of variables to receive the types of the retrieved messages. + * @param severities The address of an array of variables to receive the severites of the retrieved messages. + * @param lengths The address of an array of variables to receive the lengths of the received messages. + * @param messageLog The address of an array of variables to receive the lengths of the received messages.. + * + * mcGetDebugMessageLog retrieves messages from the debug message log. A maximum + * of \p count messages are retrieved from the log. If \p sources is not NULL then + * the source of each message is written into up to count elements of the array. + * If \p types is not NULL then the type of each message is written into up to + * count elements of the array. If \p severities is not NULL then the severity + * of each message is written into up to count elements of the array. If \p lengths + * is not NULL then the length of each message is written into up to count + * elements of the array. + * + * \p messageLog specifies the address of a character array into which the debug + * messages will be written. Each message will be concatenated onto the array + * starting at the first element of \p messageLog. \p bufSize specifies the size + * of the array \p messageLog. If a message will not fit into the remaining space + * in \p messageLog then the function terminates and returns the number of messages + * written so far, which may be zero. + * + * If ::mcGetDebugMessageLog returns zero then no messages are present in the + * debug log, or there was not enough space in \p messageLog to retrieve the + * first message in the queue. If \p messageLog is NULL then no messages are + * written and the value of \p bufSize is ignored.. + * + * Here is an example of code that can get the first N messages: + * + * @code {.c++} + * McResult GetFirstNMessages(McContext context, McUint32 numMsgs) + * { + * McSize maxMsgLen = 0; + * mcGet(MC_MAX_DEBUG_MESSAGE_LENGTH, &maxMsgLen); + * std::vector msgData(numMsgs * maxMsgLen); + * std::vector sources(numMsgs); + * std::vector types(numMsgs); + * std::vector severities(numMsgs); + * std::vector lengths(numMsgs); + * McUint32 numFound; + * + * McResult err = mcGetDebugMessageLog(context, numMsgs, numMsgs, &sources[0], &types[0], &severities[0], &lengths[0], &msgData[0], &numFound); + * + * if(err != MC_NO_ERROR) + * { + * // deal with error ... + * } + * + * sources.resize(numFound); + * types.resize(numFound); + * severities.resize(numFound); + * ids.resize(numFound); + * lengths.resize(numFound); + * std::vector messages; + * messages.reserve(numFound); + * + * std::vector::iterator currPos = msgData.begin(); + * + * for(size_t msg = 0; msg < lengths.size(); ++msg) + * { + * messages.push_back(std::string(currPos, currPos + lengths[msg] - 1)); + * currPos = currPos + lengths[msg]; + * } + * } + * @endcode + * + * @return error code + */ +extern MCAPI_ATTR McResult MCAPI_CALL mcGetDebugMessageLog( + McContext context, + McUint32 count, McSize bufSize, + McDebugSource *sources, McDebugType *types, McDebugSeverity *severities, + McSize *lengths, McChar *messageLog, McUint32* numFetched); + +/** + * Control the reporting of debug messages in a debug context. + * + * @param[in] context The context handle that was created by a previous call to @see ::mcCreateContext. + * @param[in] source The source of debug messages to enable or disable. + * @param[in] type The type of debug messages to enable or disable. + * @param[in] severity The severity of debug messages to enable or disable. + * @param[in] enabled A Boolean flag determining whether the selected messages should be enabled or disabled. + * + * ::mcDebugMessageControl controls the reporting of debug messages generated by a debug context. The parameters + * source, type and severity form a filter to select messages from the pool of potential messages generated by + * the MCUT library. + * + * \p source may be #MC_DEBUG_SOURCE_API, #MC_DEBUG_SOURCE_KERNEL to select messages + * generated by usage of the MCUT API, the MCUT kernel or by the input, respectively. It may also take the + * value #MC_DEBUG_SOURCE_ALL. If source is not #MC_DEBUG_SOURCE_ALL then only messages whose source matches + * source will be referenced. + * + * \p type may be one of #MC_DEBUG_TYPE_ERROR, #MC_DEBUG_TYPE_DEPRECATED_BEHAVIOR, or #MC_DEBUG_TYPE_OTHER to indicate + * the type of messages describing MCUT errors, attempted use of deprecated features, and other types of messages, + * respectively. It may also take the value #MC_DONT_CARE. If type is not #MC_DEBUG_TYPE_ALL then only messages whose + * type matches type will be referenced. + * + * \p severity may be one of #MC_DEBUG_SEVERITY_LOW, #MC_DEBUG_SEVERITY_MEDIUM, or #MC_DEBUG_SEVERITY_HIGH to + * select messages of low, medium or high severity messages or to #MC_DEBUG_SEVERITY_NOTIFICATION for notifications. + * It may also take the value #MC_DEBUG_SEVERITY_ALL. If severity is not #MC_DEBUG_SEVERITY_ALL then only + * messages whose severity matches severity will be referenced. + * + * If \p enabled is true then messages that match the filter formed by source, type and severity are enabled. + * Otherwise, those messages are disabled. + * + * + * An example of usage: + * @code + * // ... typically after setting debug callback with ::mcDebugMessageCallback + * McResult err = mcDebugMessageControl(myContext, MC_DEBUG_SOURCE_ALL, MC_DEBUG_TYPE_ALL, MC_DEBUG_SEVERITY_ALL, true); + * if(err != MC_NO_ERROR) + * { + * // deal with error + * } + * @endcode + * + * @return Error code. + * + * Error codes + * - MC_NO_ERROR + * -# proper exit + * - MC_INVALID_VALUE + * -# \p pContext is NULL or \p pContext is not an existing context. + * -# \p source is not a value define in ::McDebugSource. + * -# \p type is not a value define in ::McDebugType. + * -# \p severity is not a value define in ::McDebugSeverity. + */ +extern MCAPI_ATTR McResult MCAPI_CALL mcDebugMessageControl( + McContext context, + McDebugSource source, + McDebugType type, + McDebugSeverity severity, + bool enabled); + +/** + * @brief To create a user event object, call the function + * + * @param event Event handle + * @param context must be a valid MCUT context. + * + * User events allow applications to enqueue commands that wait on a user event + * to finish before the command is executed by the MCUT engine + * + * mcCreateUserEvent returns a valid non-zero event object and return code is + * set to MC_NO_ERROR if the user event object is created successfully. + * Otherwise, it returns a NULL value with an error values. + * + * @return MCAPI_ATTR + */ +extern MCAPI_ATTR McResult MCAPI_CALL mcCreateUserEvent( + McEvent* event, + McContext context); + +/** + * @brief To set the execution status of a user event object, call the function + * + * @param event is a user event object created using mcCreateUserEvent. + * @param execution_status specifies the new execution status to be set and can + * be MC_​COMPLETE or an ::McResult value to indicate an error. Thus a negative + * integer value causes all enqueued commands that wait on this user event to + * be terminated. mcSetUserEventStatus can only be called once to change the + * execution status of event. + * + * If there are enqueued commands with user events in the event_wait_list + * argument of mcEnqueue* commands, the user must ensure that the status of + * these user events being waited on are set using mcSetUserEventStatus before + * any MCUT APIs that release MCUT objects except for event objects are called; + * otherwise the behavior is undefined. + * + */ +extern MCAPI_ATTR McResult MCAPI_CALL mcSetUserEventStatus( + McEvent event, + McInt32 execution_status); + +/** + * @brief Returns information about the event object.. + * + * @param[in] event Specifies the event object being queried.. + * @param[in] info Specifies the information to query. See:: + * @param[in] bytes Specifies the size in bytes of memory pointed to by `pMem`. This size must be >= size of the return type as described in the table below.. + * @param[in] pMem A pointer to memory where the appropriate result being queried is returned. If param_value is NULL, it is ignored. + * @param[in] pNumBytes Returns the actual size in bytes of data copied to `pMem`. If `pNumBytes` is NULL, it is ignored. + * + * Using mcGetEventInfo to determine if a operation identified by event has finished + * execution (i.e. MC_EVENT_COMMAND_EXECUTION_STATUS returns MC_COMPLETE) is not + * a synchronization point. There are no guarantees that the memory objects being + * modified by the operation associated with event will be visible to other + * enqueued commands. + * + * @return Error code. + * + * Error codes + * - ::MC_NO_ERROR + * -# proper exit + * - ::MC_INVALID_VALUE + * -# \p event is not a valid object + * -# \p bytes is greater than zero but \p pMem is null. + * -# \p bytes is zero but \p pMem is not null. + * -# \p bytes is zero \p pMem is null and \p pNumBytes is null. + * -# \p bytes is incompatible with \p info. + */ +extern MCAPI_ATTR McResult MCAPI_CALL mcGetEventInfo(const McEvent event, McFlags info, McSize bytes, McVoid* pMem, McSize* pNumBytes); +/** + * @brief Registers a user callback function. + * + * @param[in] event A valid event object returned by the MCUT API. + * @param[in] pfn_notify The event callback function that can be registered by the application.. + * @param[in] user_data Will be passed as the user_data argument when pfn_notify is called. user_data can be NULL. + * + * The registered callback function will be called when the execution of the + * command associated with event is complete. + * Each call to clSetEventCallback registers the specified user callback function + * and replaces any previously specified callback. + * An enqueued callback shall be called before the event object is destroyed. + * The callback must return promptly. The behavior of calling expensive system + * routines, or calling blocking MCUT operations etc. in a callback is undefined. + * It is the application's responsibility to ensure that the callback function is + * thread-safe. + * + * An example of usage: + * @code + * McResult err = mcSetEventCallback(ev, someFunctionPointer, NULL); + * if(err != MC_NO_ERROR) + * { + * // deal with error + * } + * @endcode + * + * @return Error code. + * + * Error codes + * - ::MC_NO_ERROR + * -# proper exit + * - ::MC_INVALID_VALUE + * -# \p event is not a valid object + * -# \p eventCallback is NULL + */ +extern MCAPI_ATTR McResult MCAPI_CALL mcSetEventCallback( + McEvent event, + pfn_McEvent_CALLBACK pfn_notify, + McVoid* user_data); + +/** + * @brief Execute a cutting operation with two meshes - the source mesh, and the cut mesh. + * + * @param[in] context The context handle that was created by a previous call to ::mcCreateContext. + * @param[in] flags The flags indicating how to interprete input data and configure the execution. + * @param[in] pSrcMeshVertices The array of vertex coordinates (i.e. in xyzxyzxyz... format) of the source mesh. + * @param[in] pSrcMeshFaceIndices The array of vertex indices of the faces (polygons) in the source mesh. + * @param[in] pSrcMeshFaceSizes The array of the sizes (in terms of number of vertex indices) of the faces in the source mesh. + * @param[in] numSrcMeshVertices The number of vertices in the source mesh. + * @param[in] numSrcMeshFaces The number of faces in the source mesh. + * @param[in] pCutMeshVertices The array of vertex coordinates (i.e. in xyzxyzxyz... format) of the cut mesh. + * @param[in] pCutMeshFaceIndices The array of vertex indices of the faces (polygons) in the cut mesh. + * @param[in] pCutMeshFaceSizes The array of the sizes (in terms of number of vertex indices) of the faces in the cut mesh. + * @param[in] numCutMeshVertices The number of vertices in the cut mesh. + * @param[in] numCutMeshFaces The number of faces in the cut mesh. + * @param[in] numEventsInWaitlist Number of events in the waitlist. + * @param[in] pEventWaitList Events that need to complete before this particular command can be executed. + * @param[out] pEvent Returns an event object that identifies this particular command and can be used to query or queue a wait for this particular command to complete. pEvent can be NULL in which case it will not be possible for the application to query the status of this command or queue a wait for this command to complete. If the pEventWaitList and the event arguments are not NULL, the pEvent argument should not refer to an element of the pEventWaitList array. + * + * This function specifies the two mesh objects to operate on. The 'source mesh' is the mesh to be cut + * (i.e. partitioned) along intersection paths prescribed by the 'cut mesh'. + * + * If pEventWaitList is NULL, then this particular command does not wait on any event to complete. If pEventWaitList + * is NULL, numEventsInWaitlist must be 0. If pEventWaitList is not NULL, the list of events pointed to + * by pEventWaitList must be valid and numEventsInWaitlist must be greater than 0. The events specified in + * pEventWaitList act as synchronization points. The memory associated with pEventWaitList can be reused or + * freed after the function returns. + * + * An example of usage: + * @code + * McEvent event; + * //... + * McResult err = mcEnqueueDispatch(..., 0, NULL, &event); + * if(err != MC_NO_ERROR) + * { + * // deal with error... + * } + * + * err = mcWaitForEvents(1, &event); + * if(err != MC_NO_ERROR) + * { + * // deal with error... + * } + * @endcode + * + * @return Error code. + * + * Error codes + * - ::MC_NO_ERROR + * -# proper exit + * - ::MC_INVALID_VALUE + * -# \p pContext is NULL or \p pContext is not an existing context. + * -# \p flags contains an invalid value. + * -# A vertex index in \p pSrcMeshFaceIndices or \p pCutMeshFaceIndices is out of bounds. + * -# Invalid face/polygon definition (vertex list) implying non-manifold mesh \p pSrcMeshFaceIndices or \p pCutMeshFaceIndices is out of bounds. + * -# The MC_DISPATCH_VERTEX_ARRAY_... value has not been specified in \p flags + * -# An input mesh contains multiple connected components. + * -# \p pSrcMeshVertices is NULL. + * -# \p pSrcMeshFaceIndices is NULL. + * -# \p pSrcMeshFaceSizes is NULL. + * -# \p numSrcMeshVertices is less than three. + * -# \p numSrcMeshFaces is less than one. + * -# \p pCutMeshVertices is NULL. + * -# \p pCutMeshFaceIndices is NULL. + * -# \p pCutMeshFaceSizes is NULL. + * -# \p numCutMeshVertices is less than three. + * -# \p numCutMeshFaces is less than one. + * -# \p numEventsInWaitlist Number of events in the waitlist. + * -# \p pEventWaitList events that need to complete before this particular command can be executed + * -# ::MC_DISPATCH_ENFORCE_GENERAL_POSITION is not set and: 1) Found two intersecting edges between the source-mesh and the cut-mesh and/or 2) An intersection test between a face and an edge failed because an edge vertex only touches (but does not penetrate) the face, and/or 3) One or more source-mesh vertices are colocated with one or more cut-mesh vertices. + * - ::MC_OUT_OF_MEMORY + * -# Insufficient memory to perform operation. + */ +extern MCAPI_ATTR McResult MCAPI_CALL mcEnqueueDispatch( + const McContext context, + McFlags dispatchFlags, + const McVoid* pSrcMeshVertices, + const uint32_t* pSrcMeshFaceIndices, + const uint32_t* pSrcMeshFaceSizes, + uint32_t numSrcMeshVertices, + uint32_t numSrcMeshFaces, + const McVoid* pCutMeshVertices, + const uint32_t* pCutMeshFaceIndices, + const uint32_t* pCutMeshFaceSizes, + uint32_t numCutMeshVertices, + uint32_t numCutMeshFaces, + uint32_t numEventsInWaitlist, + const McEvent* pEventWaitList, + McEvent* pEvent); + +/** + * @brief Blocking version of ::mcEnqueueDispatch. + */ +extern MCAPI_ATTR McResult MCAPI_CALL mcDispatch( + McContext context, + McFlags flags, + const McVoid* pSrcMeshVertices, + const uint32_t* pSrcMeshFaceIndices, + const uint32_t* pSrcMeshFaceSizes, + uint32_t numSrcMeshVertices, + uint32_t numSrcMeshFaces, + const McVoid* pCutMeshVertices, + const uint32_t* pCutMeshFaceIndices, + const uint32_t* pCutMeshFaceSizes, + uint32_t numCutMeshVertices, + uint32_t numCutMeshFaces); + +/** + * @brief Return the value of a selected parameter. + * + * @param[in] context The context handle that was created by a previous call to ::mcCreateContext. + * @param[in] info Information object being queried. ::McQueryFlags + * @param[in] bytes Size in bytes of memory pointed to by \p pMem. This size must be great than or equal to the return type size of data type queried. + * @param[out] pMem Pointer to memory where the appropriate result being queried is returned. If \p pMem is NULL, it is ignored. + * @param[out] pNumBytes returns the actual size in bytes of data being queried by info. If \p pNumBytes is NULL, it is ignored. + * + * + * An example of usage: + * @code + * McSize numBytes = 0; + * McFlags contextFlags; + * McResult err = mcGetInfo(context, MC_CONTEXT_FLAGS, 0, nullptr, &numBytes); + * if(err != MC_NO_ERROR) + * { + * // deal with error + * } + * + * err = mcGetInfo(context, MC_CONTEXT_FLAGS, numBytes, &contextFlags, nullptr); + * if(err != MC_NO_ERROR) + * { + * // deal with error + * } + * @endcode + * @return Error code. + * + * Error codes + * - MC_NO_ERROR + * -# proper exit + * - MC_INVALID_VALUE + * -# \p pContext is NULL or \p pContext is not an existing context. + * -# \p bytes is greater than the returned size of data type queried + * + * @note Event synchronisation is not implemented. + */ +extern MCAPI_ATTR McResult MCAPI_CALL mcGetInfo( + const McContext context, + McFlags info, + McSize bytes, + McVoid* pMem, + McSize* pNumBytes); + +/** + * @brief Query the connected components available in a context. + * + * This function will return an array of connected components matching the given description of flags. + * + * @param[in] context The context handle + * @param[in] connectedComponentType The type(s) of connected component sought. See also ::McConnectedComponentType. + * @param[in] numEntries The number of ::McConnectedComponent entries that can be added to \p pConnComps. If \p pConnComps is not NULL, \p numEntries must be the number of elements in \p pConnComps. + * @param[out] pConnComps Returns a list of connected components found. The ::McConnectedComponentType values returned in \p pConnComps can be used + * to identify a specific connected component. If \p pConnComps is NULL, this argument is ignored. The number of connected components returned + * is the minimum of the value specified by \p numEntries or the number of connected components whose type matches \p connectedComponentType. + * @param[out] numConnComps Returns the number of connected components available that match \p connectedComponentType. If \p numConnComps is NULL, + * this argument is ignored. + * @param[in] numEventsInWaitlist Number of events in the waitlist. + * @param[in] pEventWaitList Events that need to complete before this particular command can be executed. + * @param[out] pEvent Returns an event object that identifies this particular command and can be used to query or queue a wait for this particular command to complete. pEvent can be NULL in which case it will not be possible for the application to query the status of this command or queue a wait for this command to complete. If the pEventWaitList and the event arguments are not NULL, the pEvent argument should not refer to an element of the pEventWaitList array. + * + * If pEventWaitList is NULL, then this particular command does not wait on any event to complete. If pEventWaitList + * is NULL, numEventsInWaitlist must be 0. If pEventWaitList is not NULL, the list of events pointed to + * by pEventWaitList must be valid and numEventsInWaitlist must be greater than 0. The events specified in + * pEventWaitList act as synchronization points. The memory associated with pEventWaitList can be reused or + * freed after the function returns. + * + * An example of usage: + * @code + * uint32_t numConnComps = 0; + * McConnectedComponent* pConnComps; + * McEvent ev = MC_NULL_HANDLE; + * McResult err = err = mcGetConnectedComponents(myContext, MC_CONNECTED_COMPONENT_TYPE_ALL, 0, NULL, &numConnComps, 0, NULL, &ev); + * if(err != MC_NO_ERROR) + * { + * // deal with error + * } + * + * if (numConnComps == 0) { + * // ... + * } + * + * pConnComps = (McConnectedComponent*)malloc(sizeof(McConnectedComponent) * numConnComps); + * + * err = mcGetConnectedComponents(myContext, MC_CONNECTED_COMPONENT_TYPE_ALL, numConnComps, pConnComps, NULL, 1, &ev, NULL); + * if(err != MC_NO_ERROR) + * { + * // deal with error + * } + * ... + * @endcode + * + * @return Error code. + * + * Error codes + * - MC_NO_ERROR + * -# proper exit + * - MC_INVALID_VALUE + * -# \p pContext is NULL or \p pContext is not an existing context. + * -# \p connectedComponentType is not a value in ::McConnectedComponentType. + * -# \p numConnComps and \p pConnComps are both NULL. + * -# \p numConnComps is zero and \p pConnComps is not NULL. + * -# \p + */ +MCAPI_ATTR McResult MCAPI_CALL mcEnqueueGetConnectedComponents( + const McContext context, + const McConnectedComponentType connectedComponentType, + const uint32_t numEntries, + McConnectedComponent* pConnComps, + uint32_t* numConnComps, + uint32_t numEventsInWaitlist, + const McEvent* pEventWaitList, + McEvent* pEvent); + +/** + * @brief Blocking version of ::mcEnqueueGetConnectedComponents. + */ +extern MCAPI_ATTR McResult MCAPI_CALL mcGetConnectedComponents( + const McContext context, + const McConnectedComponentType connectedComponentType, + const uint32_t numEntries, + McConnectedComponent* pConnComps, + uint32_t* numConnComps); + +/** + * @brief Query specific information about a connected component. + * + * @param[in] context The context handle that was created by a previous call to ::mcCreateContext. + * @param[in] connCompId A connected component returned by ::mcGetConnectedComponents whose data is to be read. + * @param[in] flags An enumeration constant that identifies the connected component information being queried. + * @param[in] bytes Specifies the size in bytes of memory pointed to by \p flags. + * @param[out] pMem A pointer to memory location where appropriate values for a given \p flags will be returned. If \p pMem is NULL, it is ignored. + * @param[out] pNumBytes Returns the actual size in bytes of data being queried by \p flags. If \p pNumBytes is NULL, it is ignored. + * + * The connected component queries described in the ::McConnectedComponentData should return the same information for a connected component returned by ::mcGetConnectedComponents. + * If pEventWaitList is NULL, then this particular command does not wait on any event to complete. If pEventWaitList + * is NULL, numEventsInWaitlist must be 0. If pEventWaitList is not NULL, the list of events pointed to + * by pEventWaitList must be valid and numEventsInWaitlist must be greater than 0. The events specified in + * pEventWaitList act as synchronization points. The memory associated with pEventWaitList can be reused or + * freed after the function returns. + * + * An example of usage: + * @code + * McSize numBytes = 0; + * McEvent bytesQueryEvent=MC_NULL_HANDLE; + * McEvent ev = MC_NULL_HANDLE; + * McResult err = mcEnqueueGetConnectedComponentData(myContext, connCompHandle, MC_CONNECTED_COMPONENT_DATA_VERTEX_DOUBLE, 0, NULL, &numBytes, 0, NULL, &bytesQueryEvent, 0, NULL, &ev); + * if(err != MC_NO_ERROR) + * { + * // deal with error + * } + * + * double* pVertices = (double*)malloc(numBytes); + * // this operation shall happen AFTER the preceding operation associated with "bytesQueryEvent". + * err = mcEnqueueGetConnectedComponentData(context, connCompHandle, MC_CONNECTED_COMPONENT_DATA_VERTEX_DOUBLE, numBytes, (McVoid*)pVertices, NULL, 1, &bytesQueryEvent, NULL, 1, &ev, NULL); + * if(err != MC_NO_ERROR) + * { + * // deal with error + * } + * @endcode + * + * @return Error code. + * + * Error codes + * - MC_NO_ERROR + * -# proper exit + * - MC_INVALID_VALUE + * -# \p pContext is NULL or \p pContext is not an existing context. + * -# \p connectedComponentType is not a value in ::McConnectedComponentType. + * -# \p pMem and \p pNumBytes are both NULL (or not NULL). + * -# \p bytes is zero and \p pMem is not NULL. + * -# \p flag is MC_CONNECTED_COMPONENT_DATA_VERTEX_MAP when \p context dispatch flags did not include flag MC_DISPATCH_INCLUDE_VERTEX_MAP + * -# \p flag is MC_CONNECTED_COMPONENT_DATA_FACE_MAP when \p context dispatch flags did not include flag MC_DISPATCH_INCLUDE_FACE_MAP + * -# \p numEventsInWaitlist Number of events in the waitlist. + * -# \p pEventWaitList events that need to complete before this particular command can be executed + */ +extern MCAPI_ATTR McResult MCAPI_CALL mcEnqueueGetConnectedComponentData( + const McContext context, + const McConnectedComponent connCompId, + McFlags queryFlags, + McSize bytes, + McVoid* pMem, + McSize* pNumBytes, + uint32_t numEventsInWaitlist, + const McEvent* pEventWaitList, + McEvent* pEvent); + +/** + * @brief Blocking version of ::mcEnqueueGetConnectedComponents. + */ +extern MCAPI_ATTR McResult MCAPI_CALL mcGetConnectedComponentData( + const McContext context, + const McConnectedComponent connCompId, + McFlags flags, + McSize bytes, + McVoid* pMem, + McSize* pNumBytes); + +/** + * @brief Waits on the user thread for commands identified by event objects to complete. + * + * @param[in] numEventsInWaitlist Number of events to wait for + * @param[in] pEventWaitList List of events to wait for + * + * Waits on the user thread for commands identified by event objects in \p pEventWaitList to complete. + * The events specified in \p pEventWaitList act as synchronization points. + * + * @return Error code. + * -# proper exit + * - MC_INVALID_VALUE + * -# \p numEventsInWaitlist is greater than 0 and \p pEventWaitList is NULL (and vice versa). + * -# If an event object in \p pEventWaitList is not a valid event object. + */ +extern MCAPI_ATTR McResult MCAPI_CALL mcWaitForEvents( + uint32_t numEventsInWaitlist, + const McEvent* pEventWaitList); + +/** + * @brief Decrements the event reference count. + * + * @param numEvents Number of event objects to release + * @param pEvents Array of event objects to release + * + * The event object is deleted once the reference count becomes zero, the specific + * command identified by this event has completed (or terminated) and there are + * no commands in the internal-queue of a context that require a wait for this + * event to complete. + * + * @return Error code. + * -# proper exit + * - MC_INVALID_VALUE + * -# \p numEvents is greater than 0 and \p pEvents is NULL (and vice versa). + * -# If an event object in \p pEvents is not a valid event object. + */ +extern MCAPI_ATTR McResult MCAPI_CALL mcReleaseEvents( + uint32_t numEvents, + const McEvent* pEvents); + +/** + * @brief To release the memory of a connected component, call this function. + * + * If \p numConnComps is zero and \p pConnComps is NULL, the memory of all connected components associated with the context is freed. + * + * @param[in] context The context handle that was created by a previous call to ::mcCreateContext. + * @param[in] numConnComps Number of connected components in \p pConnComps whose memory to release. + * @param[in] pConnComps The connected components whose memory will be released. + * + * An example of usage: + * @code + * McResult err = mcReleaseConnectedComponents(myContext, pConnComps, numConnComps); + * // OR (delete all connected components in context) + * //McResult err = mcReleaseConnectedComponents(myContext, NULL, 0); + * if(err != MC_NO_ERROR) + * { + * // deal with error + * } + * @endcode + * + * @return Error code. + * + * Error codes + * - MC_NO_ERROR + * -# proper exit + * - MC_INVALID_VALUE + * -# \p pContext is NULL or \p pContext is not an existing context. + * -# \p numConnComps is zero and \p pConnComps is not NULL (and vice versa). + * + */ +extern MCAPI_ATTR McResult MCAPI_CALL mcReleaseConnectedComponents( + const McContext context, + uint32_t numConnComps, + const McConnectedComponent* pConnComps); + +/** +* @brief To release the memory of a context, call this function. +* +* This function ensures that all the state attached to context (such as unreleased connected components, and threads) are released, and the memory is deleted. + +* @param[in] context The context handle that was created by a previous call to ::mcCreateContext. +* +* + * An example of usage: + * @code + * McResult err = mcReleaseContext(myContext); + * if(err != MC_NO_ERROR) + * { + * // deal with error + * } + * @endcode +* +* @return Error code. +* +* Error codes +* - MC_NO_ERROR +* -# proper exit +* - MC_INVALID_VALUE +* -# \p pContext is NULL or \p pContext is not an existing context. +*/ +extern MCAPI_ATTR McResult MCAPI_CALL mcReleaseContext( + McContext context); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif // #ifndef MCUT_API_H_ diff --git a/src/mcut/include/mcut/platform.h b/src/mcut/include/mcut/platform.h new file mode 100644 index 0000000000..8485b3c9fb --- /dev/null +++ b/src/mcut/include/mcut/platform.h @@ -0,0 +1,109 @@ +/** + * 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: + * (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 + */ + +/** + * @file platform.h + * @author Floyd M. Chitalu + * @date 1 Jan 2021 + * @brief File containing platform-specific types and definitions. + * + * This header file defines platform specific directives and integral type + * declarations. + * Platforms should define these so that MCUT users call MCUT commands + * with the same calling conventions that the MCUT implementation expects. + * + * MCAPI_ATTR - Placed before the return type in function declarations. + * Useful for C++11 and GCC/Clang-style function attribute syntax. + * MCAPI_CALL - Placed after the return type in function declarations. + * Useful for MSVC-style calling convention syntax. + * MCAPI_PTR - Placed between the '(' and '*' in function pointer types. + * + * Function declaration: MCAPI_ATTR void MCAPI_CALL mcFunction(void); + * Function pointer type: typedef void (MCAPI_PTR *PFN_mcFunction)(void); + * + */ + +#ifndef MC_PLATFORM_H_ +#define MC_PLATFORM_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif // __cplusplus + +/** @file */ + +#if defined(_WIN32) + +#ifdef MCUT_SHARED_LIB + +#if defined(MCUT_EXPORT_SHARED_LIB_SYMBOLS) + //** Symbol visibilty */ + #define MCAPI_ATTR __declspec(dllexport) +#else + //** Symbol visibilty */ + #define MCAPI_ATTR __declspec(dllimport) +#endif + +#else // MCUT_SHARED_LIB + + //** Symbol visibilty */ +#define MCAPI_ATTR + +#endif // MCUT_SHARED_LIB + + //** Function calling convention */ + #define MCAPI_CALL __stdcall + //** Function pointer-type declaration helper */ + #define MCAPI_PTR MCAPI_CALL +#else // #if defined(_WIN32) + + //** Symbol visibilty */ + #define MCAPI_ATTR __attribute__((visibility("default"))) + //** Function calling convention */ + #define MCAPI_CALL + //** Function pointer-type declaration helper */ + #define MCAPI_PTR +#endif // #if defined(_WIN32) + +#include // standard type definitions + +#if !defined(MC_NO_STDINT_H) + #if defined(_MSC_VER) && (_MSC_VER < 1600) + typedef signed __int8 int8_t; + typedef unsigned __int8 uint8_t; + typedef signed __int16 int16_t; + typedef unsigned __int16 uint16_t; + typedef signed __int32 int32_t; + typedef unsigned __int32 uint32_t; + typedef signed __int64 int64_t; + typedef unsigned __int64 uint64_t; + #else + #include // integer types + #endif +#endif // !defined(MC_NO_STDINT_H) + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif \ No newline at end of file diff --git a/src/mcut/source/bvh.cpp b/src/mcut/source/bvh.cpp new file mode 100644 index 0000000000..925642188e --- /dev/null +++ b/src/mcut/source/bvh.cpp @@ -0,0 +1,1148 @@ +/** + * 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: + * (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 +#include +#include "mcut/internal/timer.h" + +#include // see: if it is possible to remove thsi header +#include + +#ifdef _MSC_VER +#include +#define __builtin_popcount __popcnt + +// https://stackoverflow.com/questions/355967/how-to-use-msvc-intrinsics-to-get-the-equivalent-of-this-gcc-code +unsigned int __inline clz_(unsigned int value) +{ + unsigned long leading_zero = 0; + + if (_BitScanReverse(&leading_zero, value)) { + return 31 - leading_zero; + } else { + // Same remarks as above + return 32; + } +} + +#endif + +#ifndef CHAR_BIT +#define CHAR_BIT 8 +#endif + +#if defined(USE_OIBVH) +// count leading zeros in 32 bit bitfield +unsigned int clz(unsigned int x) // stub +{ +#ifdef _MSC_VER + return clz_(x); +#else + return __builtin_clz(x); // only tested with gcc!!! +#endif +} + +// next power of two from x +int next_power_of_two(int x) +{ + x--; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + x++; + return x; +} + +// check if "x" is a power of two +bool is_power_of_two(int x) +{ + return (x != 0) && !(x & (x - 1)); +} + +// compute log-base-2 of "x" +int ilog2(unsigned int x) +{ + return sizeof(unsigned int) * CHAR_BIT - clz(x) - 1; +} + +// compute index (0...N-1) of the leaf level from the number of leaves +int get_leaf_level_from_real_leaf_count(const int t) +{ + const int np2 = next_power_of_two(t); // todo + const int tLeafLev = ilog2(np2); + return tLeafLev; +} + +// compute tree-level index from implicit index of a node +int get_level_from_implicit_idx(const int bvhNodeImplicitIndex) +{ + return ilog2(bvhNodeImplicitIndex + 1); +} + +// compute previous power of two +unsigned int flp2(unsigned int x) // prev pow2 +{ + x = x | (x >> 1); + x = x | (x >> 2); + x = x | (x >> 4); + x = x | (x >> 8); + x = x | (x >> 16); + return x - (x >> 1); +} + +// compute size of of Oi-BVH give number of triangles +int get_ostensibly_implicit_bvh_size(const int t) +{ + return 2 * t - 1 + __builtin_popcount(next_power_of_two(t) - t); +} + +// compute left-most node on a given level +int get_level_leftmost_node(const int node_level) +{ + return (1 << node_level) - 1; +} + +// compute right-most leaf node in tree +int get_rightmost_real_leaf(const int bvhLeafLevelIndex, const int num_real_leaf_nodes_in_bvh) +{ + return (get_level_leftmost_node(bvhLeafLevelIndex) + num_real_leaf_nodes_in_bvh) - 1; +} + +// check if node is a "real node" +bool is_real_implicit_tree_node_id(const int bvhNodeImplicitIndex, const int num_real_leaf_nodes_in_bvh) +{ + + const int t = num_real_leaf_nodes_in_bvh; + // const int q = bvhNodeImplicitIndex; // queried node + const int li = get_leaf_level_from_real_leaf_count(t); + const int i = get_rightmost_real_leaf(li, t); + const int lq = get_level_from_implicit_idx(bvhNodeImplicitIndex); + const int p = (int)((1.0f / (1 << (li - lq))) + ((float)i / (1 << (li - lq))) - 1); + + return bvhNodeImplicitIndex <= p || p == 0; // and p is not the root +} + +// get the right most real node on a given tree level +int get_level_rightmost_real_node( + const int rightmostRealLeafNodeImplicitIndex, + const int bvhLeafLevelIndex, + const int ancestorLevelIndex) +{ + using namespace std; + const int level_dist = (bvhLeafLevelIndex - ancestorLevelIndex); + const int implicit_index_of_ancestor = (int)((1.0f / (1 << level_dist)) + ((float)rightmostRealLeafNodeImplicitIndex / (1 << level_dist)) - 1); + return implicit_index_of_ancestor; +} + +// compute implicit index of a node's ancestor +int get_node_ancestor( + const int nodeImplicitIndex, + const int nodeLevelIndex, + const int ancestorLevelIndex) +{ + using namespace std; + const int levelDistance = nodeLevelIndex - ancestorLevelIndex; + return (int)((1.0f / (1 << levelDistance)) + ((float)nodeImplicitIndex / (1 << levelDistance)) - 1); /*trunc((1.0f / pow(bvhDegree, level_dist)) + (rightmostRealLeafNodeImplicitIndex / pow(bvhDegree, level_dist)) - 1)*/ +} + +// calculate linear memory index of a real node +int get_node_mem_index( + const int nodeImplicitIndex, + const int leftmostImplicitIndexOnNodeLevel, + const int bvh_data_base_offset, + const int rightmostRealNodeImplicitIndexOnNodeLevel) +{ + return bvh_data_base_offset + get_ostensibly_implicit_bvh_size((rightmostRealNodeImplicitIndexOnNodeLevel - leftmostImplicitIndexOnNodeLevel) + 1) - 1 - (rightmostRealNodeImplicitIndexOnNodeLevel - nodeImplicitIndex); +} + +// Expands a 10-bit integer into 30 bits by inserting 2 zeros after each bit. +unsigned int expandBits(unsigned int v) +{ + v = (v * 0x00010001u) & 0xFF0000FFu; + v = (v * 0x00000101u) & 0x0F00F00Fu; + v = (v * 0x00000011u) & 0xC30C30C3u; + v = (v * 0x00000005u) & 0x49249249u; + return v; +}; + +// Calculates a 30-bit Morton code for the given 3D point located within the unit cube [0,1]. +unsigned int morton3D(float x, float y, float z) +{ + x = std::fmin(std::fmax(x * 1024.0f, 0.0f), 1023.0f); + y = std::fmin(std::fmax(y * 1024.0f, 0.0f), 1023.0f); + z = std::fmin(std::fmax(z * 1024.0f, 0.0f), 1023.0f); + + unsigned int xx = expandBits((unsigned int)x); + unsigned int yy = expandBits((unsigned int)y); + unsigned int zz = expandBits((unsigned int)z); + + return (xx * 4 + yy * 2 + zz); +}; + +void build_oibvh( + #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + thread_pool& pool, + #endif + const hmesh_t& mesh, + std::vector>& bvhAABBs, + std::vector& bvhLeafNodeFaces, + std::vector>& face_bboxes, + const double& slightEnlargmentEps) +{ + SCOPED_TIMER(__FUNCTION__); + + const int meshFaceCount = mesh.number_of_faces(); + const int bvhNodeCount = get_ostensibly_implicit_bvh_size(meshFaceCount); + + // compute mesh-face bounding boxes and their centers + // :::::::::::::::::::::::::::::::::::::::::::::::::: + + face_bboxes.resize(meshFaceCount); //, bounding_box_t()); + std::vector face_bbox_centers(meshFaceCount, vec3()); +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + { + auto fn_compute_face_bbox_data = [&](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 int faceIdx = static_cast(*f); + std::vector vertices_on_face = mesh.get_vertices_around_face(*f); + + // for each vertex on face + for (std::vector::const_iterator v = vertices_on_face.cbegin(); v != vertices_on_face.cend(); ++v) { + const vec3 coords = mesh.vertex(*v); + face_bboxes[faceIdx].expand(coords); + } + + bounding_box_t& bbox = face_bboxes[faceIdx]; + + if (slightEnlargmentEps > double(0.0)) { + bbox.enlarge(slightEnlargmentEps); + } + + // calculate bbox center + face_bbox_centers[*f] = (bbox.minimum() + bbox.maximum()) / 2; + } + }; + + parallel_for( + pool, + mesh.faces_begin(), + mesh.faces_end(), + fn_compute_face_bbox_data); + } +#else + + // for each face in mesh + for (face_array_iterator_t f = mesh.faces_begin(); f != mesh.faces_end(); ++f) { + const int faceIdx = static_cast(*f); + const std::vector vertices_on_face = mesh.get_vertices_around_face(*f); + + // for each vertex on face + for (std::vector::const_iterator v = vertices_on_face.cbegin(); v != vertices_on_face.cend(); ++v) { + const vec3 coords = mesh.vertex(*v); + face_bboxes[faceIdx].expand(coords); + } + + bounding_box_t& bbox = face_bboxes[faceIdx]; + + if (slightEnlargmentEps > double(0.0)) { + bbox.enlarge(slightEnlargmentEps); + } + + // calculate bbox center + face_bbox_centers[*f] = (bbox.minimum() + bbox.maximum()) / 2; + } +#endif + // compute mesh bounding box + // ::::::::::::::::::::::::: + + bvhAABBs.resize(bvhNodeCount); + bounding_box_t& meshBbox = bvhAABBs.front(); // root bounding box + +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + { + std::mutex bbox_expansion_mtx; + auto fn_compute_mesh_bbox = [&](vertex_array_iterator_t block_start_, vertex_array_iterator_t block_end_) { + bounding_box_t meshBbox_local; + for (vertex_array_iterator_t v = block_start_; v != block_end_; ++v) { + const vec3& coords = mesh.vertex(*v); + meshBbox_local.expand(coords); + } + + std::lock_guard lock(bbox_expansion_mtx); + meshBbox.expand(meshBbox_local); + }; + + parallel_for( + pool, + mesh.vertices_begin(), + mesh.vertices_end(), + fn_compute_mesh_bbox); + } +#else + // for each vertex in mesh + for (vertex_array_iterator_t v = mesh.vertices_begin(); v != mesh.vertices_end(); ++v) { + const vec3& coords = mesh.vertex(*v); + meshBbox.expand(coords); + } +#endif + + // compute morton codes + // :::::::::::::::::::: + + std::vector> bvhLeafNodeDescriptors(meshFaceCount, std::pair()); + +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + { + auto fn_compute_morton_codes = [&](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 uint32_t faceIdx = static_cast(*f); + + const vec3& face_aabb_centre = SAFE_ACCESS(face_bbox_centers, faceIdx); + const vec3 offset = face_aabb_centre - meshBbox.minimum(); + const vec3 dims = meshBbox.maximum() - meshBbox.minimum(); + + const unsigned int mortion_code = morton3D( + static_cast(offset.x() / dims.x()), + static_cast(offset.y() / dims.y()), + static_cast(offset.z() / dims.z())); + + const uint32_t idx = (uint32_t)std::distance(mesh.faces_begin(), f); // NOTE: mesh.faces_begin() may not be the actual beginning internally + bvhLeafNodeDescriptors[idx].first = *f; + bvhLeafNodeDescriptors[idx].second = mortion_code; + } + }; + + parallel_for( + pool, + mesh.faces_begin(), + mesh.faces_end(), + fn_compute_morton_codes); + } +#else + for (face_array_iterator_t f = mesh.faces_begin(); f != mesh.faces_end(); ++f) { + const uint32_t faceIdx = static_cast(*f); + + const vec3& face_aabb_centre = SAFE_ACCESS(face_bbox_centers, faceIdx); + const vec3 offset = face_aabb_centre - meshBbox.minimum(); + const vec3 dims = meshBbox.maximum() - meshBbox.minimum(); + + const unsigned int mortion_code = morton3D( + static_cast(offset.x() / dims.x()), + static_cast(offset.y() / dims.y()), + static_cast(offset.z() / dims.z())); + + const uint32_t idx = (uint32_t)std::distance(mesh.faces_begin(), f); // NOTE: mesh.faces_begin() may not be the actual beginning internally + bvhLeafNodeDescriptors[idx].first = *f; + bvhLeafNodeDescriptors[idx].second = mortion_code; + } +#endif + // sort faces according to morton codes + + // TODO: make parallel + std::sort( + bvhLeafNodeDescriptors.begin(), + bvhLeafNodeDescriptors.end(), + [](const std::pair& a, const std::pair& b) { + return a.second < b.second; + }); + + bvhLeafNodeFaces.resize(meshFaceCount); + + const int leaf_level_index = get_leaf_level_from_real_leaf_count(meshFaceCount); + const int leftmost_real_node_on_leaf_level = get_level_leftmost_node(leaf_level_index); + const int rightmost_real_leaf = get_rightmost_real_leaf(leaf_level_index, meshFaceCount); + const int rightmost_real_node_on_leaf_level = get_level_rightmost_real_node(rightmost_real_leaf, leaf_level_index, leaf_level_index); +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + { + auto fn_save_leaf_node_info = [&](std::vector>::const_iterator block_start_, std::vector>::const_iterator block_end_) { + for (std::vector>::const_iterator it = block_start_; it != block_end_; ++it) { + const uint32_t index_on_leaf_level = (uint32_t)std::distance(bvhLeafNodeDescriptors.cbegin(), it); + + bvhLeafNodeFaces[index_on_leaf_level] = it->first; + + const int implicit_idx = leftmost_real_node_on_leaf_level + index_on_leaf_level; + const int memory_idx = get_node_mem_index( + implicit_idx, + leftmost_real_node_on_leaf_level, + 0, + rightmost_real_node_on_leaf_level); + + const bounding_box_t& face_bbox = face_bboxes[(uint32_t)it->first]; + bvhAABBs[memory_idx] = face_bbox; + } + }; + + parallel_for( + pool, + bvhLeafNodeDescriptors.cbegin(), + bvhLeafNodeDescriptors.cend(), + fn_save_leaf_node_info); + } +#else + // save sorted leaf node bvhAABBs and their corrresponding face id + for (std::vector>::const_iterator it = bvhLeafNodeDescriptors.cbegin(); it != bvhLeafNodeDescriptors.cend(); ++it) { + const uint32_t index_on_leaf_level = (uint32_t)std::distance(bvhLeafNodeDescriptors.cbegin(), it); + + bvhLeafNodeFaces[index_on_leaf_level] = it->first; + + const int implicit_idx = leftmost_real_node_on_leaf_level + index_on_leaf_level; + const int memory_idx = get_node_mem_index( + implicit_idx, + leftmost_real_node_on_leaf_level, + 0, + rightmost_real_node_on_leaf_level); + + const bounding_box_t& face_bbox = face_bboxes[(uint32_t)it->first]; + bvhAABBs[memory_idx] = face_bbox; + } +#endif + // construct internal-node bounding boxes + // :::::::::::::::::::::::::::::::::::::: + + // for each level in the oi-bvh tree (starting from the penultimate level) + for (int level_index = leaf_level_index - 1; level_index >= 0; --level_index) { + + const int rightmost_real_node_on_level = get_level_rightmost_real_node(rightmost_real_leaf, leaf_level_index, level_index); + const int leftmost_real_node_on_level = get_level_leftmost_node(level_index); + const int number_of_real_nodes_on_level = (rightmost_real_node_on_level - leftmost_real_node_on_level) + 1; + +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + { + // allows us to pretend that we can use an iterator over the nodes + std::vector level_nodes_placeholder(number_of_real_nodes_on_level); + auto fn_compute_bvh_level_nodes = [&](std::vector::const_iterator block_start_, std::vector::const_iterator block_end_) { + uint32_t base_offset = std::distance(level_nodes_placeholder.cbegin(), block_start_); + uint32_t counter = 0; + for (std::vector::const_iterator it = block_start_; it != block_end_; ++it) { + const int level_node_idx_iter = base_offset + (counter++); + + const int node_implicit_idx = leftmost_real_node_on_level + level_node_idx_iter; + const int left_child_implicit_idx = (node_implicit_idx * 2) + 1; + const int right_child_implicit_idx = (node_implicit_idx * 2) + 2; + const bool is_penultimate_level = (level_index == (leaf_level_index - 1)); + const int rightmost_real_node_on_child_level = get_level_rightmost_real_node(rightmost_real_leaf, leaf_level_index, level_index + 1); + const int leftmost_real_node_on_child_level = get_level_leftmost_node(level_index + 1); + const bool right_child_exists = (right_child_implicit_idx <= rightmost_real_node_on_child_level); + + bounding_box_t node_bbox; + + if (is_penultimate_level) { // both children are leaves + + const int left_child_index_on_level = left_child_implicit_idx - leftmost_real_node_on_child_level; + const fd_t& left_child_face = SAFE_ACCESS(bvhLeafNodeFaces, left_child_index_on_level); + const bounding_box_t& left_child_bbox = SAFE_ACCESS(face_bboxes, left_child_face); + + node_bbox.expand(left_child_bbox); + + if (right_child_exists) { + const int right_child_index_on_level = right_child_implicit_idx - leftmost_real_node_on_child_level; + const fd_t& right_child_face = SAFE_ACCESS(bvhLeafNodeFaces, right_child_index_on_level); + const bounding_box_t& right_child_bbox = SAFE_ACCESS(face_bboxes, right_child_face); + node_bbox.expand(right_child_bbox); + } + } else { // remaining internal node levels + + const int left_child_memory_idx = get_node_mem_index( + left_child_implicit_idx, + leftmost_real_node_on_child_level, + 0, + rightmost_real_node_on_child_level); + const bounding_box_t& left_child_bbox = SAFE_ACCESS(bvhAABBs, left_child_memory_idx); + + node_bbox.expand(left_child_bbox); + + if (right_child_exists) { + const int right_child_memory_idx = get_node_mem_index( + right_child_implicit_idx, + leftmost_real_node_on_child_level, + 0, + rightmost_real_node_on_child_level); + const bounding_box_t& right_child_bbox = SAFE_ACCESS(bvhAABBs, right_child_memory_idx); + node_bbox.expand(right_child_bbox); + } + } + + const int node_memory_idx = get_node_mem_index( + node_implicit_idx, + leftmost_real_node_on_level, + 0, + rightmost_real_node_on_level); + + SAFE_ACCESS(bvhAABBs, node_memory_idx) = node_bbox; + } + }; + + parallel_for( + pool, + level_nodes_placeholder.cbegin(), + level_nodes_placeholder.cend(), + fn_compute_bvh_level_nodes); + } +#else + // for each node on the current level + for (int level_node_idx_iter = 0; level_node_idx_iter < number_of_real_nodes_on_level; ++level_node_idx_iter) { + + const int node_implicit_idx = leftmost_real_node_on_level + level_node_idx_iter; + const int left_child_implicit_idx = (node_implicit_idx * 2) + 1; + const int right_child_implicit_idx = (node_implicit_idx * 2) + 2; + const bool is_penultimate_level = (level_index == (leaf_level_index - 1)); + const int rightmost_real_node_on_child_level = get_level_rightmost_real_node(rightmost_real_leaf, leaf_level_index, level_index + 1); + const int leftmost_real_node_on_child_level = get_level_leftmost_node(level_index + 1); + const bool right_child_exists = (right_child_implicit_idx <= rightmost_real_node_on_child_level); + + bounding_box_t node_bbox; + + if (is_penultimate_level) { // both children are leaves + + const int left_child_index_on_level = left_child_implicit_idx - leftmost_real_node_on_child_level; + const fd_t& left_child_face = SAFE_ACCESS(bvhLeafNodeFaces, left_child_index_on_level); + const bounding_box_t& left_child_bbox = SAFE_ACCESS(face_bboxes, left_child_face); + + node_bbox.expand(left_child_bbox); + + if (right_child_exists) { + const int right_child_index_on_level = right_child_implicit_idx - leftmost_real_node_on_child_level; + const fd_t& right_child_face = SAFE_ACCESS(bvhLeafNodeFaces, right_child_index_on_level); + const bounding_box_t& right_child_bbox = SAFE_ACCESS(face_bboxes, right_child_face); + node_bbox.expand(right_child_bbox); + } + } else { // remaining internal node levels + + const int left_child_memory_idx = get_node_mem_index( + left_child_implicit_idx, + leftmost_real_node_on_child_level, + 0, + rightmost_real_node_on_child_level); + const bounding_box_t& left_child_bbox = SAFE_ACCESS(bvhAABBs, left_child_memory_idx); + + node_bbox.expand(left_child_bbox); + + if (right_child_exists) { + const int right_child_memory_idx = get_node_mem_index( + right_child_implicit_idx, + leftmost_real_node_on_child_level, + 0, + rightmost_real_node_on_child_level); + const bounding_box_t& right_child_bbox = SAFE_ACCESS(bvhAABBs, right_child_memory_idx); + node_bbox.expand(right_child_bbox); + } + } + + const int node_memory_idx = get_node_mem_index( + node_implicit_idx, + leftmost_real_node_on_level, + 0, + rightmost_real_node_on_level); + + SAFE_ACCESS(bvhAABBs, node_memory_idx) = node_bbox; + } // for each real node on level +#endif + } // for each internal level +} + +void intersectOIBVHs( + std::map>& ps_face_to_potentially_intersecting_others, + const std::vector>& srcMeshBvhAABBs, + const std::vector& srcMeshBvhLeafNodeFaces, + const std::vector>& cutMeshBvhAABBs, + const std::vector& cutMeshBvhLeafNodeFaces) +{ + TIMESTACK_PUSH(__FUNCTION__); + // simultaneuosly traverse both BVHs to find intersecting pairs + std::queue traversalQueue; + traversalQueue.push({ 0, 0 }); // left = sm BVH; right = cm BVH + + const int numSrcMeshFaces = (int)srcMeshBvhLeafNodeFaces.size(); + MCUT_ASSERT(numSrcMeshFaces >= 1); + const int numCutMeshFaces = (int)cutMeshBvhLeafNodeFaces.size(); + MCUT_ASSERT(numCutMeshFaces >= 1); + + const int sm_bvh_leaf_level_idx = get_leaf_level_from_real_leaf_count(numSrcMeshFaces); + const int cs_bvh_leaf_level_idx = get_leaf_level_from_real_leaf_count(numCutMeshFaces); + + const int sm_bvh_rightmost_real_leaf = get_rightmost_real_leaf(sm_bvh_leaf_level_idx, numSrcMeshFaces); + const int cs_bvh_rightmost_real_leaf = get_rightmost_real_leaf(cs_bvh_leaf_level_idx, numCutMeshFaces); + + do { + node_pair_t ct_front_node = traversalQueue.front(); + + bounding_box_t sm_bvh_node_bbox; + bounding_box_t cs_bvh_node_bbox; + + // sm + const int sm_bvh_node_implicit_idx = ct_front_node.m_left; + const int sm_bvh_node_level_idx = get_level_from_implicit_idx(sm_bvh_node_implicit_idx); + const bool sm_bvh_node_is_leaf = sm_bvh_node_level_idx == sm_bvh_leaf_level_idx; + const int sm_bvh_node_level_leftmost_node = get_level_leftmost_node(sm_bvh_node_level_idx); + fd_t sm_node_face = hmesh_t::null_face(); + const int sm_bvh_node_level_rightmost_node = get_level_rightmost_real_node(sm_bvh_rightmost_real_leaf, sm_bvh_leaf_level_idx, sm_bvh_node_level_idx); + const int sm_bvh_node_mem_idx = get_node_mem_index( + sm_bvh_node_implicit_idx, + sm_bvh_node_level_leftmost_node, + 0, + sm_bvh_node_level_rightmost_node); + sm_bvh_node_bbox = SAFE_ACCESS(srcMeshBvhAABBs, sm_bvh_node_mem_idx); + + if (sm_bvh_node_is_leaf) { + const int sm_bvh_node_idx_on_level = sm_bvh_node_implicit_idx - sm_bvh_node_level_leftmost_node; + sm_node_face = SAFE_ACCESS(srcMeshBvhLeafNodeFaces, sm_bvh_node_idx_on_level); + } + + // cs + const int cs_bvh_node_implicit_idx = ct_front_node.m_right; + const int cs_bvh_node_level_idx = get_level_from_implicit_idx(cs_bvh_node_implicit_idx); + const int cs_bvh_node_level_leftmost_node = get_level_leftmost_node(cs_bvh_node_level_idx); + const bool cs_bvh_node_is_leaf = cs_bvh_node_level_idx == cs_bvh_leaf_level_idx; + fd_t cs_node_face = hmesh_t::null_face(); + const int cs_bvh_node_level_rightmost_node = get_level_rightmost_real_node(cs_bvh_rightmost_real_leaf, cs_bvh_leaf_level_idx, cs_bvh_node_level_idx); + const int cs_bvh_node_mem_idx = get_node_mem_index( + cs_bvh_node_implicit_idx, + cs_bvh_node_level_leftmost_node, + 0, + cs_bvh_node_level_rightmost_node); + cs_bvh_node_bbox = SAFE_ACCESS(cutMeshBvhAABBs, cs_bvh_node_mem_idx); + + if (cs_bvh_node_is_leaf) { + const int cs_bvh_node_idx_on_level = cs_bvh_node_implicit_idx - cs_bvh_node_level_leftmost_node; + cs_node_face = SAFE_ACCESS(cutMeshBvhLeafNodeFaces, cs_bvh_node_idx_on_level); + } + + const bool haveOverlap = intersect_bounding_boxes(sm_bvh_node_bbox, cs_bvh_node_bbox); + + if (haveOverlap) { + + if (cs_bvh_node_is_leaf && sm_bvh_node_is_leaf) { + MCUT_ASSERT(cs_node_face != hmesh_t::null_face()); + MCUT_ASSERT(sm_node_face != hmesh_t::null_face()); + + fd_t cs_node_face_offsetted = fd_t(cs_node_face + numSrcMeshFaces); + + ps_face_to_potentially_intersecting_others[sm_node_face].push_back(cs_node_face_offsetted); + ps_face_to_potentially_intersecting_others[cs_node_face_offsetted].push_back(sm_node_face); + } else if (sm_bvh_node_is_leaf && !cs_bvh_node_is_leaf) { + MCUT_ASSERT(cs_node_face == hmesh_t::null_face()); + MCUT_ASSERT(sm_node_face != hmesh_t::null_face()); + + const int cs_bvh_node_left_child_implicit_idx = (cs_bvh_node_implicit_idx * 2) + 1; + const int cs_bvh_node_right_child_implicit_idx = (cs_bvh_node_implicit_idx * 2) + 2; + + const int rightmost_real_node_on_child_level = get_level_rightmost_real_node(cs_bvh_rightmost_real_leaf, cs_bvh_leaf_level_idx, cs_bvh_node_level_idx + 1); + const bool right_child_is_real = cs_bvh_node_right_child_implicit_idx <= rightmost_real_node_on_child_level; + + traversalQueue.push({ sm_bvh_node_implicit_idx, cs_bvh_node_left_child_implicit_idx }); + + if (right_child_is_real) { + traversalQueue.push({ sm_bvh_node_implicit_idx, cs_bvh_node_right_child_implicit_idx }); + } + } else if (!sm_bvh_node_is_leaf && cs_bvh_node_is_leaf) { + + MCUT_ASSERT(cs_node_face != hmesh_t::null_face()); + MCUT_ASSERT(sm_node_face == hmesh_t::null_face()); + + const int sm_bvh_node_left_child_implicit_idx = (sm_bvh_node_implicit_idx * 2) + 1; + const int sm_bvh_node_right_child_implicit_idx = (sm_bvh_node_implicit_idx * 2) + 2; + + const int rightmost_real_node_on_child_level = get_level_rightmost_real_node(sm_bvh_rightmost_real_leaf, sm_bvh_leaf_level_idx, sm_bvh_node_level_idx + 1); + const bool right_child_is_real = sm_bvh_node_right_child_implicit_idx <= rightmost_real_node_on_child_level; + + traversalQueue.push({ sm_bvh_node_left_child_implicit_idx, cs_bvh_node_implicit_idx }); + + if (right_child_is_real) { + traversalQueue.push({ sm_bvh_node_right_child_implicit_idx, cs_bvh_node_implicit_idx }); + } + } else { // both nodes are internal + MCUT_ASSERT(cs_node_face == hmesh_t::null_face()); + MCUT_ASSERT(sm_node_face == hmesh_t::null_face()); + + const int sm_bvh_node_left_child_implicit_idx = (sm_bvh_node_implicit_idx * 2) + 1; + const int sm_bvh_node_right_child_implicit_idx = (sm_bvh_node_implicit_idx * 2) + 2; + + const int cs_bvh_node_left_child_implicit_idx = (cs_bvh_node_implicit_idx * 2) + 1; + const int cs_bvh_node_right_child_implicit_idx = (cs_bvh_node_implicit_idx * 2) + 2; + + const int sm_rightmost_real_node_on_child_level = get_level_rightmost_real_node(sm_bvh_rightmost_real_leaf, sm_bvh_leaf_level_idx, sm_bvh_node_level_idx + 1); + const bool sm_right_child_is_real = sm_bvh_node_right_child_implicit_idx <= sm_rightmost_real_node_on_child_level; + + const int cs_rightmost_real_node_on_child_level = get_level_rightmost_real_node(cs_bvh_rightmost_real_leaf, cs_bvh_leaf_level_idx, cs_bvh_node_level_idx + 1); + const bool cs_right_child_is_real = cs_bvh_node_right_child_implicit_idx <= cs_rightmost_real_node_on_child_level; + + traversalQueue.push({ sm_bvh_node_left_child_implicit_idx, cs_bvh_node_left_child_implicit_idx }); + + if (cs_right_child_is_real) { + traversalQueue.push({ sm_bvh_node_left_child_implicit_idx, cs_bvh_node_right_child_implicit_idx }); + } + + if (sm_right_child_is_real) { + traversalQueue.push({ sm_bvh_node_right_child_implicit_idx, cs_bvh_node_left_child_implicit_idx }); + + if (cs_right_child_is_real) { + traversalQueue.push({ sm_bvh_node_right_child_implicit_idx, cs_bvh_node_right_child_implicit_idx }); + } + } + } + } + + traversalQueue.pop(); // rm ct_front_node + } while (!traversalQueue.empty()); + TIMESTACK_POP(); +} +#else +BoundingVolumeHierarchy::BoundingVolumeHierarchy() +{ +} + +BoundingVolumeHierarchy::~BoundingVolumeHierarchy() { } + +// three stages to BVH construction +void BoundingVolumeHierarchy::buildTree(const hmesh_t& mesh_, + const fixed_precision_number_t& enlargementEps_, + uint32_t mp_, + const SplitMethod& sm_) +{ + SCOPED_TIMER(__FUNCTION__); + mesh = &(mesh_); /// + MCUT_ASSERT(mesh->number_of_faces() >= 1); + maxPrimsInNode = (std::min(255u, mp_)); // + splitMethod = (sm_); // + enlargementEps = (enlargementEps_); + + buildData.clear(); + primitives.clear(); + primitiveOrderedBBoxes.clear(); + nodes.clear(); + + // First, bounding information about each primitive is computed and stored in an array + // that will be used during tree construction. + + // initialize buildData array for primitives + + buildData.reserve(mesh->number_of_faces()); + + primitiveOrderedBBoxes.resize(mesh->number_of_faces()); + primitives.resize(mesh->number_of_faces()); + + // TODO make this parallel + // for each face in mesh + for (face_array_iterator_t f = mesh->faces_begin(); f != mesh->faces_end(); ++f) { + const int i = static_cast(*f); + primitives[i] = *f; + + const std::vector vertices_on_face = mesh->get_vertices_around_face(*f); + + bounding_box_t bbox; + // for each vertex on face + for (std::vector::const_iterator v = vertices_on_face.cbegin(); v != vertices_on_face.cend(); ++v) { + const vec3 coords = mesh->vertex(*v); + bbox.expand(coords); + } + + if (enlargementEps > 0.0) { + bbox.enlarge(enlargementEps); + } + + primitiveOrderedBBoxes[i] = bbox; + + buildData.push_back(BVHPrimitiveInfo(i, primitiveOrderedBBoxes[i])); + } + + // Next, the tree is built via a procedure that splits the primitives into subsets and + // recursively builds BVHs for the subsets. The result is a binary tree where each + // interior node holds pointers to its children and each leaf node holds references to + // one or more primitives. + + uint32_t totalNodes = 0; + std::vector orderedPrims; + // orderedPrims.reserve(mesh->number_of_faces()); + + std::shared_ptr root = recursiveBuild( + buildData, + 0, + /*primitives.size()*/ mesh->number_of_faces(), + &totalNodes, + orderedPrims); + + primitives.swap(orderedPrims); + + // Finally, this tree is converted to a more compact (and thus more efficient) pointerless + // representation for use during rendering + + nodes.resize(totalNodes); + for (uint32_t i = 0; i < totalNodes; ++i) { + // new (&nodes[i]) LinearBVHNode; + nodes[i] = std::make_shared(); + } + uint32_t offset = 0; + flattenBVHTree(root, &offset); +} + +const BBox& BoundingVolumeHierarchy::GetPrimitiveBBox(int primitiveIndex) const +{ + MCUT_ASSERT(primitiveIndex < mesh->number_of_faces()); + return primitiveOrderedBBoxes[primitiveIndex]; +} + +uint32_t BoundingVolumeHierarchy::flattenBVHTree(std::shared_ptr node, uint32_t* offset) +{ + MCUT_ASSERT(*offset < nodes.size()); + std::shared_ptr linearNode = nodes[*offset]; + linearNode->bounds = node->bounds; + uint32_t myOffset = (*offset)++; + if (node->nPrimitives > 0) { + linearNode->primitivesOffset = node->firstPrimOffset; + linearNode->nPrimitives = node->nPrimitives; + } else { + // Creater interior flattened BVH node + linearNode->axis = node->splitAxis; + linearNode->nPrimitives = 0; + + flattenBVHTree(node->children[0], offset); + + linearNode->secondChildOffset = flattenBVHTree(node->children[1], + offset); + } + return myOffset; +} + +std::shared_ptr BoundingVolumeHierarchy::recursiveBuild( + std::vector& buildData, + uint32_t start, + uint32_t end, + uint32_t* totalNodes, + std::vector& orderedPrims) +{ + (*totalNodes)++; + + std::shared_ptr node = std::make_shared(); + + // Compute bounds of all primitives in BVH node + uint32_t nPrimitives = end - start; + MCUT_ASSERT((nPrimitives - 1) < (uint32_t)mesh->number_of_faces()); + + BBox bbox; + for (uint32_t i = start; i < end; ++i) { + MCUT_ASSERT(i < buildData.size()); + bbox = Union(bbox, buildData[i].bounds); + } + if (nPrimitives == 1) { + // Create leaf BVHBuildNode + uint32_t firstPrimOffset = orderedPrims.size(); + for (uint32_t i = start; i < end; ++i) { + MCUT_ASSERT(i < buildData.size()); + uint32_t primNum = buildData[i].primitiveNumber; + orderedPrims.push_back(primitives[primNum]); + } + node->InitLeaf(firstPrimOffset, nPrimitives, bbox); + } else { + // Compute bound of primitive centroids, choose split dimension dim + BBox centroidBounds; + for (uint32_t i = start; i < end; ++i) { + MCUT_ASSERT(i < buildData.size()); + centroidBounds = Union(centroidBounds, buildData[i].centroid); + } + + int dim = centroidBounds.MaximumExtent(); + MCUT_ASSERT(dim < 3); + + // + // Partition primitives into two sets and build children + // + uint32_t mid = (start + end) / 2; + switch (this->splitMethod) { + case SplitMethod::SPLIT_MIDDLE: { + // Partition primitives through node’s midpoint + fixed_precision_number_t pmid = (centroidBounds.minimum()[dim] + centroidBounds.maximum()[dim]) * .5; +#if 1 + MCUT_ASSERT(start < buildData.size()); + BVHPrimitiveInfo* midPtr = std::partition(&buildData[start], + &buildData[end - 1] + 1, + /*CompareToMid(dim, pmid)*/ + [dim, pmid](const BVHPrimitiveInfo& pi) { + return pi.centroid[dim] < pmid; + }); + mid = midPtr - &buildData[0]; +#else + std::vector::iterator midPtr = std::partition(buildData.begin() + start, + buildData.end(), + CompareToMid(dim, pmid)); + mid = std::distance(buildData.begin(), midPtr); +#endif + } break; + case SplitMethod::SPLIT_EQUAL_COUNTS: { + // Partition primitives into equally-sized subsets + mid = (start + end) / 2; + std::nth_element(&buildData[start], &buildData[mid], + &buildData[end - 1] + 1, ComparePoints(dim)); + } break; + case SplitMethod::SPLIT_SAH: { + // Partition primitives using approximate SAH + if (nPrimitives <= 4) { + // Partition primitives into equally-sized subsets + mid = (start + end) / 2; + std::nth_element(&buildData[start], &buildData[mid], + &buildData[end - 1] + 1, ComparePoints(dim)); + } else { + // Allocate BucketInfo for SAH partition buckets + const int nBuckets = 12; + BucketInfo buckets[nBuckets]; + + // Initialize BucketInfo for SAH partition buckets + for (uint32_t i = start; i < end; ++i) { + int b = nBuckets * ((buildData[i].centroid[dim] - centroidBounds.minimum()[dim]) / (centroidBounds.maximum()[dim] - centroidBounds.minimum()[dim])); + if (b == nBuckets) + b = nBuckets - 1; + buckets[b].count++; + buckets[b].bounds = Union(buckets[b].bounds, buildData[i].bounds); + } + // Compute costs for splitting after each bucket + + fixed_precision_number_t cost[nBuckets - 1]; + + for (int i = 0; i < nBuckets - 1; ++i) { + BBox b0, b1; + int count0 = 0, count1 = 0; + for (int j = 0; j <= i; ++j) { + b0 = Union(b0, buckets[j].bounds); + count0 += buckets[j].count; + } + for (int j = i + 1; j < nBuckets; ++j) { + b1 = Union(b1, buckets[j].bounds); + count1 += buckets[j].count; + } + cost[i] = .125f + (count0 * b0.SurfaceArea() + count1 * b1.SurfaceArea()) / bbox.SurfaceArea(); + } + // Find bucket to split at that minimizes SAH metric + float minCost = cost[0]; + uint32_t minCostSplit = 0; + for (int i = 1; i < nBuckets - 1; ++i) { + if (cost[i] < minCost) { + minCost = cost[i]; + minCostSplit = i; + } + } + // Either create leaf or split primitives at selected SAH bucket + + if (nPrimitives > (uint32_t)maxPrimsInNode || minCost < nPrimitives) { + const BVHPrimitiveInfo* pmid = std::partition(&buildData[start], + &buildData[end - 1] + 1, + CompareToBucket(minCostSplit, nBuckets, dim, centroidBounds)); + mid = pmid - &buildData[0]; + } else { + // Create leaf BVHBuildNode + uint32_t firstPrimOffset = orderedPrims.size(); + for (uint32_t i = start; i < end; ++i) { + uint32_t primNum = buildData[i].primitiveNumber; + orderedPrims.push_back(primitives[primNum]); + } + node->InitLeaf(firstPrimOffset, nPrimitives, bbox); + } + } + } break; + default: + fprintf(stderr, "[MCUT]: error, unknown split method\n"); + break; + } + + mid = (start + end) / 2; + if (centroidBounds.maximum()[dim] == centroidBounds.minimum()[dim]) { + // Create leaf BVHBuildNode + int32_t firstPrimOffset = orderedPrims.size(); + for (uint32_t i = start; i < end; ++i) { + uint32_t primNum = buildData[i].primitiveNumber; + orderedPrims.push_back(primitives[primNum]); + } + node->InitLeaf(firstPrimOffset, nPrimitives, bbox); + return node; + } + + std::shared_ptr leftSubTree = recursiveBuild(buildData, start, mid, + totalNodes, orderedPrims); + std::shared_ptr rightSubTree = recursiveBuild(buildData, mid, end, + totalNodes, orderedPrims); + // Partition primitives based on splitMethod〉 + node->InitInterior(dim, + leftSubTree, + rightSubTree); + } + return node; +} + +int BoundingVolumeHierarchy::GetNodeCount() const +{ + return (int)nodes.size(); +} + +const std::shared_ptr& BoundingVolumeHierarchy::GetNode(int idx) const +{ + return nodes[idx]; +} + +const fd_t& BoundingVolumeHierarchy::GetPrimitive(int index) const +{ + MCUT_ASSERT(index < (int)primitives.size()); + return primitives[index]; +} + +void BoundingVolumeHierarchy::intersectBVHTrees( +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + thread_pool& scheduler, +#endif + std::map>& symmetric_intersecting_pairs, + const BoundingVolumeHierarchy& bvhA, + const BoundingVolumeHierarchy& bvhB, + const uint32_t primitiveOffsetA, + const uint32_t primitiveOffsetB) +{ + SCOPED_TIMER(__FUNCTION__); + MCUT_ASSERT(bvhA.GetNodeCount() > 0); + MCUT_ASSERT(bvhB.GetNodeCount() > 0); + + auto fn_intersectBVHTrees = [&bvhA, &bvhB, &primitiveOffsetA, &primitiveOffsetB]( + std::vector>& worklist_, + std::map>& symmetric_intersecting_pairs_, + const uint32_t maxWorklistSize) { + // Simultaneous DFS traversal + while (worklist_.size() > 0 && worklist_.size() < maxWorklistSize) { + // maxTodoSz = std::max(maxTodoSz, (int)worklist_.size()); + // std::cout << "worklist_.size()="< cur = worklist_.back(); + // TODO: try to keep an additional counter that allows us to minimize pushing and popping + // Might require a wrapper class over std::vector "lazy vector" + worklist_.pop_back(); + + const uint32_t nodeAIndex = cur.first; + const uint32_t nodeBIndex = cur.second; + const std::shared_ptr nodeA = bvhA.GetNode(nodeAIndex); + const std::shared_ptr nodeB = bvhB.GetNode(nodeBIndex); + + if (!intersect_bounding_boxes(nodeA->bounds, nodeB->bounds)) { + continue; + } + + bool nodeAIsLeaf = nodeA->nPrimitives > 0; + bool nodeBIsLeaf = nodeB->nPrimitives > 0; + + if (nodeAIsLeaf) { + if (nodeBIsLeaf) { + for (int i = 0; i < nodeA->nPrimitives; ++i) { + const fd_t faceA = bvhA.GetPrimitive((uint32_t)(nodeA->primitivesOffset + i)); + const fd_t faceAOffsetted(primitiveOffsetA + faceA); + + for (int j = 0; j < nodeB->nPrimitives; ++j) { + const fd_t faceB = bvhB.GetPrimitive((uint32_t)(nodeB->primitivesOffset + j)); + const fd_t faceBOffsetted(primitiveOffsetB + faceB); + + symmetric_intersecting_pairs_[faceAOffsetted].push_back(faceBOffsetted); + symmetric_intersecting_pairs_[faceBOffsetted].push_back(faceAOffsetted); + } + } + } else { + const uint32_t nodeBLeftChild = nodeBIndex + 1; + const uint32_t nodeBRightChild = nodeB->secondChildOffset; + worklist_.emplace_back(nodeAIndex, nodeBLeftChild); + worklist_.emplace_back(nodeAIndex, nodeBRightChild); + } + } else { + if (nodeBIsLeaf) { + const uint32_t nodeALeftChild = nodeAIndex + 1; + const uint32_t nodeARightChild = nodeA->secondChildOffset; + worklist_.emplace_back(nodeALeftChild, nodeBIndex); + worklist_.emplace_back(nodeARightChild, nodeBIndex); + } else { + const uint32_t nodeALeftChild = nodeAIndex + 1; + const uint32_t nodeARightChild = nodeA->secondChildOffset; + + const uint32_t nodeBLeftChild = nodeBIndex + 1; + const uint32_t nodeBRightChild = nodeB->secondChildOffset; + + worklist_.emplace_back(nodeALeftChild, nodeBLeftChild); + worklist_.emplace_back(nodeALeftChild, nodeBRightChild); + + worklist_.emplace_back(nodeARightChild, nodeBLeftChild); + worklist_.emplace_back(nodeARightChild, nodeBRightChild); + } + } + } + }; + + // start with pair of root nodes + std::vector> todo(1, std::make_pair(0, 0)); + +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + { + // master thread intersects the BVHs until the number of node pairs + // reaches a threshold (or workload was small enough that traversal + // is finished) + const uint32_t threshold = scheduler.get_num_threads(); + fn_intersectBVHTrees(todo, symmetric_intersecting_pairs, threshold); + + uint32_t remainingWorkloadCount = (uint32_t)todo.size(); // how much work do we still have left + + if (remainingWorkloadCount > 0) { // do parallel traversal by distributing blocks of node-pairs across worker threads + // NOTE: we do not manage load-balancing (too complex for the perf gain) + typedef std::vector>::const_iterator InputStorageIteratorType; + typedef std::map> OutputStorageType; // symmetric_intersecting_pairs (local) + + auto fn_intersect = [&](InputStorageIteratorType block_start_, InputStorageIteratorType block_end_) -> OutputStorageType { + OutputStorageType symmetric_intersecting_pairs_local; + + std::vector> todo_local(block_start_, block_end_); + + fn_intersectBVHTrees( + todo_local, + symmetric_intersecting_pairs_local, + // traverse until leaves + std::numeric_limits::max()); + + return symmetric_intersecting_pairs_local; + }; + + std::vector> futures; + OutputStorageType partial_res; + + parallel_for( + scheduler, + todo.cbegin(), + todo.cend(), + (1 << 1), + fn_intersect, + partial_res, // output of master thread + futures); + + symmetric_intersecting_pairs.insert(partial_res.cbegin(), partial_res.cend()); + + for (int i = 0; i < (int)futures.size(); ++i) { + std::future& f = futures[i]; + MCUT_ASSERT(f.valid()); + OutputStorageType future_res = f.get(); + + symmetric_intersecting_pairs.insert(future_res.cbegin(), future_res.cend()); + } + } + } +#else + fn_intersectBVHTrees(todo, symmetric_intersecting_pairs, std::numeric_limits::max()); +#endif // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) +} + +#endif diff --git a/src/mcut/source/frontend.cpp b/src/mcut/source/frontend.cpp new file mode 100644 index 0000000000..da3ec443a6 --- /dev/null +++ b/src/mcut/source/frontend.cpp @@ -0,0 +1,3058 @@ +#include "mcut/internal/frontend.h" +#include "mcut/internal/preproc.h" + +#include "mcut/internal/hmesh.h" +#include "mcut/internal/math.h" +#include "mcut/internal/utils.h" + +#include +#include +#include + +#include + +#include // iota +#include +#include +#include + +#include "mcut/internal/cdt/cdt.h" +#include "mcut/internal/timer.h" + +#if defined(PROFILING_BUILD) +thread_local std::stack> g_thrd_loc_timerstack; +#endif + +thread_local std::string per_thread_api_log_str; + +threadsafe_list> g_contexts = {}; +threadsafe_list> g_events = {}; +std::atomic g_objects_counter; // a counter that is used to assign a unique value to e.g. a McContext handle that will be returned to the user +std::once_flag g_objects_counter_init_flag; // flag used to initialise "g_objects_counter" with "std::call_once" + +void create_context_impl(McContext* pOutContext, McFlags flags, uint32_t helperThreadCount) +{ +#if !defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + UNUSED(helperThreadCount); +#endif + + MCUT_ASSERT(pOutContext != nullptr); + + std::call_once(g_objects_counter_init_flag, []() { g_objects_counter.store(0xDECAF); /*any non-ero value*/ }); + + const McContext handle = reinterpret_cast(g_objects_counter++); + g_contexts.push_front(std::shared_ptr( + new context_t(handle, flags +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + , + helperThreadCount +#endif + ))); + + *pOutContext = handle; +} + +void debug_message_callback_impl( + McContext contextHandle, + pfn_mcDebugOutput_CALLBACK cb, + const McVoid* userParam) +{ + MCUT_ASSERT(contextHandle != nullptr); + MCUT_ASSERT(cb != nullptr); + + std::shared_ptr context_ptr = g_contexts.find_first_if([=](const std::shared_ptr cptr) { return cptr->m_user_handle == contextHandle; }); + + // std::map>::iterator context_entry_iter = g_contexts.find(contextHandle); + + if (context_ptr == nullptr) { + // "contextHandle" may not be NULL but that does not mean it maps to + // a valid object in "g_contexts" + throw std::invalid_argument("invalid context"); + } + // const std::unique_ptr& context_uptr = context_entry_iter->second; + + // set callback function ptr, and user pointer + context_ptr->set_debug_callback_data(cb, userParam); +} + +void get_debug_message_log_impl(McContext context, + McUint32 count, McSize bufSize, + McDebugSource* sources, McDebugType* types, McDebugSeverity* severities, + McSize* lengths, McChar* messageLog, McUint32& numFetched) +{ + MCUT_ASSERT(context != nullptr); + + std::shared_ptr context_ptr = g_contexts.find_first_if([=](const std::shared_ptr cptr) { return cptr->m_user_handle == context; }); + + if (context_ptr == nullptr) { + throw std::invalid_argument("invalid context"); + } + + numFetched = 0; + + if (messageLog == nullptr) { + context_ptr->dbg_cb(MC_DEBUG_SOURCE_API, MC_DEBUG_TYPE_OTHER, 0, MC_DEBUG_SEVERITY_NOTIFICATION, "output messageLog is NULL. Return."); + return; + } + + McSize messageLogOffset = 0; + + const uint32_t N = std::min((uint32_t)count, (uint32_t)context_ptr->m_debug_logs.size()); + + // for internal message + for (numFetched = 0; numFetched < N; ++numFetched) { + + const context_t::debug_log_msg_t& cur_dbg_msg = context_ptr->m_debug_logs[numFetched]; + const McSize msg_length = (McSize)cur_dbg_msg.str.size(); + const McSize newLengthOfActualMessageLog = (messageLogOffset + msg_length); + + if (newLengthOfActualMessageLog > bufSize) { + break; // stop + } + + if (sources != nullptr) { + sources[numFetched] = cur_dbg_msg.source; + } + + if (types != nullptr) { + types[numFetched] = cur_dbg_msg.type; + } + + if (severities != nullptr) { + severities[numFetched] = cur_dbg_msg.severity; + } + + if (lengths != nullptr) { + lengths[numFetched] = msg_length; + } + + // copy into output array + memcpy(messageLog + messageLogOffset, cur_dbg_msg.str.data(), msg_length); + + messageLogOffset = newLengthOfActualMessageLog; + } +} + +// find the number of trailing zeros in v +// http://graphics.stanford.edu/~seander/bithacks.html#ZerosOnRightLinear +inline int trailing_zeroes(uint32_t v) +{ + int r; // the result goes here +#ifdef _WIN32 +#pragma warning(disable : 4146) // "unary minus operator applied to unsigned type, result still unsigned" +#endif // #ifdef _WIN32 + float f = (float)(v & -v); // cast the least significant bit in v to a float +#ifdef _WIN32 +#pragma warning(default : 4146) +#endif // #ifdef _WIN32 + +// dereferencing type-punned pointer will break strict-aliasing rules +#if __linux__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstrict-aliasing" +#endif + + r = (*(uint32_t*)&f >> 23) - 0x7f; + +#if __linux__ +#pragma GCC diagnostic pop +#endif + return r; +} + +// https://stackoverflow.com/questions/47981/how-do-you-set-clear-and-toggle-a-single-bit +inline int set_bit(uint32_t v, uint32_t pos) +{ + v |= 1U << pos; + return v; +} + +inline int clear_bit(uint32_t v, uint32_t pos) +{ + v &= ~(1UL << pos); + return v; +} + +void debug_message_control_impl( + McContext contextHandle, + McDebugSource sourceBitfieldParam, + McDebugType typeBitfieldParam, + McDebugSeverity severityBitfieldParam, + bool enabled) +{ + std::shared_ptr context_ptr = g_contexts.find_first_if([=](const std::shared_ptr cptr) { return cptr->m_user_handle == contextHandle; }); + + if (context_ptr == nullptr) { + throw std::invalid_argument("invalid context"); + } + + // + // Debug "source" flag + // + + // for each possible "source" flag + for (auto i : { MC_DEBUG_SOURCE_API, MC_DEBUG_SOURCE_KERNEL }) { + if (sourceBitfieldParam & i) { // was it set/included by the user (to be enabled/disabled)? + int n = trailing_zeroes(MC_DEBUG_SOURCE_ALL & i); // get position of bit representing current "source" flag + if (enabled) { // does the user want to enabled this information (from being logged in the debug callback function) + context_ptr->dbgCallbackBitfieldSource = set_bit(context_ptr->dbgCallbackBitfieldSource, n); + } else { // ... user wants to disable this information + context_ptr->dbgCallbackBitfieldSource = clear_bit(context_ptr->dbgCallbackBitfieldSource, n); + } + } + } + + // + // Debug "type" flag + // + + for (auto i : { MC_DEBUG_TYPE_DEPRECATED_BEHAVIOR, MC_DEBUG_TYPE_ERROR, MC_DEBUG_TYPE_OTHER }) { + if (typeBitfieldParam & i) { + + const int n = trailing_zeroes(MC_DEBUG_TYPE_ALL & i); + + if (enabled) { + context_ptr->dbgCallbackBitfieldType = set_bit(context_ptr->dbgCallbackBitfieldType, n); + } else { + + context_ptr->dbgCallbackBitfieldType = clear_bit(context_ptr->dbgCallbackBitfieldType, n); + } + } + } + + // + // Debug "severity" flag + // + + for (auto i : { MC_DEBUG_SEVERITY_HIGH, MC_DEBUG_SEVERITY_LOW, MC_DEBUG_SEVERITY_MEDIUM, MC_DEBUG_SEVERITY_NOTIFICATION }) { + if (severityBitfieldParam & i) { + + const int n = trailing_zeroes(MC_DEBUG_SEVERITY_ALL & i); + + if (enabled) { + context_ptr->dbgCallbackBitfieldSeverity = set_bit(context_ptr->dbgCallbackBitfieldSeverity, n); + } else { + context_ptr->dbgCallbackBitfieldSeverity = clear_bit(context_ptr->dbgCallbackBitfieldSeverity, n); + } + } + } +} + +void get_info_impl( + const McContext contextHandle, + McFlags info, + McSize bytes, + McVoid* pMem, + McSize* pNumBytes) +{ + std::shared_ptr context_ptr = g_contexts.find_first_if([=](const std::shared_ptr cptr) { return cptr->m_user_handle == contextHandle; }); + + if (context_ptr == nullptr) { + throw std::invalid_argument("invalid context"); + } + + switch (info) { + case MC_CONTEXT_FLAGS: { + McFlags flags = context_ptr->get_flags(); + if (pMem == nullptr) { + *pNumBytes = sizeof(McFlags); + } else { + if (bytes < sizeof(McFlags)) { + throw std::invalid_argument("invalid bytes"); + } + memcpy(pMem, reinterpret_cast(&flags), bytes); + } + break; + } + case MC_MAX_DEBUG_MESSAGE_LENGTH: { + McSize sizeMax = 0; + for (McUint32 i = 0; i < (McUint32)context_ptr->m_debug_logs.size(); ++i) { + sizeMax = std::max((McSize)sizeMax, (McSize)context_ptr->m_debug_logs[i].str.size()); + } + memcpy(pMem, reinterpret_cast(&sizeMax), sizeof(McSize)); + } break; + default: + throw std::invalid_argument("unknown info parameter"); + break; + } +} + +void create_user_event_impl(McEvent* eventHandle, McContext context) +{ + std::shared_ptr context_ptr = g_contexts.find_first_if([=](const std::shared_ptr cptr) { return cptr->m_user_handle == context; }); + + if (context_ptr == nullptr) { + throw std::invalid_argument("invalid context"); + } + + // + // create the event object associated with the enqueued task + // + + std::shared_ptr user_event_ptr = std::shared_ptr(new event_t); + + MCUT_ASSERT(user_event_ptr != nullptr); + + g_events.push_front(user_event_ptr); + + user_event_ptr->m_user_handle = reinterpret_cast(g_objects_counter++); + user_event_ptr->m_profiling_enabled = (context_ptr->get_flags() & MC_PROFILING_ENABLE) != 0; + user_event_ptr->m_command_type = McCommandType::MC_COMMAND_USER; + user_event_ptr->m_context = context; + user_event_ptr->m_responsible_thread_id = 0; // initialized but unused for user event + + user_event_ptr->log_submit_time(); + + std::weak_ptr user_event_weak_ptr(user_event_ptr); + + user_event_ptr->m_user_API_command_task_emulator = std::unique_ptr>(new std::packaged_task( + // will be "called" when user updates the command status to MC_COMPLETE + [user_event_weak_ptr]() { + if (user_event_weak_ptr.expired()) { + throw std::runtime_error("user event expired"); + } + + std::shared_ptr event_ptr = user_event_weak_ptr.lock(); + + if (event_ptr == nullptr) { + throw std::runtime_error("user event null"); + } + + event_ptr->log_start_time(); + })); + + user_event_ptr->m_future = user_event_ptr->m_user_API_command_task_emulator->get_future(); // the future we can later wait on via mcWaitForEVents + user_event_ptr->m_responsible_thread_id = MC_UNDEFINED_VALUE; // some user thread + + *eventHandle = user_event_ptr->m_user_handle; +} + +/** + * execution_status specifies the new execution status to be set and can be CL_​COMPLETE or a negative integer value to indicate an error. + * A negative integer value causes all enqueued commands that wait on this user + * event to be terminated. + */ +void set_user_event_status_impl(McEvent event, McInt32 execution_status) +{ + std::shared_ptr event_ptr = g_events.find_first_if([=](const std::shared_ptr ptr) { return ptr->m_user_handle == event; }); + + if (event_ptr == nullptr) { + throw std::invalid_argument("invalid event"); + } + + McResult userEventErrorCode = McResult::MC_NO_ERROR; + + std::unique_ptr>& associated_task_emulator = event_ptr->m_user_API_command_task_emulator; + MCUT_ASSERT(associated_task_emulator != nullptr); + // simply logs the start time and makes the std::packaged_task future object ready + associated_task_emulator->operator()(); // call the internal "task" function to update event status + + switch (execution_status) { + case McEventCommandExecStatus::MC_COMPLETE: { + event_ptr->log_end_time(); + } break; + default: { + MCUT_ASSERT(execution_status < 0); // an error + switch (execution_status) { + case McResult::MC_INVALID_OPERATION: + case McResult::MC_INVALID_VALUE: + case McResult::MC_OUT_OF_MEMORY: { + userEventErrorCode = (McResult)execution_status; + } break; + default: { + throw std::invalid_argument("invalid command status"); + } + } + } + } + + // invoke call back + event_ptr->notify_task_complete(userEventErrorCode); // updated event state to indicate task completion (lock-based) +} + +void get_event_info_impl( + const McEvent event, + McFlags info, + McSize bytes, + McVoid* pMem, + McSize* pNumBytes) +{ + std::shared_ptr event_ptr = g_events.find_first_if([=](const std::shared_ptr ptr) { return ptr->m_user_handle == event; }); + + if (event_ptr == nullptr) { + throw std::invalid_argument("invalid event"); + } + + switch (info) { + case MC_EVENT_RUNTIME_EXECUTION_STATUS: { + + if (pMem == nullptr) { + *pNumBytes = sizeof(McResult); + } else { + if (bytes < sizeof(McResult)) { + throw std::invalid_argument("invalid bytes"); + } + McResult status = (McResult)event_ptr->m_runtime_exec_status.load(); + memcpy(pMem, reinterpret_cast(&status), bytes); + } + break; + } + case MC_EVENT_TIMESTAMP_SUBMIT: + case MC_EVENT_TIMESTAMP_START: + case MC_EVENT_TIMESTAMP_END: { + if (pMem == nullptr) { + *pNumBytes = sizeof(McSize); + } else { + if (bytes < sizeof(McSize)) { + throw std::invalid_argument("invalid bytes"); + } + McSize nanoseconds_since_epoch = 0; + if (info == MC_EVENT_TIMESTAMP_SUBMIT) { + nanoseconds_since_epoch = event_ptr->m_timestamp_submit.load(); + } else if (info == MC_EVENT_TIMESTAMP_START) { + nanoseconds_since_epoch = event_ptr->m_timestamp_start.load(); + } else if (info == MC_EVENT_TIMESTAMP_END) { + nanoseconds_since_epoch = event_ptr->m_timestamp_end.load(); + } + MCUT_ASSERT(nanoseconds_since_epoch != 0); + memcpy(pMem, reinterpret_cast(&nanoseconds_since_epoch), bytes); + } + break; + } + case MC_EVENT_COMMAND_EXECUTION_STATUS: { + if (pMem == nullptr) { + *pNumBytes = sizeof(McEventCommandExecStatus); + } else { + if (bytes < sizeof(McEventCommandExecStatus)) { + throw std::invalid_argument("invalid bytes"); + } + McEventCommandExecStatus status = (McEventCommandExecStatus)event_ptr->m_command_exec_status.load(); + memcpy(pMem, reinterpret_cast(&status), bytes); + } + } break; + case MC_EVENT_COMMAND_TYPE: { + if (pMem == nullptr) { + *pNumBytes = sizeof(McCommandType); + } else { + if (bytes < sizeof(McCommandType)) { + throw std::invalid_argument("invalid bytes"); + } + McCommandType cmdType = (McCommandType)event_ptr->m_command_type; + memcpy(pMem, reinterpret_cast(&cmdType), bytes); + } + } break; + case MC_EVENT_CONTEXT: { + if (pMem == nullptr) { + *pNumBytes = sizeof(McContext); + } else { + if (bytes < sizeof(McContext)) { + throw std::invalid_argument("invalid bytes"); + } + McContext ctxt = (McContext)event_ptr->m_context; + memcpy(pMem, reinterpret_cast(&ctxt), bytes); + } + } break; + default: + throw std::invalid_argument("unknown info parameter"); + break; + } +} + +void wait_for_events_impl( + uint32_t numEventsInWaitlist, + const McEvent* pEventWaitList, McResult& runtimeStatusFromAllPrecedingEvents) +{ + for (uint32_t i = 0; i < numEventsInWaitlist; ++i) { + McEvent eventHandle = pEventWaitList[i]; + + std::shared_ptr event_ptr = g_events.find_first_if([=](const std::shared_ptr eptr) { return eptr->m_user_handle == eventHandle; }); + + if (event_ptr == nullptr) { + // "contextHandle" may not be NULL but that does not mean it maps to + // a valid object in "g_contexts" + throw std::invalid_argument("null event object"); + } else { + if (event_ptr->m_future.valid()) { + + event_ptr->m_future.wait(); // block until event task is finished + + runtimeStatusFromAllPrecedingEvents = (McResult)event_ptr->m_runtime_exec_status.load(); + if (runtimeStatusFromAllPrecedingEvents != McResult::MC_NO_ERROR) { + // indicate that a task waiting on any one of the event in pEventWaitList + // must not proceed because a runtime error occurred + break; + } + } + } + } +} + +void set_event_callback_impl( + McEvent eventHandle, + pfn_McEvent_CALLBACK eventCallback, + McVoid* data) +{ + std::shared_ptr event_ptr = g_events.find_first_if([=](const std::shared_ptr eptr) { return eptr->m_user_handle == eventHandle; }); + + if (event_ptr == nullptr) { + // "contextHandle" may not be NULL but that does not mean it maps to + // a valid object in "g_contexts" + throw std::invalid_argument("unknown event object"); + } + + event_ptr->set_callback_data(eventHandle, eventCallback, data); +} + +void dispatch_impl( + McContext contextHandle, + McFlags dispatchFlags, + const McVoid* pSrcMeshVertices, + const uint32_t* pSrcMeshFaceIndices, + const uint32_t* pSrcMeshFaceSizes, + uint32_t numSrcMeshVertices, + uint32_t numSrcMeshFaces, + const McVoid* pCutMeshVertices, + const uint32_t* pCutMeshFaceIndices, + const uint32_t* pCutMeshFaceSizes, + uint32_t numCutMeshVertices, + uint32_t numCutMeshFaces, + uint32_t numEventsInWaitlist, + const McEvent* pEventWaitList, + McEvent* pEvent) +{ + std::shared_ptr context_ptr = g_contexts.find_first_if([=](const std::shared_ptr cptr) { return cptr->m_user_handle == contextHandle; }); + + if (context_ptr == nullptr) { + throw std::invalid_argument("invalid context"); + } + + std::weak_ptr context_weak_ptr(context_ptr); + + // submit the dispatch call to be executed asynchronously and return the future + // object that will be waited on as an event + const McEvent event_handle = context_ptr->prepare_and_submit_API_task( + MC_COMMAND_DISPATCH, numEventsInWaitlist, pEventWaitList, + [=]() { + if (!context_weak_ptr.expired()) { + std::shared_ptr context = context_weak_ptr.lock(); + if (context) { + preproc( + context, + dispatchFlags, + pSrcMeshVertices, + pSrcMeshFaceIndices, + pSrcMeshFaceSizes, + numSrcMeshVertices, + numSrcMeshFaces, + pCutMeshVertices, + pCutMeshFaceIndices, + pCutMeshFaceSizes, + numCutMeshVertices, + numCutMeshFaces); + } + } + }); + + MCUT_ASSERT(pEvent != nullptr); + + *pEvent = event_handle; +} + +void get_connected_components_impl( + const McContext contextHandle, + const McConnectedComponentType connectedComponentType, + const uint32_t numEntries, + McConnectedComponent* pConnComps, + uint32_t* numConnComps, + uint32_t numEventsInWaitlist, + const McEvent* pEventWaitList, + McEvent* pEvent) +{ + std::shared_ptr context_ptr = g_contexts.find_first_if([=](const std::shared_ptr cptr) { return cptr->m_user_handle == contextHandle; }); + + if (context_ptr == nullptr) { + throw std::invalid_argument("invalid context"); + } + + std::weak_ptr context_weak_ptr(context_ptr); + + const McEvent event_handle = context_ptr->prepare_and_submit_API_task( + MC_COMMAND_GET_CONNECTED_COMPONENTS, numEventsInWaitlist, pEventWaitList, + [=]() { + if (!context_weak_ptr.expired()) { + std::shared_ptr context = context_weak_ptr.lock(); + + if (context) { + if (numConnComps != nullptr) { + (*numConnComps) = 0; // reset + } + + uint32_t valid_cc_counter = 0; + + context->connected_components.for_each([&](const std::shared_ptr cc_ptr) { + const bool is_valid = (cc_ptr->type & connectedComponentType) != 0; + + if (is_valid) { + if (pConnComps == nullptr) // query number + { + (*numConnComps)++; + } else // populate pConnComps + { + if (valid_cc_counter == numEntries) { + return; + } + pConnComps[valid_cc_counter] = cc_ptr->m_user_handle; + valid_cc_counter += 1; + } + } + }); + } + } + }); + + *pEvent = event_handle; +} + +template +OutputIt partial_sum(InputIt first, InputIt last, OutputIt d_first) +{ + if (first == last) + return d_first; + + typename std::iterator_traits::value_type sum = *first; + *d_first = sum; + + while (++first != last) { + sum = sum + *first; + *++d_first = sum; + } + + return ++d_first; +} + +void triangulate_face( + // list of indices which define all triangles that result from the CDT + std::vector& cc_face_triangulation, + const std::shared_ptr& context_uptr, + const uint32_t cc_face_vcount, + const std::vector& cc_face_vertices, + const hmesh_t& cc, + const fd_t cc_face_iter) +{ + // + // init vars (which we do not want to be re-inititalizing) + // + std::vector cc_face_vcoords3d; + cc_face_vcoords3d.resize(cc_face_vcount); + + // NOTE: the elements of this array might be reversed, which occurs + // when the winding-order/orientation of "cc_face_iter" is flipped + // due to projection (see call to project_to_2d()) + std::vector cc_face_vcoords2d; // resized by project_to_2d(...) + // edge of face, which are used by triangulator as "fixed edges" to + // constrain the CDT + std::vector cc_face_edges; + + // used to check that all indices where used in the triangulation. + // If any entry is false after finshing triangulation then there will be a hole in the output + // This is use for sanity checking + std::vector cc_face_vtx_to_is_used_flag; + cc_face_vtx_to_is_used_flag.resize(cc_face_vcount); + + // for each vertex in face: get its coordinates + for (uint32_t i = 0; i < cc_face_vcount; ++i) { + + const vertex_descriptor_t cc_face_vertex_descr = SAFE_ACCESS(cc_face_vertices, i); + + const vec3& coords = cc.vertex(cc_face_vertex_descr); + + SAFE_ACCESS(cc_face_vcoords3d, i) = coords; + } + + // Project face-vertex coordinates to 2D + // + // NOTE: Although we are projecting using the plane normal of + // the plane, the shape and thus area of the face polygon is + // unchanged (but the winding order might change!). + // See definition of "project_to_2d()" + // ===================================================== + + // Maps each vertex in face to the reversed index if the polygon + // winding order was reversed due to projection to 2D. Otherwise, + // Simply stores the indices from 0 to N-1 + std::vector face_to_cdt_vmap(cc_face_vcount); + std::iota(std::begin(face_to_cdt_vmap), std::end(face_to_cdt_vmap), 0); + + { + vec3 cc_face_normal_vector; + double cc_face_plane_eq_dparam; // + const int largest_component_of_normal = compute_polygon_plane_coefficients( + cc_face_normal_vector, + cc_face_plane_eq_dparam, + cc_face_vcoords3d.data(), + (int)cc_face_vcount); + + project_to_2d(cc_face_vcoords2d, cc_face_vcoords3d, cc_face_normal_vector, largest_component_of_normal); + + // + // determine the signed area to check if the 2D face polygon + // is CW (negative) or CCW (positive) + // + + double signed_area = 0; + + for (uint32_t i = 0; i < cc_face_vcount - 2; ++i) { + vec2 cur = cc_face_vcoords2d[i]; + vec2 nxt = cc_face_vcoords2d[(i + 1) % cc_face_vcount]; + vec2 nxtnxt = cc_face_vcoords2d[(i + 2) % cc_face_vcount]; + signed_area += orient2d(cur, nxt, nxtnxt); + } + + const bool winding_order_flipped_due_to_projection = (signed_area < 0); + + if (winding_order_flipped_due_to_projection) { + // Reverse the order of points so that they are CCW + std::reverse(cc_face_vcoords2d.begin(), cc_face_vcoords2d.end()); + + // for each vertex index in face + for (int32_t i = 0; i < (int32_t)cc_face_vcount; ++i) { + // save reverse index map + face_to_cdt_vmap[i] = wrap_integer(-(i + 1), 0, cc_face_vcount - 1); + } + } + } + + // Winding order tracker (WOT): + // We use this halfedge data structure to ensure that the winding-order + // that is computed by the CDT triangulator is consistent with that + // of "cc_face_iter". + // Before triangulation, we populate it with the vertices, (half)edges and + // faces of the neighbours of "cc_face_iter". This information we will be + // used to check for proper winding-order when we later insert the CDT + // triangles whose winding order we assume to be inconsistent with + // "cc_face_iter" + hmesh_t wot; + // vertex descriptor map (from WOT to CC) + // std::map wot_to_cc_vmap; + + // vertex descriptor map (from CC to WOT) + std::map cc_to_wot_vmap; + + // The halfedge with-which we will identify the first CDT triangle to insert into the + // array "cc_face_triangulation" (see below when we actually do insertion). + // + // The order of triangle insertion must priotise the triangle adjacent to the boundary, + // which are those that are incident to a fixed-edge in the CDT triangulators output. + // We need "cc_seed_halfedge" to ensure that the first CDT triangle to be inserted is inserted with the + // correct winding order. This caters to the scenario where "WOT" does not + // contain enough information to be able to reject the winding-order with which we + // attempt to insert _the first_ CDT triangle into "cc_face_triangulation". + // + // It is perfectly possible for "cc_seed_halfedge" to remain null, which will happen if "cc_face_iter" + // is the only face in the connected component. + halfedge_descriptor_t cc_seed_halfedge = hmesh_t::null_halfedge(); + // ... those we have already saved in the wot + // This is needed to prevent attempting to add the same neighbour face into + // the WOT, which can happen if the cc_face_iter shares two or more edges + // with a neighbours (this is possible since our connected components + // can have n-gon faces ) + std::unordered_set wot_traversed_neighbours; + // ... in CCW order + const std::vector& cc_face_halfedges = cc.get_halfedges_around_face(cc_face_iter); + + // for each halfedge of face + for (std::vector::const_iterator hiter = cc_face_halfedges.begin(); hiter != cc_face_halfedges.end(); ++hiter) { + + halfedge_descriptor_t h = *hiter; + halfedge_descriptor_t opph = cc.opposite(h); + face_descriptor_t neigh = cc.face(opph); + + const bool neighbour_exists = (neigh != hmesh_t::null_face()); + + // neighbour exists and we have not already traversed it + // by adding it into the WOT + if (neighbour_exists && wot_traversed_neighbours.count(neigh) == 0) { + + if (cc_seed_halfedge == hmesh_t::null_halfedge()) { + cc_seed_halfedge = h; // set once based on first neighbour + } + + // + // insert the neighbour into WOT. + // REMEMBER: the stored connectivity information is what we + // will use to ensure that we insert triangles into "cc_face_triangulation" + // with the correct orientation. + // + + const std::vector& vertices_around_neighbour = cc.get_vertices_around_face(neigh); + + // face vertices (their descriptors for indexing into the WOT) + std::vector remapped_descrs; // from CC to WOT + + // for each vertex around neighbour + for (std::vector::const_iterator neigh_viter = vertices_around_neighbour.cbegin(); + neigh_viter != vertices_around_neighbour.cend(); ++neigh_viter) { + + // Check if vertex is already added into the WOT + std::map::const_iterator cc_to_wot_vmap_iter = cc_to_wot_vmap.find(*neigh_viter); + + if (cc_to_wot_vmap_iter == cc_to_wot_vmap.cend()) { // if not .. + + const vec3& neigh_vertex_coords = cc.vertex(*neigh_viter); + + const vertex_descriptor_t woe_vdescr = wot.add_vertex(neigh_vertex_coords); + + cc_to_wot_vmap_iter = cc_to_wot_vmap.insert(std::make_pair(*neigh_viter, woe_vdescr)).first; + } + + MCUT_ASSERT(cc_to_wot_vmap_iter != cc_to_wot_vmap.cend()); + + remapped_descrs.push_back(cc_to_wot_vmap_iter->second); + } + + // add the neighbour into WOT + face_descriptor_t nfd = wot.add_face(remapped_descrs); + + MCUT_ASSERT(nfd != hmesh_t::null_face()); + } + + wot_traversed_neighbours.insert(neigh); + } + + // Add (remaining) vertices of "cc_face_iter" into WOT. + // + // NOTE: some (or all) of the vertices of the "cc_face_iter" + // might already have been added when registering the neighbours. + // However, we must still check that all vertices have been added + // since a vertex is added (during the previous neighbour + // registration phase) if-and-only-if it is used by a neighbour. + // Thus vertices are only added during neighbour registration + // phase if they are incident to an edge that is shared with another + // face. + // If "cc_face_iter" has zero neighbours then non of it vertices + // will have been added in the previous phase. + // ======================================= + + // vertex descriptor map (from CDT to WOT) + std::map cdt_to_wot_vmap; + // vertex descriptor map (from WOT to CDT) + std::map wot_to_cdt_vmap; + + // for each vertex of face + for (uint32_t i = 0; i < cc_face_vcount; ++i) { + + const vertex_descriptor_t cc_face_vertex_descr = SAFE_ACCESS(cc_face_vertices, i); + + // check if vertex has already been added into the WOT + std::map::const_iterator fiter = cc_to_wot_vmap.find(cc_face_vertex_descr); + + if (fiter == cc_to_wot_vmap.cend()) { // ... if not + + const vec3& coords = SAFE_ACCESS(cc_face_vcoords3d, i); + + vertex_descriptor_t vd = wot.add_vertex(coords); + + fiter = cc_to_wot_vmap.insert(std::make_pair(cc_face_vertex_descr, vd)).first; + } + + cdt_to_wot_vmap[i] = fiter->second; + wot_to_cdt_vmap[fiter->second] = i; + } + + // + // In the following section, we will check-for and handle + // the case of having duplicate vertices in "cc_face_iter". + // + // Duplicate vertices arise when "cc_face_iter" is from the source-mesh + // and it has a partial-cut. Example: source-mesh=triangle and + // cut-mesh=triangle, where the cut-mesh does not split the source-mesh into + // two disjoint parts (i.e. a triangle and a quad) but instead + // induces a slit + // + + // Find the duplicates (if any) + const cdt::duplicates_info_t duplicates_info_pre = cdt::find_duplicates( + cc_face_vcoords2d.begin(), + cc_face_vcoords2d.end(), + cdt::get_x_coord_vec2d, + cdt::get_y_coord_vec2d); + + // number of duplicate vertices (if any) + const uint32_t duplicate_vcount = (uint32_t)duplicates_info_pre.duplicates.size(); + const bool have_duplicates = duplicate_vcount > 0; + + if (have_duplicates) { + + // for each pair of duplicate vertices + for (std::vector::const_iterator duplicate_vpair_iter = duplicates_info_pre.duplicates.cbegin(); + duplicate_vpair_iter != duplicates_info_pre.duplicates.cend(); ++duplicate_vpair_iter) { + + // + // The two vertices are duplicates because they have the _exact_ same coordinates. + // We make these points unique by perturbing the coordinates of one of them. This requires care + // because we want to ensure that "cc_face_iter" remains a simple polygon (without + // self-intersections) after perturbation. To do this, we must perturbation one + // vertex in the direction that lies on the left-side (i.e. CCW dir) of the two + // halfedges incident to that vertex. We also take care to account for the fact the two + // incident edges may be parallel. + // + + // current duplicate vertex (index in "cc_face_iter") + const std::int32_t perturbed_dvertex_id = (std::uint32_t)(*duplicate_vpair_iter); + // previous vertex (in "cc_face_iter") from current duplicate vertex + const std::uint32_t prev_vtx_id = wrap_integer(perturbed_dvertex_id - 1, 0, cc_face_vcount - 1); + // next vertex (in "cc_face_iter") from current duplicate vertex + const std::uint32_t next_vtx_id = wrap_integer(perturbed_dvertex_id + 1, 0, cc_face_vcount - 1); + // the other duplicate vertex of pair + const std::int32_t other_dvertex_id = (std::uint32_t)SAFE_ACCESS(duplicates_info_pre.mapping, perturbed_dvertex_id); + + vec2& perturbed_dvertex_coords = SAFE_ACCESS(cc_face_vcoords2d, perturbed_dvertex_id); // will be modified by shifting/perturbation + const vec2& prev_vtx_coords = SAFE_ACCESS(cc_face_vcoords2d, prev_vtx_id); + const vec2& next_vtx_coords = SAFE_ACCESS(cc_face_vcoords2d, next_vtx_id); + + // vector along incident edge, pointing from current to previous vertex (NOTE: clockwise dir, reverse) + const vec2 to_prev = prev_vtx_coords - perturbed_dvertex_coords; + // vector along incident edge, pointing from current to next vertex (NOTE: counter-clockwise dir, normal) + const vec2 to_next = next_vtx_coords - perturbed_dvertex_coords; + + // positive-value if three points are in CCW order (sign_t::ON_POSITIVE_SIDE) + // negative-value if three points are in CW order (sign_t::ON_NEGATIVE_SIDE) + // zero if collinear (sign_t::ON_ORIENTED_BOUNDARY) + const double orient2d_res = orient2d(perturbed_dvertex_coords, next_vtx_coords, prev_vtx_coords); + const sign_t orient2d_sgn = sign(orient2d_res); + + const double to_prev_sqr_len = squared_length(to_prev); + const double to_next_sqr_len = squared_length(to_next); + + // + // Now we must determine which side is the perturbation_vector must be + // pointing. i.e. the side of "perturbed_dvertex_coords" or the side + // of its duplicate + // + // NOTE: this is only really necessary if the partially cut polygon + // Has more that 3 intersection points (i.e. more than the case of + // one tip, and two duplicates) + // + + const int32_t flip = (orient2d_sgn == sign_t::ON_NEGATIVE_SIDE) ? -1 : 1; + + // + // Compute the perturbation vector as the average of the two incident edges eminating + // from the current vertex. NOTE: This perturbation vector should generally point in + // the direction of the polygon-interior (i.e. analogous to pushing the polygon at + // the location represented by perturbed_dvertex_coords) to cause a minute dent due to small + // loss of area. + // Normalization happens below + vec2 perturbation_vector = ((to_prev + to_next) / 2.0) * flip; + + // "orient2d()" is exact in the sense that it can depend on computations with numbers + // whose magnitude is lower than the threshold "orient2d_ccwerrboundA". It follows + // that this threshold is too "small" a number for us to be able to reliably compute + // stuff with the result of "orient2d()" that is near this threshold. + const double errbound = 1e-2; + + // We use "errbound", rather than "orient2d_res", to determine if the incident edges + // are parallel to give us sufficient room of numerical-precision to reliably compute + // the perturbation vector. + // In general, if the incident edges are not parallel then the perturbation vector + // is computed as the mean of "to_prev" and "to_next". Thus, being "too close" + // (within some threshold) to the edges being parallel, can induce unpredicatable + // numerical instabilities, where the mean-vector will be too close to the zero-vector + // and can complicate the task of perturbation. + const bool incident_edges_are_parallel = std::fabs(orient2d_res) <= std::fabs(errbound); + + if (incident_edges_are_parallel) { + // + // pick the shortest of the two incident edges and compute the + // orthogonal perturbation vector as the counter-clockwise rotation + // of this shortest incident edge. + // + + // flip sign so that the edge is in the CCW dir by pointing from "prev" to "cur" + vec2 edge_vec(-to_prev.x(), -to_prev.y()); + + if (to_prev_sqr_len > to_next_sqr_len) { + edge_vec = to_next; // pick shortest (NOTE: "to_next" is already in CCW dir) + } + + // rotate the selected edge by 90 degrees + const vec2 edge_vec_rotated90(-edge_vec.y(), edge_vec.x()); + + perturbation_vector = edge_vec_rotated90; + } + + const vec2 perturbation_dir = normalize(perturbation_vector); + + // + // Compute the maximum length between any two vertices in "cc_face_iter" as the + // largest length between any two vertices. + // + // This will be used to scale "perturbation_dir" so that we find the + // closest edge (from "perturbed_dvertex_coords") that is intersected by this ray. + // We will use the resulting information to determine the amount by-which + // "perturbed_dvertex_coords" is to be perturbed. + // + + // largest squared length between any two vertices in "cc_face_iter" + double largest_sqrd_length = -1.0; + + for (uint32_t i = 0; i < cc_face_vcount; ++i) { + + const vec2& a = SAFE_ACCESS(cc_face_vcoords2d, i); + + for (uint32_t j = 0; j < cc_face_vcount; ++j) { + + if (i == j) { + continue; // skip -> comparison is redundant + } + + const vec2& b = SAFE_ACCESS(cc_face_vcoords2d, j); + + const double sqrd_length = squared_length(b - a); + largest_sqrd_length = std::max(sqrd_length, largest_sqrd_length); + } + } + + // + // construct the segment with-which will will find the closest + // intersection point from "perturbed_dvertex_coords" to "perturbed_dvertex_coords + perturbation_dir*std::sqrt(largest_sqrd_length)""; + // + + const double shift_len = std::sqrt(largest_sqrd_length); + const vec2 shift = perturbation_dir * shift_len; + + vec2 intersection_point_on_edge = perturbed_dvertex_coords + shift; // some location potentially outside of polygon + + { + struct { + vec2 start; + vec2 end; + } segment; + segment.start = perturbed_dvertex_coords; + segment.end = perturbed_dvertex_coords + shift; + + // test segment against all edges to find closest intersection point + + double segment_min_tval = 1.0; + + // for each edge of face to be triangulated (number of vertices == number of edges) + for (std::uint32_t i = 0; i < cc_face_vcount; ++i) { + const std::uint32_t edge_start_idx = i; + const std::uint32_t edge_end_idx = (i + 1) % cc_face_vcount; + + if ((edge_start_idx == (uint32_t)perturbed_dvertex_id || edge_end_idx == (uint32_t)perturbed_dvertex_id) || // + (edge_start_idx == (uint32_t)other_dvertex_id || edge_end_idx == (uint32_t)other_dvertex_id)) { + continue; // impossible to properly intersect incident edges + } + + const vec2& edge_start_coords = SAFE_ACCESS(cc_face_vcoords2d, edge_start_idx); + const vec2& edge_end_coords = SAFE_ACCESS(cc_face_vcoords2d, edge_end_idx); + + double segment_tval; // parameter along segment + double edge_tval; // parameter along current edge + vec2 ipoint; // intersection point between segment and current edge + + const char result = compute_segment_intersection( + segment.start, segment.end, edge_start_coords, edge_end_coords, + ipoint, segment_tval, edge_tval); + + if (result == '1' && segment_min_tval > segment_tval) { // we have an clear intersection point + segment_min_tval = segment_tval; + intersection_point_on_edge = ipoint; + } else if ( + // segment and edge are collinear + result == 'e' || + // segment and edge are collinear, or one entity cuts through the vertex of the other + result == 'v') { + // pick the closest vertex of edge and compute "segment_tval" as a ratio of vector length + + // length from segment start to the start of edge + const double sqr_dist_to_edge_start = squared_length(edge_start_coords - segment.start); + // length from segment start to the end of edge + const double sqr_dist_to_edge_end = squared_length(edge_end_coords - segment.start); + + // length from start of segment to either start of edge or end of edge (depending on which is closer) + double sqr_dist_to_closest = sqr_dist_to_edge_start; + const vec2* ipoint_ptr = &edge_start_coords; + + if (sqr_dist_to_edge_start > sqr_dist_to_edge_end) { + sqr_dist_to_closest = sqr_dist_to_edge_end; + ipoint_ptr = &edge_end_coords; + } + + // ratio along segment + segment_tval = std::sqrt(sqr_dist_to_closest) / shift_len; + + if (segment_min_tval > segment_tval) { + segment_min_tval = segment_tval; + intersection_point_on_edge = *ipoint_ptr; // closest point + } + } + } + + MCUT_ASSERT(segment_min_tval <= 1.0); // ... because we started from max length between any two vertices + } + + // Shortened perturbation vector: shortening from the vector that is as long as the + // max length between any two vertices in "cc_face_iter", to a vector that runs + // from "perturbed_dvertex_coords" and upto the boundary-point of the "cc_face_iter", along + // "perturbation_vector" and passing through the interior of "cc_face_iter") + const vec2 revised_perturbation_vector = (intersection_point_on_edge - perturbed_dvertex_coords); + const double revised_perturbation_len = length(revised_perturbation_vector); + + const double scale = (errbound * revised_perturbation_len); + // The translation by which we perturb "perturbed_dvertex_coords" + // + // NOTE: since "perturbation_vector" was constructed from "to_prev" and "to_next", + // "displacement" is by-default pointing in the positive/CCW direction, which is torward + // the interior of the polygon represented by "cc_face_iter". + // Thus, the cases with "orient2d_sgn == sign_t::ON_POSITIVE_SIDE" and + // "orient2d_sgn == sign_t::ON_ORIENTED_BOUNDARY", result in the same displacement vector + const vec2 displacement = (perturbation_dir * scale); + + // perturb + perturbed_dvertex_coords = perturbed_dvertex_coords + displacement; + + //} // for (std::uint32_t dv_iter = 0; dv_iter < 2; ++dv_iter) { + } // for (std::vector::const_iterator duplicate_vpair_iter = duplicates_info_pre.duplicates.cbegin(); ... + } // if (have_duplicates) { + + // + // create the constraint edges for the CDT triangulator, which are just the edges of "cc_face_iter" + // + for (uint32_t i = 0; i < cc_face_vcount; ++i) { + cc_face_edges.push_back(cdt::edge_t(i, (i + 1) % cc_face_vcount)); + } + + // check for duplicate vertices again + const cdt::duplicates_info_t duplicates_info_post = cdt::find_duplicates( + cc_face_vcoords2d.begin(), + cc_face_vcoords2d.end(), + cdt::get_x_coord_vec2d, + cdt::get_y_coord_vec2d); + + if (!duplicates_info_post.duplicates.empty()) { + // This should not happen! Probably a good idea to email the author + context_uptr->dbg_cb( + MC_DEBUG_SOURCE_KERNEL, + MC_DEBUG_TYPE_ERROR, 0, + MC_DEBUG_SEVERITY_HIGH, "face f" + std::to_string(cc_face_iter) + " has duplicate vertices that could not be resolved (bug)"); + return; // skip to next face (will leave a hole in the output) + } + + // allocate triangulator + cdt::triangulator_t cdt(cdt::vertex_insertion_order_t::AS_GIVEN); + cdt.insert_vertices(cc_face_vcoords2d); // potentially perturbed (if duplicates exist) + cdt.insert_edges(cc_face_edges); + cdt.erase_outer_triangles(); // do the constrained delaunay triangulation + + // const std::unordered_map> tmp = cdt::edge_to_pieces_mapping(cdt.pieceToOriginals); + // const std::unordered_map> edgeToSplitVerts = cdt::get_edge_to_split_vertices_map(tmp, cdt.vertices); + + if (!cdt::check_topology(cdt)) { + + context_uptr->dbg_cb( + MC_DEBUG_SOURCE_KERNEL, + MC_DEBUG_TYPE_OTHER, 0, + MC_DEBUG_SEVERITY_NOTIFICATION, "triangulation on face f" + std::to_string(cc_face_iter) + " has invalid topology"); + + return; // skip to next face (will leave a hole in the output) + } + + if (cdt.triangles.empty()) { + context_uptr->dbg_cb( + MC_DEBUG_SOURCE_KERNEL, + MC_DEBUG_TYPE_OTHER, 0, + MC_DEBUG_SEVERITY_NOTIFICATION, "triangulation on face f" + std::to_string(cc_face_iter) + " produced zero faces"); + + return; // skip to next face (will leave a hole in the output) + } + + // + // In the following, we will now save the produce triangles into the + // output array "cc_face_triangulation". + // + + // number of CDT triangles + const uint32_t cc_face_triangle_count = (uint32_t)cdt.triangles.size(); + + // + // We insert triangles into "cc_face_triangulation" by using a + // breadth-first search-like flood-fill strategy to "walk" the + // triangles of the CDT. We start from a prescribed triangle next to the + // boundary of "cc_face_iter". + // + + // map vertices to CDT triangles + // Needed for the BFS traversal of triangles + std::vector> vertex_to_triangle_map(cc_face_vcount, std::vector()); + + // for each CDT triangle + for (uint32_t i = 0; i < cc_face_triangle_count; ++i) { + + const cdt::triangle_t& triangle = SAFE_ACCESS(cdt.triangles, i); + + // for each triangle vertex + for (uint32_t j = 0; j < 3; j++) { + const uint32_t cdt_vertex_id = SAFE_ACCESS(triangle.vertices, j); + const uint32_t cc_face_vertex_id = SAFE_ACCESS(face_to_cdt_vmap, cdt_vertex_id); + std::vector& incident_triangles = SAFE_ACCESS(vertex_to_triangle_map, cc_face_vertex_id); + incident_triangles.push_back(i); // save mapping + } + } + + // start with any boundary edge (AKA constraint/fixed edge) + std::unordered_set::const_iterator fixed_edge_iter = cdt.fixedEdges.cbegin(); + + // NOTE: in the case that "cc_seed_halfedge" is null, then "cc_face_iter" + // is the only face in its connected component (mesh) and therefore + // it has no neighbours. In this case, the winding-order of the produced triangles + // is dependent on the CDT triangulator. The MCUT frontend will at best be able to + // ensure that all CDT triangles have consistent winding order (even if the triangulator + // produced mixed winding orders between the resulting triangles) but we cannot guarrantee + // the "front-facing" side of triangulated "cc_face_iter" will match that of its + // original non-triangulated form from the connected component. + // + // We leave that to the user to fix upon visual inspection. + // + const bool have_seed_halfedge = cc_seed_halfedge != hmesh_t::null_halfedge(); + + if (have_seed_halfedge) { + // if the seed halfedge exists then the triangulated face must have + // atleast one neighbour + MCUT_ASSERT(wot.number_of_faces() != 0); + + // source and target descriptor in the connected component + const vertex_descriptor_t cc_seed_halfedge_src = cc.source(cc_seed_halfedge); + const vertex_descriptor_t cc_seed_halfedge_tgt = cc.target(cc_seed_halfedge); + + // source and target descriptor in the face + const vertex_descriptor_t woe_src = SAFE_ACCESS(cc_to_wot_vmap, cc_seed_halfedge_src); + const uint32_t cdt_src = SAFE_ACCESS(wot_to_cdt_vmap, woe_src); + const vertex_descriptor_t woe_tgt = SAFE_ACCESS(cc_to_wot_vmap, cc_seed_halfedge_tgt); + const uint32_t cdt_tgt = SAFE_ACCESS(wot_to_cdt_vmap, woe_tgt); + + // find the fixed edge in the CDT matching the vertices of the seed halfedge + + fixed_edge_iter = std::find_if( + cdt.fixedEdges.cbegin(), + cdt.fixedEdges.cend(), + [&](const cdt::edge_t& e) -> bool { + return (e.v1() == cdt_src && e.v2() == cdt_tgt) || // + (e.v2() == cdt_src && e.v1() == cdt_tgt); + }); + + MCUT_ASSERT(fixed_edge_iter != cdt.fixedEdges.cend()); + } + + // must always exist since cdt edge ultimately came from the CC, and also + // due to the fact that we have inserted edges into the CDT + MCUT_ASSERT(fixed_edge_iter != cdt.fixedEdges.cend()); + + // get the two vertices of the "seed" fixed edge (indices into CDT) + const std::uint32_t fixed_edge_vtx0_id = fixed_edge_iter->v1(); + const std::uint32_t fixed_edge_vtx1_id = fixed_edge_iter->v2(); + + // + // Since these vertices share an edge, they will share a triangle in the CDT + // So lets get that shared triangle, which will be the seed triangle for the + // traversal process, which we will use to walk and insert triangles into + // the output array "cc_face_triangulation" + // + + // incident triangles of first vertex + const std::vector& fixed_edge_vtx0_tris = SAFE_ACCESS(vertex_to_triangle_map, fixed_edge_vtx0_id); + MCUT_ASSERT(fixed_edge_vtx0_tris.empty() == false); + // incident triangles of second vertex + const std::vector& fixed_edge_vtx1_tris = SAFE_ACCESS(vertex_to_triangle_map, fixed_edge_vtx1_id); + MCUT_ASSERT(fixed_edge_vtx1_tris.empty() == false); + + // the shared triangle between the two vertices of fixed edge + std::uint32_t fix_edge_seed_triangle = cdt::null_neighbour; + + // for each CDT triangle incident to the first vertex + for (std::vector::const_iterator it = fixed_edge_vtx0_tris.begin(); it != fixed_edge_vtx0_tris.end(); ++it) { + + if (*it == cdt::null_neighbour) { + continue; + } + + // does it exist in the incident triangle list of the other vertex? + if (std::find(fixed_edge_vtx1_tris.begin(), fixed_edge_vtx1_tris.end(), *it) != fixed_edge_vtx1_tris.end()) { + fix_edge_seed_triangle = *it; // found + break; // done + } + } + + MCUT_ASSERT(fix_edge_seed_triangle != cdt::null_neighbour); + + std::stack seeds(std::deque(1, fix_edge_seed_triangle)); + + // collection of traversed CDT triangles + std::unordered_set traversed; + + while (!seeds.empty()) { // while we still have triangles to walk + + const std::uint32_t curr_triangle_id = seeds.top(); + seeds.pop(); + + traversed.insert(curr_triangle_id); // those we have walked + + const cdt::triangle_t& triangle = cdt.triangles[curr_triangle_id]; + + // + // insert current triangle into our triangulated CC mesh + // + const uint32_t triangle_vertex_count = 3; + + // from CDT/"cc_face_iter" indices to WOT descriptors + std::vector remapped_triangle(triangle_vertex_count, hmesh_t::null_vertex()); + + // for each vertex of triangle + for (uint32_t i = 0; i < triangle_vertex_count; i++) { + // index of current vertex in CDT/"cc_face_iter" + const uint32_t cdt_vertex_id = SAFE_ACCESS(triangle.vertices, i); + const uint32_t cc_face_vertex_id = SAFE_ACCESS(face_to_cdt_vmap, cdt_vertex_id); + + // mark vertex as used (for sanity check) + SAFE_ACCESS(cc_face_vtx_to_is_used_flag, cc_face_vertex_id) = true; + + // remap triangle vertex index + SAFE_ACCESS(remapped_triangle, i) = SAFE_ACCESS(cdt_to_wot_vmap, cc_face_vertex_id); + + // save index into output array (where every three indices is a triangle) + cc_face_triangulation.emplace_back(cc_face_vertex_id); + } + + // check that the winding order respects the winding order of "cc_face_iter" + const bool is_insertible = wot.is_insertable(remapped_triangle); + + if (!is_insertible) { // CDT somehow produce a triangle with reversed winding-order (i.e. CW) + + // flip the winding order by simply swapping indices + uint32_t a = remapped_triangle[0]; + uint32_t c = remapped_triangle[2]; + std::swap(a, c); // swap indices in the remapped triangle + remapped_triangle[0] = vertex_descriptor_t(a); + remapped_triangle[2] = vertex_descriptor_t(c); + const size_t N = cc_face_triangulation.size(); + + // swap indice in the saved triangle + std::swap(cc_face_triangulation[N - 1], cc_face_triangulation[N - 3]); // reverse last added triangle's indices + } + + // add face into our WO wot + face_descriptor_t fd = wot.add_face(remapped_triangle); // keep track of added triangles from CDT + + // if this happens then CDT gave us a strange triangulation e.g. duplicate triangles with opposite winding order + if (fd == hmesh_t::null_face()) { + // Simply remove/ignore the offending triangle. We cannot do anything at this stage. + cc_face_triangulation.pop_back(); + cc_face_triangulation.pop_back(); + cc_face_triangulation.pop_back(); + + const std::string msg = "triangulation on face f" + std::to_string(cc_face_iter) + " produced invalid triangles that could not be stored"; + + context_uptr->dbg_cb( + MC_DEBUG_SOURCE_KERNEL, + MC_DEBUG_TYPE_OTHER, 0, + MC_DEBUG_SEVERITY_HIGH, msg); + } + + // + // We will now add the neighbouring CDT triangles into queue/stack + // + + // for each CDT vertex + for (std::uint32_t i(0); i < triangle_vertex_count; ++i) { + + const uint32_t next = triangle.vertices[cdt::ccw(i)]; + const uint32_t prev = triangle.vertices[cdt::cw(i)]; + const cdt::edge_t query_edge(next, prev); + + if (cdt.fixedEdges.count(query_edge)) { + continue; // current edge is fixed edge so there is no neighbour + } + + const std::uint32_t neighbour_index = triangle.neighbors[cdt::get_opposite_neighbour_from_vertex(i)]; + + if (neighbour_index != cdt::null_neighbour && traversed.count(neighbour_index) == 0) { + seeds.push(neighbour_index); + } + } + } // while (!seeds.empty()) { + + // every triangle in the finalized CDT must be walked! + MCUT_ASSERT(traversed.size() == cdt.triangles.size()); // this might be violated if CDT produced duplicate triangles + + // + // Final sanity check + // + + for (std::uint32_t i = 0; i < (std::uint32_t)cc_face_vcount; ++i) { + if (SAFE_ACCESS(cc_face_vtx_to_is_used_flag, i) != true) { + context_uptr->dbg_cb( + MC_DEBUG_SOURCE_KERNEL, + MC_DEBUG_TYPE_OTHER, 0, + MC_DEBUG_SEVERITY_HIGH, "triangulation on face f" + std::to_string(cc_face_iter) + " did not use vertex v" + std::to_string(i)); + } + } +} + +/* Helper function (to prevent code duplication), which maps a given index of + an internal-inputmesh face, to it corresponding face-index value in the user-provided + input-mesh. + + This is needed because MCUT may modify the user-inputmesh (source mesh or cut mesh) + when it performs polygon partitioning (subject to teh nature of the intersection). + + Refer to the comments about polygon partitioning + */ +uint32_t map_internal_inputmesh_face_idx_to_user_inputmesh_face_idx( + const uint32_t internal_inputmesh_face_idx, + const std::shared_ptr& cc_uptr) +{ + // uint32_t internal_inputmesh_face_idx = (uint32_t)cc_uptr->kernel_hmesh_data->data_maps.face_map[i]; + uint32_t user_inputmesh_face_idx = INT32_MAX; // return value + const bool internal_input_mesh_face_idx_is_for_src_mesh = (internal_inputmesh_face_idx < cc_uptr->internal_sourcemesh_face_count); + + if (internal_input_mesh_face_idx_is_for_src_mesh) { + + std::unordered_map::const_iterator fiter = cc_uptr->source_hmesh_child_to_usermesh_birth_face->find(fd_t(internal_inputmesh_face_idx)); + + if (fiter != cc_uptr->source_hmesh_child_to_usermesh_birth_face->cend()) { + user_inputmesh_face_idx = fiter->second; + } else { + user_inputmesh_face_idx = internal_inputmesh_face_idx; + } + MCUT_ASSERT(user_inputmesh_face_idx < cc_uptr->client_sourcemesh_face_count); + } else // internalInputMeshVertexDescrIsForCutMesh + { + std::unordered_map::const_iterator fiter = cc_uptr->cut_hmesh_child_to_usermesh_birth_face->find(fd_t(internal_inputmesh_face_idx)); + + if (fiter != cc_uptr->cut_hmesh_child_to_usermesh_birth_face->cend()) { + uint32_t unoffsettedDescr = (fiter->second - cc_uptr->internal_sourcemesh_face_count); + user_inputmesh_face_idx = unoffsettedDescr + cc_uptr->client_sourcemesh_face_count; + } else { + uint32_t unoffsettedDescr = (internal_inputmesh_face_idx - cc_uptr->internal_sourcemesh_face_count); + user_inputmesh_face_idx = unoffsettedDescr + cc_uptr->client_sourcemesh_face_count; + } + } + + return user_inputmesh_face_idx; +} + +void get_connected_component_data_impl_detail( + std::shared_ptr context_ptr, + const McConnectedComponent connCompId, + McFlags flags, + McSize bytes, + McVoid* pMem, + McSize* pNumBytes) +{ +#if 0 + std::map>::iterator context_entry_iter = g_contexts.find(context); + + if (context_entry_iter == g_contexts.end()) { + throw std::invalid_argument("invalid context"); + } + + std::unique_ptr& context_uptr = context_entry_iter->second; + + std::map>::iterator cc_entry_iter = context_uptr->connected_components.find(connCompId); + + if (cc_entry_iter == context_uptr->connected_components.cend()) { + throw std::invalid_argument("invalid connected component"); + } + + std::shared_ptr& cc_uptr = cc_entry_iter->second; +#endif + + std::shared_ptr cc_uptr = context_ptr->connected_components.find_first_if([=](const std::shared_ptr ccptr) { return ccptr->m_user_handle == connCompId; }); + if (!cc_uptr) { + throw std::invalid_argument("invalid connected component"); + } + switch (flags) { + + case MC_CONNECTED_COMPONENT_DATA_VERTEX_FLOAT: { + SCOPED_TIMER("MC_CONNECTED_COMPONENT_DATA_VERTEX_FLOAT"); + + const McSize allocated_bytes = cc_uptr->kernel_hmesh_data->mesh->number_of_vertices() * sizeof(float) * 3ul; // cc_uptr->indexArrayMesh.numVertices * sizeof(float) * 3; + + if (pMem == nullptr) { + *pNumBytes = allocated_bytes; + } else { // copy mem to client ptr + + if (bytes > allocated_bytes) { + throw std::invalid_argument("out of bounds memory access"); + } // if + + // an element is a component + const McSize nelems = (McSize)(bytes / sizeof(float)); + + if (nelems % 3 != 0) { + throw std::invalid_argument("invalid number of bytes"); + } + + const McSize num_vertices_to_copy = (nelems / 3); + + float* casted_ptr = reinterpret_cast(pMem); + +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + { + + auto fn_copy_vertex_coords = [&casted_ptr, &cc_uptr, &num_vertices_to_copy](vertex_array_iterator_t block_start_, vertex_array_iterator_t block_end_) { + // thread starting offset (in vertex count) in the "array of vertices" + const McSize base_offset = std ::distance(cc_uptr->kernel_hmesh_data->mesh->vertices_begin(), block_start_); + + McSize elem_offset = base_offset * 3; + + for (vertex_array_iterator_t vertex_iter = block_start_; vertex_iter != block_end_; ++vertex_iter) { + + if (((elem_offset + 1) / 3) == num_vertices_to_copy) { + break; // reach what the user asked for + } + + const vertex_descriptor_t descr = *vertex_iter; + const vec3& coords = cc_uptr->kernel_hmesh_data->mesh->vertex(descr); + + // for each component of coordinate + for (int i = 0; i < 3; ++i) { + const float val = static_cast(coords[i]); + *(casted_ptr + elem_offset) = val; + elem_offset += 1; + } + } + }; + + parallel_for( + context_ptr->get_shared_compute_threadpool(), + cc_uptr->kernel_hmesh_data->mesh->vertices_begin(), + cc_uptr->kernel_hmesh_data->mesh->vertices_end(), + fn_copy_vertex_coords); + } +#else // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + McSize elem_offset = 0; + for (vertex_array_iterator_t viter = cc_uptr->kernel_hmesh_data->mesh->vertices_begin(); viter != cc_uptr->kernel_hmesh_data->mesh->vertices_end(); ++viter) { + const vec3& coords = cc_uptr->kernel_hmesh_data->mesh->vertex(*viter); + + for (int i = 0; i < 3; ++i) { + const float val = static_cast(coords[i]); + *(casted_ptr + elem_offset) = val; + elem_offset += 1; + } + + if ((elem_offset / 3) == num_vertices_to_copy) { + break; + } + } + + MCUT_ASSERT((elem_offset * sizeof(float)) <= allocated_bytes); +#endif // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + } + } break; + case MC_CONNECTED_COMPONENT_DATA_VERTEX_DOUBLE: { + SCOPED_TIMER("MC_CONNECTED_COMPONENT_DATA_VERTEX_DOUBLE"); + const McSize allocated_bytes = cc_uptr->kernel_hmesh_data->mesh->number_of_vertices() * sizeof(double) * 3ul; // cc_uptr->indexArrayMesh.numVertices * sizeof(float) * 3; + + if (pMem == nullptr) { + *pNumBytes = allocated_bytes; + } else { // copy mem to client ptr + + if (bytes > allocated_bytes) { + throw std::invalid_argument("out of bounds memory access"); + } // if + + // an element is a component + const int64_t nelems = (McSize)(bytes / sizeof(double)); + + if (nelems % 3 != 0) { + throw std::invalid_argument("invalid number of bytes"); + } + + const McSize num_vertices_to_copy = (nelems / 3); + + double* casted_ptr = reinterpret_cast(pMem); + +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + { + typedef vertex_array_iterator_t InputStorageIteratorType; + + auto fn_copy_vertex_coords = [&casted_ptr, &cc_uptr, &num_vertices_to_copy](vertex_array_iterator_t block_start_, vertex_array_iterator_t block_end_) { + // thread starting offset (in vertex count) in the "array of vertices" + const McSize base_offset = std ::distance(cc_uptr->kernel_hmesh_data->mesh->vertices_begin(), block_start_); + + McSize elem_offset = base_offset * 3; + + for (InputStorageIteratorType vertex_iter = block_start_; vertex_iter != block_end_; ++vertex_iter) { + + if ((elem_offset / 3) == num_vertices_to_copy) { + break; // reach what the user asked for + } + + const vertex_descriptor_t descr = *vertex_iter; + const vec3& coords = cc_uptr->kernel_hmesh_data->mesh->vertex(descr); + + // for each component of coordinate + for (int i = 0; i < 3; ++i) { + const double val = static_cast(coords[i]); + *(casted_ptr + elem_offset) = val; + elem_offset += 1; + } + } + }; + + parallel_for( + context_ptr->get_shared_compute_threadpool(), + cc_uptr->kernel_hmesh_data->mesh->vertices_begin(), + cc_uptr->kernel_hmesh_data->mesh->vertices_end(), + fn_copy_vertex_coords); + } +#else // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + McSize elem_offset = 0; + for (vertex_array_iterator_t viter = cc_uptr->kernel_hmesh_data->mesh->vertices_begin(); viter != cc_uptr->kernel_hmesh_data->mesh->vertices_end(); ++viter) { + + const vec3& coords = cc_uptr->kernel_hmesh_data->mesh->vertex(*viter); + + for (int i = 0; i < 3; ++i) { + *(casted_ptr + elem_offset) = coords[i]; + elem_offset += 1; + } + + if ((elem_offset / 3) == num_vertices_to_copy) { + break; + } + } + + MCUT_ASSERT((elem_offset * sizeof(float)) <= allocated_bytes); +#endif // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + } + } break; + case MC_CONNECTED_COMPONENT_DATA_FACE: { + SCOPED_TIMER("MC_CONNECTED_COMPONENT_DATA_FACE"); + if (pMem == nullptr) { // querying for number of bytes + uint32_t num_indices = 0; + +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + { + // each worker-thread will count the number of indices according + // to the number of faces in its range/block. The master thread + // will then sum the total from all threads + + // typedef std::tuple OutputStorageTypesTuple; // store number of indices computed by worker + typedef face_array_iterator_t InputStorageIteratorType; + + auto fn_count_indices = [&cc_uptr](InputStorageIteratorType block_start_, InputStorageIteratorType block_end_) { + uint32_t num_indices_LOCAL = 0; + + // thread starting offset (in vertex count) in the "array of vertices" + // const McSize face_base_offset = std::distance(cc_uptr->kernel_hmesh_data->mesh->faces_begin(), block_start_); + + for (InputStorageIteratorType fiter = block_start_; fiter != block_end_; ++fiter) { + + const uint32_t num_vertices_around_face = cc_uptr->kernel_hmesh_data->mesh->get_num_vertices_around_face(*fiter); + + MCUT_ASSERT(num_vertices_around_face >= 3); + + num_indices_LOCAL += num_vertices_around_face; + } + + return num_indices_LOCAL; + }; + + std::vector> futures; + uint32_t partial_res; + + parallel_for( + context_ptr->get_shared_compute_threadpool(), + cc_uptr->kernel_hmesh_data->mesh->faces_begin(), + cc_uptr->kernel_hmesh_data->mesh->faces_end(), + fn_count_indices, + partial_res, // output computed by master thread + futures); + + const uint32_t& num_indices_MASTER_THREAD_LOCAL = partial_res; + num_indices += num_indices_MASTER_THREAD_LOCAL; + + // wait for all worker-threads to finish copies + for (uint32_t i = 0; i < (uint32_t)futures.size(); ++i) { + const uint32_t num_indices_THREAD_LOCAL = futures[i].get(); + num_indices += num_indices_THREAD_LOCAL; + } + } +#else // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + for (face_array_iterator_t fiter = cc_uptr->kernel_hmesh_data->mesh->faces_begin(); fiter != cc_uptr->kernel_hmesh_data->mesh->faces_end(); ++fiter) { + const uint32_t num_vertices_around_face = cc_uptr->kernel_hmesh_data->mesh->get_num_vertices_around_face(*fiter); + + MCUT_ASSERT(num_vertices_around_face >= 3); + + num_indices += num_vertices_around_face; + } + +#endif // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + MCUT_ASSERT(num_indices >= 3); // min is a triangle + + *pNumBytes = num_indices * sizeof(uint32_t); + } else { // querying for data to copy back to user pointer + if (bytes % sizeof(uint32_t) != 0) { + throw std::invalid_argument("invalid number of bytes"); + } + + const uint32_t num_indices_to_copy = bytes / sizeof(uint32_t); + uint32_t* casted_ptr = reinterpret_cast(pMem); + +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + { + // step 1: compute array storing face sizes (recursive API call) + // - for computing exclusive sum + // step 2: compute exclusive sum array ( in spirit of std::exclusive_scan) + // - for determining per-thread (work-block) output-array offsets + // step 3: copy face indices into output array using offsets from previous steps + // - final result that is stored in user-output array + + const uint32_t nfaces = cc_uptr->kernel_hmesh_data->mesh->number_of_faces(); + + // + // step 1 + // + + // If client already called mcGetConnectedComponentData(..., MC_CONNECTED_COMPONENT_DATA_FACE_SIZE, numBytes, faceSizes.data(), NULL) + // then we should have already cached the array of face sizes + + if (!cc_uptr->face_sizes_cache_initialized) { // fill the cache by calling the API, within the API! + + // this is like resizing output array in the client application, after knowing the number of faces in CC + cc_uptr->face_sizes_cache.resize(nfaces); + + const std::size_t num_bytes = nfaces * sizeof(uint32_t); + + // recursive Internal API call: populate cache here, which also sets "cc_uptr->face_sizes_cache_initialized" to true + get_connected_component_data_impl_detail(context_ptr, connCompId, MC_CONNECTED_COMPONENT_DATA_FACE_SIZE, num_bytes, cc_uptr->face_sizes_cache.data(), NULL); + } else { // cache already initialized + MCUT_ASSERT(cc_uptr->face_sizes_cache.empty() == false); + MCUT_ASSERT(cc_uptr->face_sizes_cache_initialized == true); + } + + // + // step 2 + // + std::vector partial_sum_vec = cc_uptr->face_sizes_cache; // copy + + parallel_partial_sum(context_ptr->get_shared_compute_threadpool(), partial_sum_vec.begin(), partial_sum_vec.end()); + + // + // step 3 + // + + auto fn_face_indices_copy = [&cc_uptr, &partial_sum_vec, &casted_ptr, &num_indices_to_copy](face_array_iterator_t block_start_, face_array_iterator_t block_end_) { + const uint32_t base_face_offset = std::distance(cc_uptr->kernel_hmesh_data->mesh->faces_begin(), block_start_); + + MCUT_ASSERT(base_face_offset < (uint32_t)cc_uptr->face_sizes_cache.size()); + + // the first face in the range between block start and end + const uint32_t base_face_vertex_count = SAFE_ACCESS(cc_uptr->face_sizes_cache, base_face_offset); + + const uint32_t partial_sum_vec_val = SAFE_ACCESS(partial_sum_vec, base_face_offset); + const uint32_t index_arr_base_offset = partial_sum_vec_val - base_face_vertex_count; + uint32_t index_arr_offset = index_arr_base_offset; + + std::vector vertices_around_face; // tmp to prevent reallocations + vertices_around_face.reserve(3); + + for (face_array_iterator_t f_iter = block_start_; f_iter != block_end_; ++f_iter) { + + vertices_around_face.clear(); + cc_uptr->kernel_hmesh_data->mesh->get_vertices_around_face(vertices_around_face, *f_iter); + const uint32_t num_vertices_around_face = (uint32_t)vertices_around_face.size(); + + MCUT_ASSERT(num_vertices_around_face >= 3u); + + // for each vertex in face + for (uint32_t i = 0; i < num_vertices_around_face; ++i) { + const uint32_t vertex_idx = (uint32_t)SAFE_ACCESS(vertices_around_face, i); + *(casted_ptr + index_arr_offset) = vertex_idx; + ++index_arr_offset; + + if (index_arr_offset == num_indices_to_copy) { + break; + } + } + } + }; + + parallel_for( + context_ptr->get_shared_compute_threadpool(), + cc_uptr->kernel_hmesh_data->mesh->faces_begin(), + cc_uptr->kernel_hmesh_data->mesh->faces_end(), + fn_face_indices_copy, + (1 << 14)); // blocks until all work is done + } +#else // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + + std::vector cc_face_vertices; + uint32_t elem_offset = 0; + + for (face_array_iterator_t fiter = cc_uptr->kernel_hmesh_data->mesh->faces_begin(); fiter != cc_uptr->kernel_hmesh_data->mesh->faces_end(); ++fiter) { + + cc_face_vertices.clear(); + cc_uptr->kernel_hmesh_data->mesh->get_vertices_around_face(cc_face_vertices, *fiter); + const uint32_t num_vertices_around_face = (uint32_t)cc_face_vertices.size(); + + MCUT_ASSERT(num_vertices_around_face >= 3u); + + for (uint32_t i = 0; i < num_vertices_around_face; ++i) { + const uint32_t vertex_idx = (uint32_t)cc_face_vertices[i]; + *(casted_ptr + elem_offset) = vertex_idx; + ++elem_offset; + + if (elem_offset == num_indices_to_copy) { + break; + } + } + } + + MCUT_ASSERT(elem_offset == num_indices_to_copy); +#endif // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + + } // if (pMem == nullptr) { // querying for number of bytes + } break; + case MC_CONNECTED_COMPONENT_DATA_FACE_SIZE: { // non-triangulated only (don't want to store redundant information) + SCOPED_TIMER("MC_CONNECTED_COMPONENT_DATA_FACE_SIZE"); + if (pMem == nullptr) { + *pNumBytes = cc_uptr->kernel_hmesh_data->mesh->number_of_faces() * sizeof(uint32_t); // each face has a size (num verts) + } else { + if (bytes > cc_uptr->kernel_hmesh_data->mesh->number_of_faces() * sizeof(uint32_t)) { + throw std::invalid_argument("out of bounds memory access"); + } + + if (bytes % sizeof(uint32_t) != 0) { + throw std::invalid_argument("invalid number of bytes"); + } + + uint32_t* casted_ptr = reinterpret_cast(pMem); + +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + { + const uint32_t face_count = cc_uptr->kernel_hmesh_data->mesh->number_of_faces(); + + if (!cc_uptr->face_sizes_cache_initialized) { // init cache by storing data into it + // the code execution can also reach here because we asked for the cache + // to be populated in order to compute/copy the face indices in parallel. + // see: MC_CONNECTED_COMPONENT_DATA_FACE case + const bool cache_allocated_prior = !cc_uptr->face_sizes_cache.empty(); + + if (!cache_allocated_prior) { + cc_uptr->face_sizes_cache.resize(face_count); + } + + auto fn_face_size = [&cc_uptr](std::vector::iterator block_start_, std::vector::iterator block_end_) { + const uint32_t face_base_offset = (uint32_t)std::distance(cc_uptr->face_sizes_cache.begin(), block_start_); + uint32_t face_offset = face_base_offset; + + for (std::vector::iterator fs_iter = block_start_; fs_iter != block_end_; ++fs_iter) { + + const face_descriptor_t descr(face_offset); + + const uint32_t num_vertices_around_face = cc_uptr->kernel_hmesh_data->mesh->get_num_vertices_around_face(descr); + + MCUT_ASSERT(num_vertices_around_face >= 3); + + *fs_iter = num_vertices_around_face; + + face_offset++; + } + }; + + parallel_for( + context_ptr->get_shared_compute_threadpool(), + cc_uptr->face_sizes_cache.begin(), + cc_uptr->face_sizes_cache.end(), + fn_face_size); // blocks until all work is done + + cc_uptr->face_sizes_cache_initialized = true; + } + + // the pointers are different if "cc_uptr->face_sizes_cache" is not + // being populated in the current call + const McVoid* src_ptr = reinterpret_cast(&(cc_uptr->face_sizes_cache[0])); + const McVoid* dst_ptr = pMem; + const bool writing_to_client_pointer = (src_ptr != dst_ptr); + + if (writing_to_client_pointer) // copy only if "casted_ptr" is client pointer + { + // copy to user pointer + memcpy(casted_ptr, &(cc_uptr->face_sizes_cache[0]), sizeof(uint32_t) * face_count); + } + } +#else + // + McSize elem_offset = 0; + + for (face_array_iterator_t fiter = cc_uptr->kernel_hmesh_data->mesh->faces_begin(); fiter != cc_uptr->kernel_hmesh_data->mesh->faces_end(); ++fiter) { + const uint32_t num_vertices_around_face = cc_uptr->kernel_hmesh_data->mesh->get_num_vertices_around_face(*fiter); + + MCUT_ASSERT(num_vertices_around_face >= 3); + + *(casted_ptr + elem_offset) = num_vertices_around_face; + ++elem_offset; + } +#endif + } + } break; + case MC_CONNECTED_COMPONENT_DATA_FACE_ADJACENT_FACE: { + SCOPED_TIMER("MC_CONNECTED_COMPONENT_DATA_FACE_ADJACENT_FACE"); + if (pMem == nullptr) { + + MCUT_ASSERT(pNumBytes != nullptr); + + uint32_t num_face_adjacent_face_indices = 0; + +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + { + // each worker-thread will count the number of indices according + // to the number of faces in its range/block. The master thread + // will then sum the total from all threads + + auto fn_count_faces_around_face = [&cc_uptr](face_array_iterator_t block_start_, face_array_iterator_t block_end_) { + uint32_t num_face_adjacent_face_indices_LOCAL = 0; + + for (face_array_iterator_t fiter = block_start_; fiter != block_end_; ++fiter) { + + const uint32_t num_faces_around_face = cc_uptr->kernel_hmesh_data->mesh->get_num_faces_around_face(*fiter, nullptr); + num_face_adjacent_face_indices_LOCAL += num_faces_around_face; + } + + return num_face_adjacent_face_indices_LOCAL; + }; + + std::vector> futures; + uint32_t partial_res; + + parallel_for( + context_ptr->get_shared_compute_threadpool(), + cc_uptr->kernel_hmesh_data->mesh->faces_begin(), + cc_uptr->kernel_hmesh_data->mesh->faces_end(), + fn_count_faces_around_face, + partial_res, // output computed by master thread + futures); + + const uint32_t& num_face_adjacent_face_indices_MASTER_THREAD_LOCAL = partial_res; + num_face_adjacent_face_indices += num_face_adjacent_face_indices_MASTER_THREAD_LOCAL; + + // wait for all worker-threads to finish copies + for (uint32_t i = 0; i < (uint32_t)futures.size(); ++i) { + const uint32_t num_face_adjacent_face_indices_THREAD_LOCAL = futures[i].get(); + num_face_adjacent_face_indices += num_face_adjacent_face_indices_THREAD_LOCAL; + } + } +#else // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + for (face_array_iterator_t fiter = cc_uptr->kernel_hmesh_data->mesh->faces_begin(); fiter != cc_uptr->kernel_hmesh_data->mesh->faces_end(); ++fiter) { + const uint32_t num_faces_around_face = cc_uptr->kernel_hmesh_data->mesh->get_num_faces_around_face(*fiter, nullptr); + num_face_adjacent_face_indices += num_faces_around_face; + } +#endif + + *pNumBytes = num_face_adjacent_face_indices * sizeof(uint32_t); + } else { + + if (bytes % sizeof(uint32_t) != 0) { + throw std::invalid_argument("invalid number of bytes"); + } + + uint32_t* casted_ptr = reinterpret_cast(pMem); + +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + { + // step 1: compute array storing face-adjacent-face sizes (recursive API call) + // - for computing exclusive sum + // step 2: compute exclusive sum array ( in spirit of std::exclusive_scan) + // - for determining per-thread (work-block) output-array offsets + // step 3: copy adjacent face indices into output array using offsets from previous steps + // - final result that is stored in user-output array + + const uint32_t nfaces = cc_uptr->kernel_hmesh_data->mesh->number_of_faces(); + + // + // step 1 + // + + // If client already called mcGetConnectedComponentData(..., MC_CONNECTED_COMPONENT_DATA_FACE_ADJACENT_FACE_SIZE, numBytes, faceAdjFaceSizes.data(), NULL) + // then we should have already cached the array of face sizes + + if (!cc_uptr->face_adjacent_faces_size_cache_initialized) { // fill the cache by calling the API, within the API! + + // this is like resizing output array in the client application, after knowing the number of faces in CC + cc_uptr->face_adjacent_faces_size_cache.resize(nfaces); + + const std::size_t num_bytes = nfaces * sizeof(uint32_t); + + // recursive Internal API call: populate cache here, which also sets "cc_uptr->face_adjacent_faces_size_cache" to true + get_connected_component_data_impl_detail( + context_ptr, + connCompId, + MC_CONNECTED_COMPONENT_DATA_FACE_ADJACENT_FACE_SIZE, + num_bytes, + cc_uptr->face_adjacent_faces_size_cache.data(), + NULL); + } + + MCUT_ASSERT(cc_uptr->face_adjacent_faces_size_cache.empty() == false); + MCUT_ASSERT(cc_uptr->face_adjacent_faces_size_cache_initialized == true); + + // + // step 2 + // + std::vector partial_sum_vec = cc_uptr->face_adjacent_faces_size_cache; // copy + + parallel_partial_sum(context_ptr->get_shared_compute_threadpool(), partial_sum_vec.begin(), partial_sum_vec.end()); + + // + // step 3 + // + + auto fn_face_adjface_indices_copy = [&cc_uptr, &partial_sum_vec, &casted_ptr](face_array_iterator_t block_start_, face_array_iterator_t block_end_) { + const uint32_t base_face_offset = std::distance(cc_uptr->kernel_hmesh_data->mesh->faces_begin(), block_start_); + + MCUT_ASSERT(base_face_offset < (uint32_t)cc_uptr->face_adjacent_faces_size_cache.size()); + + // the first face in the range between block start and end + const uint32_t base_face_adjface_count = SAFE_ACCESS(cc_uptr->face_adjacent_faces_size_cache, base_face_offset); + + const uint32_t partial_sum_vec_val = SAFE_ACCESS(partial_sum_vec, base_face_offset); + const uint32_t index_arr_base_offset = partial_sum_vec_val - base_face_adjface_count; + uint32_t index_arr_offset = index_arr_base_offset; + + std::vector faces_around_face; // tmp to prevent reallocations + faces_around_face.reserve(3); + + for (face_array_iterator_t f_iter = block_start_; f_iter != block_end_; ++f_iter) { + + faces_around_face.clear(); + cc_uptr->kernel_hmesh_data->mesh->get_faces_around_face(faces_around_face, *f_iter); + const uint32_t num_faces_around_face = (uint32_t)faces_around_face.size(); + + // for each vertex in face + for (uint32_t i = 0; i < num_faces_around_face; ++i) { + const uint32_t face_idx = (uint32_t)SAFE_ACCESS(faces_around_face, i); + *(casted_ptr + index_arr_offset) = face_idx; + ++index_arr_offset; + } + } + }; + + parallel_for( + context_ptr->get_shared_compute_threadpool(), + cc_uptr->kernel_hmesh_data->mesh->faces_begin(), + cc_uptr->kernel_hmesh_data->mesh->faces_end(), + fn_face_adjface_indices_copy); // blocks until all work is done + } +#else // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + McSize elem_offset = 0; + std::vector faces_around_face; + + for (face_array_iterator_t fiter = cc_uptr->kernel_hmesh_data->mesh->faces_begin(); fiter != cc_uptr->kernel_hmesh_data->mesh->faces_end(); ++fiter) { + + faces_around_face.clear(); + cc_uptr->kernel_hmesh_data->mesh->get_faces_around_face(faces_around_face, *fiter, nullptr); + + if (!faces_around_face.empty()) { + for (uint32_t i = 0; i < (uint32_t)faces_around_face.size(); ++i) { + *(casted_ptr + elem_offset) = (uint32_t)faces_around_face[i]; + elem_offset++; + } + } + } + + MCUT_ASSERT((elem_offset * sizeof(uint32_t)) <= bytes); +#endif // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + } + } break; + case MC_CONNECTED_COMPONENT_DATA_FACE_ADJACENT_FACE_SIZE: { + SCOPED_TIMER("MC_CONNECTED_COMPONENT_DATA_FACE_ADJACENT_FACE_SIZE"); + + if (pMem == nullptr) { + *pNumBytes = cc_uptr->kernel_hmesh_data->mesh->number_of_faces() * sizeof(uint32_t); // each face has a size value (num adjacent faces) + } else { + if (bytes > cc_uptr->kernel_hmesh_data->mesh->number_of_faces() * sizeof(uint32_t)) { + throw std::invalid_argument("out of bounds memory access"); + } + + if (bytes % sizeof(uint32_t) != 0) { + throw std::invalid_argument("invalid number of bytes"); + } + + uint32_t* casted_ptr = reinterpret_cast(pMem); + +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + { + const uint32_t face_count = cc_uptr->kernel_hmesh_data->mesh->number_of_faces(); + + if (!cc_uptr->face_adjacent_faces_size_cache_initialized) { // init cache by storing data into it + // the code execution can also reach here because we asked for the cache + // to be populated in order to compute/copy the face indices in parallel. + // see: MC_CONNECTED_COMPONENT_DATA_FACE case + const bool cache_allocated_prior = !cc_uptr->face_adjacent_faces_size_cache.empty(); + + if (!cache_allocated_prior) { + cc_uptr->face_adjacent_faces_size_cache.resize(face_count); + } + + auto fn_face_adj_face_size = [&cc_uptr](std::vector::iterator block_start_, std::vector::iterator block_end_) { + const uint32_t face_base_offset = (uint32_t)std::distance(cc_uptr->face_adjacent_faces_size_cache.begin(), block_start_); + uint32_t face_offset = face_base_offset; + + for (std::vector::iterator fs_iter = block_start_; fs_iter != block_end_; ++fs_iter) { + + const face_descriptor_t descr(face_offset); + + const uint32_t num_faces_around_face = cc_uptr->kernel_hmesh_data->mesh->get_num_faces_around_face(descr); + + *fs_iter = num_faces_around_face; + + face_offset++; + } + }; + + parallel_for( + context_ptr->get_shared_compute_threadpool(), + cc_uptr->face_adjacent_faces_size_cache.begin(), + cc_uptr->face_adjacent_faces_size_cache.end(), + fn_face_adj_face_size); // blocks until all work is done + + cc_uptr->face_adjacent_faces_size_cache_initialized = true; + } + + // the pointers are different if "cc_uptr->face_adjacent_faces_size_cache" is not + // being populated in the current call + const McVoid* src_ptr = reinterpret_cast(&(cc_uptr->face_adjacent_faces_size_cache[0])); + const McVoid* dst_ptr = pMem; + const bool writing_to_client_pointer = (src_ptr != dst_ptr); + + if (writing_to_client_pointer) // copy only if "casted_ptr" is client pointer + { + // copy to user pointer + memcpy(casted_ptr, &(cc_uptr->face_adjacent_faces_size_cache[0]), sizeof(uint32_t) * face_count); + } + } +#else // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + McSize elem_offset = 0; + for (face_array_iterator_t fiter = cc_uptr->kernel_hmesh_data->mesh->faces_begin(); fiter != cc_uptr->kernel_hmesh_data->mesh->faces_end(); ++fiter) { + const uint32_t num_faces_around_face = cc_uptr->kernel_hmesh_data->mesh->get_num_faces_around_face(*fiter, nullptr); + *(casted_ptr + elem_offset) = num_faces_around_face; + elem_offset++; + } + + MCUT_ASSERT((elem_offset * sizeof(uint32_t)) <= bytes); +#endif // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + } + } break; + + case MC_CONNECTED_COMPONENT_DATA_EDGE: { + if (pMem == nullptr) { + *pNumBytes = cc_uptr->kernel_hmesh_data->mesh->number_of_edges() * 2 * sizeof(uint32_t); // each edge has two indices + } else { + if (bytes > cc_uptr->kernel_hmesh_data->mesh->number_of_edges() * 2 * sizeof(uint32_t)) { + throw std::invalid_argument("out of bounds memory access"); + } + + if (bytes % (sizeof(uint32_t) * 2) != 0) { + throw std::invalid_argument("invalid number of bytes"); + } + + uint32_t* casted_ptr = reinterpret_cast(pMem); + +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + { + auto fn_copy_edges = [&casted_ptr, &cc_uptr](edge_array_iterator_t block_start_, edge_array_iterator_t block_end_) { + // thread starting offset (in edge count) in the "array of edges" + const McSize base_offset = std::distance(cc_uptr->kernel_hmesh_data->mesh->edges_begin(), block_start_); + + McSize elem_offset = base_offset * 2; // two (vertex) indices per edge + + for (edge_array_iterator_t edge_iter = block_start_; edge_iter != block_end_; ++edge_iter) { + + const vertex_descriptor_t v0 = cc_uptr->kernel_hmesh_data->mesh->vertex(*edge_iter, 0); + *(casted_ptr + elem_offset) = (uint32_t)v0; + elem_offset++; + + const vertex_descriptor_t v1 = cc_uptr->kernel_hmesh_data->mesh->vertex(*edge_iter, 1); + *(casted_ptr + elem_offset) = (uint32_t)v1; + elem_offset++; + } + }; + + parallel_for( + context_ptr->get_shared_compute_threadpool(), + cc_uptr->kernel_hmesh_data->mesh->edges_begin(), + cc_uptr->kernel_hmesh_data->mesh->edges_end(), + fn_copy_edges); + } +#else // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + McSize elem_offset = 0; + for (edge_array_iterator_t eiter = cc_uptr->kernel_hmesh_data->mesh->edges_begin(); eiter != cc_uptr->kernel_hmesh_data->mesh->edges_end(); ++eiter) { + const vertex_descriptor_t v0 = cc_uptr->kernel_hmesh_data->mesh->vertex(*eiter, 0); + *(casted_ptr + elem_offset) = (uint32_t)v0; + elem_offset++; + + const vertex_descriptor_t v1 = cc_uptr->kernel_hmesh_data->mesh->vertex(*eiter, 1); + *(casted_ptr + elem_offset) = (uint32_t)v1; + elem_offset++; + } + + MCUT_ASSERT((elem_offset * sizeof(uint32_t)) <= bytes); +#endif // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + } + } break; + case MC_CONNECTED_COMPONENT_DATA_TYPE: { + if (pMem == nullptr) { + *pNumBytes = sizeof(McConnectedComponentType); + } else { + if (bytes > sizeof(McConnectedComponentType)) { + throw std::invalid_argument("out of bounds memory access"); + } + if (bytes % sizeof(McConnectedComponentType) != 0) { + throw std::invalid_argument("invalid number of bytes"); + } + memcpy(pMem, reinterpret_cast(&cc_uptr->type), bytes); + } + } break; + case MC_CONNECTED_COMPONENT_DATA_FRAGMENT_LOCATION: { + + if (cc_uptr->type != MC_CONNECTED_COMPONENT_TYPE_FRAGMENT) { + throw std::invalid_argument("invalid client pointer type"); + } + + if (pMem == nullptr) { + *pNumBytes = sizeof(McFragmentLocation); + } else { + + if (bytes > sizeof(McFragmentLocation)) { + throw std::invalid_argument("out of bounds memory access"); + } + + if (bytes % sizeof(McFragmentLocation) != 0) { + throw std::invalid_argument("invalid number of bytes"); + } + + fragment_cc_t* fragPtr = dynamic_cast(cc_uptr.get()); + memcpy(pMem, reinterpret_cast(&fragPtr->fragmentLocation), bytes); + } + } break; + case MC_CONNECTED_COMPONENT_DATA_PATCH_LOCATION: { + + if (cc_uptr->type != MC_CONNECTED_COMPONENT_TYPE_FRAGMENT && cc_uptr->type != MC_CONNECTED_COMPONENT_TYPE_PATCH) { + throw std::invalid_argument("connected component must be a patch or a fragment"); + } + + if (pMem == nullptr) { + *pNumBytes = sizeof(McPatchLocation); + } else { + if (bytes > sizeof(McPatchLocation)) { + throw std::invalid_argument("out of bounds memory access"); + } + + if (bytes % sizeof(McPatchLocation) != 0) { + throw std::invalid_argument("invalid number of bytes"); + } + + const McVoid* src = nullptr; + if (cc_uptr->type == MC_CONNECTED_COMPONENT_TYPE_FRAGMENT) { + src = reinterpret_cast(&dynamic_cast(cc_uptr.get())->patchLocation); + } else { + MCUT_ASSERT(cc_uptr->type == MC_CONNECTED_COMPONENT_TYPE_PATCH); + src = reinterpret_cast(&dynamic_cast(cc_uptr.get())->patchLocation); + } + memcpy(pMem, src, bytes); + } + } break; + case MC_CONNECTED_COMPONENT_DATA_FRAGMENT_SEAL_TYPE: { + + if (cc_uptr->type != MC_CONNECTED_COMPONENT_TYPE_FRAGMENT) { + throw std::invalid_argument("invalid client pointer type"); + } + + if (pMem == nullptr) { + *pNumBytes = sizeof(McFragmentSealType); + } else { + if (bytes > sizeof(McFragmentSealType)) { + throw std::invalid_argument("out of bounds memory access"); + } + + if (bytes % sizeof(McFragmentSealType) != 0) { + throw std::invalid_argument("invalid number of bytes"); + } + fragment_cc_t* fragPtr = dynamic_cast(cc_uptr.get()); + memcpy(pMem, reinterpret_cast(&fragPtr->srcMeshSealType), bytes); + } + } break; + // + case MC_CONNECTED_COMPONENT_DATA_ORIGIN: { + + if (cc_uptr->type != MC_CONNECTED_COMPONENT_TYPE_SEAM && cc_uptr->type != MC_CONNECTED_COMPONENT_TYPE_INPUT) { + throw std::invalid_argument("invalid connected component type"); + } + + size_t nbytes = (cc_uptr->type != MC_CONNECTED_COMPONENT_TYPE_SEAM ? sizeof(McSeamOrigin) : sizeof(McInputOrigin)); + + if (pMem == nullptr) { + *pNumBytes = nbytes; + } else { + if (bytes > nbytes) { + throw std::invalid_argument("out of bounds memory access"); + } + + if ((bytes % nbytes) != 0) { + throw std::invalid_argument("invalid number of bytes"); + } + + if (cc_uptr->type == MC_CONNECTED_COMPONENT_TYPE_SEAM) { + seam_cc_t* ptr = dynamic_cast(cc_uptr.get()); + memcpy(pMem, reinterpret_cast(&ptr->origin), bytes); + } else { + input_cc_t* ptr = dynamic_cast(cc_uptr.get()); + memcpy(pMem, reinterpret_cast(&ptr->origin), bytes); + } + } + } break; + case MC_CONNECTED_COMPONENT_DATA_SEAM_VERTEX: { + if (cc_uptr->type == MC_CONNECTED_COMPONENT_TYPE_INPUT) { + throw std::invalid_argument("cannot query seam vertices on input connected component"); + } + + const uint32_t seam_vertex_count = (uint32_t)cc_uptr->kernel_hmesh_data->seam_vertices.size(); + + if (pMem == nullptr) { + *pNumBytes = seam_vertex_count * sizeof(uint32_t); + } else { + if (bytes > (seam_vertex_count * sizeof(uint32_t))) { + throw std::invalid_argument("out of bounds memory access"); + } + + if ((bytes % (sizeof(uint32_t))) != 0) { + throw std::invalid_argument("invalid number of bytes"); + } + + const uint32_t elems_to_copy = bytes / sizeof(uint32_t); + + uint32_t* casted_ptr = reinterpret_cast(pMem); + +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + { + auto fn_copy_seam_vertices = [&casted_ptr, &cc_uptr, &elems_to_copy](std::vector::const_iterator block_start_, std::vector::const_iterator block_end_) { + // thread starting offset (in edge count) in the "array of edges" + const McSize base_offset = std::distance(cc_uptr->kernel_hmesh_data->seam_vertices.cbegin(), block_start_); + + McSize elem_offset = base_offset; + + for (std::vector::const_iterator sv_iter = block_start_; sv_iter != block_end_; ++sv_iter) { + + *(casted_ptr + elem_offset) = (uint32_t)(*sv_iter); + elem_offset++; + + if (elem_offset == elems_to_copy) { + break; + } + } + }; + + parallel_for( + context_ptr->get_shared_compute_threadpool(), + cc_uptr->kernel_hmesh_data->seam_vertices.cbegin(), + cc_uptr->kernel_hmesh_data->seam_vertices.cend(), + fn_copy_seam_vertices); + } +#else // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + uint32_t elem_offset = 0; + for (uint32_t i = 0; i < elems_to_copy; ++i) { + const uint32_t seam_vertex_idx = cc_uptr->kernel_hmesh_data->seam_vertices[i]; + *(casted_ptr + elem_offset) = seam_vertex_idx; + elem_offset++; + } + + MCUT_ASSERT(elem_offset <= seam_vertex_count); +#endif + } + } break; + case MC_CONNECTED_COMPONENT_DATA_VERTEX_MAP: { + SCOPED_TIMER("MC_CONNECTED_COMPONENT_DATA_VERTEX_MAP"); + + const uint32_t vertex_map_size = cc_uptr->kernel_hmesh_data->data_maps.vertex_map.size(); + + if (vertex_map_size == 0) { + throw std::invalid_argument("vertex map not available"); // user probably forgot to set the dispatch flag + } + + MCUT_ASSERT(vertex_map_size == (uint32_t)cc_uptr->kernel_hmesh_data->mesh->number_of_vertices()); + + if (pMem == nullptr) { + *pNumBytes = (vertex_map_size * sizeof(uint32_t)); // each each vertex has a map value (intersection point == uint_max) + } else { + if (bytes > (vertex_map_size * sizeof(uint32_t))) { + throw std::invalid_argument("out of bounds memory access"); + } + + if (bytes % (sizeof(uint32_t)) != 0) { + throw std::invalid_argument("invalid number of bytes"); + } + + const uint32_t elems_to_copy = (bytes / sizeof(uint32_t)); + + MCUT_ASSERT(elems_to_copy <= vertex_map_size); + + uint32_t* casted_ptr = reinterpret_cast(pMem); + +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + { + auto fn_copy_vertex_map = [&casted_ptr, &cc_uptr, &elems_to_copy](std::vector::const_iterator block_start_, std::vector::const_iterator block_end_) { + // thread starting offset + const uint32_t base_offset = (uint32_t)std::distance(cc_uptr->kernel_hmesh_data->data_maps.vertex_map.cbegin(), block_start_); + + uint32_t elem_offset = base_offset; + + for (std::vector::const_iterator v_iter = block_start_; v_iter != block_end_; ++v_iter) { + + if ((elem_offset) >= elems_to_copy) { + break; + } + + uint32_t i = elem_offset; + + // Refer to single-threaded code (below) for documentation + uint32_t internal_input_mesh_vertex_idx = cc_uptr->kernel_hmesh_data->data_maps.vertex_map[i]; + uint32_t client_input_mesh_vertex_idx = UINT32_MAX; + const bool internal_input_mesh_vertex_is_intersection_point = (internal_input_mesh_vertex_idx == UINT32_MAX); + + if (!internal_input_mesh_vertex_is_intersection_point) { + + bool vertex_exists_due_to_face_partitioning = false; + const bool internal_input_mesh_vertex_is_for_source_mesh = (internal_input_mesh_vertex_idx < cc_uptr->internal_sourcemesh_vertex_count); + + if (internal_input_mesh_vertex_is_for_source_mesh) { + const std::unordered_map::const_iterator fiter = cc_uptr->source_hmesh_new_poly_partition_vertices->find(vd_t(internal_input_mesh_vertex_idx)); + vertex_exists_due_to_face_partitioning = (fiter != cc_uptr->source_hmesh_new_poly_partition_vertices->cend()); + } else { + std::unordered_map::const_iterator fiter = cc_uptr->cut_hmesh_new_poly_partition_vertices->find(vd_t(internal_input_mesh_vertex_idx)); + vertex_exists_due_to_face_partitioning = (fiter != cc_uptr->cut_hmesh_new_poly_partition_vertices->cend()); + } + + if (!vertex_exists_due_to_face_partitioning) { + + MCUT_ASSERT(cc_uptr->internal_sourcemesh_vertex_count > 0); + + if (!internal_input_mesh_vertex_is_for_source_mesh) { + const uint32_t internal_input_mesh_vertex_idx_without_offset = (internal_input_mesh_vertex_idx - cc_uptr->internal_sourcemesh_vertex_count); + client_input_mesh_vertex_idx = (internal_input_mesh_vertex_idx_without_offset + cc_uptr->client_sourcemesh_vertex_count); // ensure that we offset using number of [user-provided mesh] vertices + } else { + client_input_mesh_vertex_idx = internal_input_mesh_vertex_idx; + } + } + } + + *(casted_ptr + elem_offset) = client_input_mesh_vertex_idx; + elem_offset++; + } + }; + + parallel_for( + context_ptr->get_shared_compute_threadpool(), + cc_uptr->kernel_hmesh_data->data_maps.vertex_map.cbegin(), + cc_uptr->kernel_hmesh_data->data_maps.vertex_map.cend(), + fn_copy_vertex_map); + } +#else // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + uint32_t elem_offset = 0; + for (uint32_t i = 0; i < elems_to_copy; ++i) // ... for each vertex in CC + { + // Here we use whatever index value was assigned to the current vertex by the kernel, where the + // the kernel does not necessarilly know that the input meshes it was given where modified by + // the frontend (in this case via polygon partitioning) + // Vertices that are polygon intersection points have a value of uint_max i.e. null_vertex(). + + uint32_t internal_input_mesh_vertex_idx = cc_uptr->kernel_hmesh_data->data_maps.vertex_map[i]; + // We use the same default value as that used by the kernel for intersection + // points (intersection points at mapped to uint_max i.e. null_vertex()) + uint32_t client_input_mesh_vertex_idx = UINT32_MAX; + // This is true only for polygon intersection points computed by the kernel + const bool internal_input_mesh_vertex_is_intersection_point = (internal_input_mesh_vertex_idx == UINT32_MAX); + + if (!internal_input_mesh_vertex_is_intersection_point) { // i.e. a client-mesh vertex or vertex that is added due to face-partitioning + // NOTE: The kernel will assign/map a 'proper' index value to vertices that exist due to face partitioning. + // 'proper' here means that the kernel treats these vertices as 'original vertices' from a client-provided input + // mesh. In reality, the frontend added such vertices in order to partition a face. i.e. the kernel is not aware + // that a given input mesh it is working with is modified by the frontend (it assumes that the meshes is exactly as was + // provided by the client). + // So, here we have to fix that mapping information to correctly state that "any vertex added due to face + // partitioning was not in the user provided input mesh" and should therefore be treated/labelled as an intersection + // point i.e. it should map to UINT32_MAX because it does not map to any vertex in the client-provided input mesh. + bool vertex_exists_due_to_face_partitioning = false; + // this flag tells us whether the current vertex maps to one in the internal version of the source mesh + // i.e. it does not map to the internal version cut-mesh + const bool internal_input_mesh_vertex_is_for_source_mesh = (internal_input_mesh_vertex_idx < cc_uptr->internal_sourcemesh_vertex_count); + + if (internal_input_mesh_vertex_is_for_source_mesh) { + const std::unordered_map::const_iterator fiter = cc_uptr->source_hmesh_new_poly_partition_vertices->find(vd_t(internal_input_mesh_vertex_idx)); + vertex_exists_due_to_face_partitioning = (fiter != cc_uptr->source_hmesh_new_poly_partition_vertices->cend()); + } else // i.e. internal_input_mesh_vertex_is_for_cut_mesh + { + std::unordered_map::const_iterator fiter = cc_uptr->cut_hmesh_new_poly_partition_vertices->find(vd_t(internal_input_mesh_vertex_idx)); + vertex_exists_due_to_face_partitioning = (fiter != cc_uptr->cut_hmesh_new_poly_partition_vertices->cend()); + } + + if (!vertex_exists_due_to_face_partitioning) { // i.e. is a client-mesh vertex (an original vertex) + + MCUT_ASSERT(cc_uptr->internal_sourcemesh_vertex_count > 0); + + if (!internal_input_mesh_vertex_is_for_source_mesh) // is it a cut-mesh vertex discriptor ..? + { + // vertices added due to face-partitioning will have an offsetted index/descriptor that is >= client_sourcemesh_vertex_count + const uint32_t internal_input_mesh_vertex_idx_without_offset = (internal_input_mesh_vertex_idx - cc_uptr->internal_sourcemesh_vertex_count); + client_input_mesh_vertex_idx = (internal_input_mesh_vertex_idx_without_offset + cc_uptr->client_sourcemesh_vertex_count); // ensure that we offset using number of [user-provided mesh] vertices + } else { + client_input_mesh_vertex_idx = internal_input_mesh_vertex_idx; // src-mesh vertices have no offset unlike cut-mesh vertices + } + } + } + + *(casted_ptr + elem_offset) = client_input_mesh_vertex_idx; + elem_offset++; + } + + MCUT_ASSERT(elem_offset <= vertex_map_size); +#endif // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + } + } break; + case MC_CONNECTED_COMPONENT_DATA_FACE_MAP: { + SCOPED_TIMER("MC_CONNECTED_COMPONENT_DATA_FACE_MAP"); + + const uint32_t face_map_size = cc_uptr->kernel_hmesh_data->data_maps.face_map.size(); + + if (face_map_size == 0) { + throw std::invalid_argument("face map not available"); // user probably forgot to set the dispatch flag + } + + MCUT_ASSERT(face_map_size == (uint32_t)cc_uptr->kernel_hmesh_data->mesh->number_of_faces()); + + if (pMem == nullptr) { + *pNumBytes = face_map_size * sizeof(uint32_t); // each face has a map value (intersection point == uint_max) + } else { + if (bytes > (face_map_size * sizeof(uint32_t))) { + throw std::invalid_argument("out of bounds memory access"); + } + + if ((bytes % sizeof(uint32_t)) != 0) { + throw std::invalid_argument("invalid number of bytes"); + } + + const uint32_t elems_to_copy = (bytes / sizeof(uint32_t)); + + uint32_t* casted_ptr = reinterpret_cast(pMem); + +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + { + auto fn_copy_face_map = [&casted_ptr, &cc_uptr, &elems_to_copy](std::vector::const_iterator block_start_, std::vector::const_iterator block_end_) { + // thread starting offset (in edge count) in the "array of edges" + // thread starting offset + const uint32_t base_offset = (uint32_t)std::distance(cc_uptr->kernel_hmesh_data->data_maps.face_map.cbegin(), block_start_); + + uint32_t elem_offset = base_offset; + + for (std::vector::const_iterator f_iter = block_start_; f_iter != block_end_; ++f_iter) { + + if ((elem_offset + 1) >= elems_to_copy) { + break; + } + + uint32_t i = elem_offset; + + // Refer to single-threaded code (below) for documentation + const uint32_t internal_inputmesh_face_idx = (uint32_t)cc_uptr->kernel_hmesh_data->data_maps.face_map[i]; + // const uint32_t internal_inputmesh_face_idx = (uint32_t)cc_uptr->kernel_hmesh_data->data_maps.face_map[(uint32_t)*cc_face_iter]; + const uint32_t user_inputmesh_face_idx = map_internal_inputmesh_face_idx_to_user_inputmesh_face_idx( + internal_inputmesh_face_idx, + cc_uptr); + + *(casted_ptr + elem_offset) = user_inputmesh_face_idx; + elem_offset++; + } + }; + + parallel_for( + context_ptr->get_shared_compute_threadpool(), + cc_uptr->kernel_hmesh_data->data_maps.face_map.cbegin(), + cc_uptr->kernel_hmesh_data->data_maps.face_map.cend(), + fn_copy_face_map); + } +#else // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + uint32_t elem_offset = 0; + for (uint32_t i = 0; i < elems_to_copy; ++i) // ... for each FACE (to copy) in CC + { + const uint32_t internal_inputmesh_face_idx = (uint32_t)cc_uptr->kernel_hmesh_data->data_maps.face_map[i]; + const uint32_t user_inputmesh_face_idx = map_internal_inputmesh_face_idx_to_user_inputmesh_face_idx( + internal_inputmesh_face_idx, + cc_uptr); + + *(casted_ptr + elem_offset) = user_inputmesh_face_idx; + elem_offset++; + } + + MCUT_ASSERT(elem_offset <= face_map_size); +#endif // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + } + } break; + case MC_CONNECTED_COMPONENT_DATA_FACE_TRIANGULATION: { + SCOPED_TIMER("MC_CONNECTED_COMPONENT_DATA_FACE_TRIANGULATION"); + // internal halfedge data structure from the current connected component + const std::shared_ptr cc = cc_uptr->kernel_hmesh_data->mesh; + + const uint32_t nontri_face_map_size = cc_uptr->kernel_hmesh_data->data_maps.face_map.size(); + + // user has set the dispatch flag to allow us to save the face maps. + const bool user_requested_cdt_face_maps = (nontri_face_map_size != 0); + + if (cc_uptr->cdt_index_cache_initialized == false) // compute triangulation if not yet available + { + MCUT_ASSERT(cc_uptr->cdt_index_cache.empty()); + + const uint32_t cc_face_count = cc->number_of_faces(); + if (user_requested_cdt_face_maps) { + cc_uptr->cdt_face_map_cache.reserve(cc_face_count * 1.2); + } + +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + { + + // the offset from which each thread will write its CDT indices + // into "cc_uptr->cdt_index_cache" + std::atomic cdt_index_cache_offset; + cdt_index_cache_offset.store(0); + + // The following scheduling parameters are needed because we need + // to know how many threads will be schedule to perform triangulation. + // (for the barrier primitive). + // The _exact_ same information must be computed inside "parallel_for", + // which is why "min_per_thread" needs to be explicitly passed to + // "parallel_for" after setting it here. + + // number of threads to perform task (some/all of the pool threads plus master thread) + uint32_t num_threads = 0; + const uint32_t min_per_thread = 1 << 10; + { + uint32_t max_threads = 0; + const uint32_t available_threads = context_ptr->get_shared_compute_threadpool().get_num_threads() + 1; // workers and master (+1) + uint32_t block_size_unused = 0; + const uint32_t length = cc_face_count; + + get_scheduling_parameters( + num_threads, + max_threads, + block_size_unused, + length, + available_threads, + min_per_thread); + } + + const std::thread::id master_thread_id = std::this_thread::get_id(); + + barrier_t barrier(num_threads); + + auto fn_triangulate_faces = [&](face_array_iterator_t block_start_, face_array_iterator_t block_end_) { + // CDT indices computed per thread + std::vector cdt_index_cache_local; + const uint32_t num_elems_to_process = (uint32_t)std::distance(block_start_, block_end_); + cdt_index_cache_local.reserve(num_elems_to_process * 4); + std::vector cdt_face_map_cache_local; + cdt_face_map_cache_local.reserve(num_elems_to_process * 1.2); + + std::vector cc_face_vertices; + std::vector cc_face_triangulation; + + for (face_array_iterator_t cc_face_iter = block_start_; cc_face_iter != block_end_; ++cc_face_iter) { + + cc->get_vertices_around_face(cc_face_vertices, *cc_face_iter); + + const uint32_t cc_face_vcount = (uint32_t)cc_face_vertices.size(); + + MCUT_ASSERT(cc_face_vcount >= 3); + + const bool cc_face_is_triangle = (cc_face_vcount == 3); + + if (cc_face_is_triangle) { + + for (uint32_t i = 0; i < cc_face_vcount; ++i) { + const uint32_t vertex_id_in_cc = (uint32_t)SAFE_ACCESS(cc_face_vertices, i); + cdt_index_cache_local.push_back(vertex_id_in_cc); + } + + if (user_requested_cdt_face_maps) { + const uint32_t internal_inputmesh_face_idx = (uint32_t)cc_uptr->kernel_hmesh_data->data_maps.face_map[(uint32_t)*cc_face_iter]; + const uint32_t user_inputmesh_face_idx = map_internal_inputmesh_face_idx_to_user_inputmesh_face_idx( + internal_inputmesh_face_idx, + cc_uptr); + cdt_face_map_cache_local.push_back(user_inputmesh_face_idx); + } + + } else { + + cc_face_triangulation.clear(); + + triangulate_face(cc_face_triangulation, context_ptr, cc_face_vcount, cc_face_vertices, *(cc.get()), *cc_face_iter); + + for (uint32_t i = 0; i < (uint32_t)cc_face_triangulation.size(); ++i) { + const uint32_t local_idx = cc_face_triangulation[i]; // id local within the current face that we are triangulating + const uint32_t global_idx = (uint32_t)cc_face_vertices[local_idx]; + + cdt_index_cache_local.push_back(global_idx); + + if (user_requested_cdt_face_maps) { + if ((i % 3) == 0) { // every three indices constitute one triangle + // map every CDT triangle in "*cc_face_iter" to the index value of "*cc_face_iter" (in the user input mesh) + const uint32_t internal_inputmesh_face_idx = (uint32_t)cc_uptr->kernel_hmesh_data->data_maps.face_map[(uint32_t)*cc_face_iter]; + const uint32_t user_inputmesh_face_idx = map_internal_inputmesh_face_idx_to_user_inputmesh_face_idx( + internal_inputmesh_face_idx, + cc_uptr); + cdt_face_map_cache_local.push_back(user_inputmesh_face_idx); + } + } + } + } + } + + // local num triangulation indices computed by thread + const uint32_t cdt_index_cache_local_len = cdt_index_cache_local.size(); + + const uint32_t write_offset = cdt_index_cache_offset.fetch_add(cdt_index_cache_local_len); + + barrier.wait(); // .. for all threads to triangulate their range of faces + + if (std::this_thread::get_id() == master_thread_id) { + // + // allocate memory for threads to write in parallel + // + const uint32_t num_triangulation_indices_global = cdt_index_cache_offset.load(); + MCUT_ASSERT((num_triangulation_indices_global % 3) == 0); + const uint32_t num_triangles_global = num_triangulation_indices_global / 3; + MCUT_ASSERT(num_triangles_global >= 1); + cc_uptr->cdt_index_cache.resize(num_triangulation_indices_global); + + if (user_requested_cdt_face_maps) { + cc_uptr->cdt_face_map_cache.resize(num_triangles_global); + } + } + + barrier.wait(); // .. for memory to be allocated + + // for each local triangulation index (NOTE: num triangles local = "cdt_index_cache_local_len/3") + for (uint32_t i = 0; i < cdt_index_cache_local_len; ++i) { + + // local triangulation cache index + const uint32_t local_cache_idx = i; + // global triangulation cache index + const uint32_t global_cache_idx = write_offset + local_cache_idx; + + SAFE_ACCESS(cc_uptr->cdt_index_cache, global_cache_idx) = SAFE_ACCESS(cdt_index_cache_local, local_cache_idx); + + if (user_requested_cdt_face_maps && ((local_cache_idx % 3) == 0)) // every three cdt indices constitute a triangle + { + const uint32_t triangle_index_global = global_cache_idx / 3; + const uint32_t triangle_index_local = local_cache_idx / 3; + + SAFE_ACCESS(cc_uptr->cdt_face_map_cache, triangle_index_global) = SAFE_ACCESS(cdt_face_map_cache_local, triangle_index_local); + } + } + }; + + parallel_for( + context_ptr->get_shared_compute_threadpool(), + cc_uptr->kernel_hmesh_data->mesh->faces_begin(), + cc_uptr->kernel_hmesh_data->mesh->faces_end(), + fn_triangulate_faces, + min_per_thread); + } +#else // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + uint32_t face_indices_offset = 0; + cc_uptr->cdt_index_cache.reserve(cc_face_count * 1.2); + + // descriptors of vertices in face (they index into the CC) + std::vector cc_face_vertices; + + for (face_array_iterator_t cc_face_iter = cc->faces_begin(); cc_face_iter != cc->faces_end(); ++cc_face_iter) { + + cc->get_vertices_around_face(cc_face_vertices, *cc_face_iter); + + // number of vertices of triangulated face + const uint32_t cc_face_vcount = (uint32_t)cc_face_vertices.size(); + + MCUT_ASSERT(cc_face_vcount >= 3); + + const bool cc_face_is_triangle = (cc_face_vcount == 3); + + if (cc_face_is_triangle) { + + // for each vertex in face + for (uint32_t i = 0; i < cc_face_vcount; ++i) { + const uint32_t vertex_id_in_cc = (uint32_t)SAFE_ACCESS(cc_face_vertices, i); + cc_uptr->cdt_index_cache.push_back(vertex_id_in_cc); + } + + if (user_requested_cdt_face_maps) { + const uint32_t internal_inputmesh_face_idx = (uint32_t)cc_uptr->kernel_hmesh_data->data_maps.face_map[(uint32_t)*cc_face_iter]; + const uint32_t user_inputmesh_face_idx = map_internal_inputmesh_face_idx_to_user_inputmesh_face_idx( + internal_inputmesh_face_idx, + cc_uptr); + cc_uptr->cdt_face_map_cache.push_back(user_inputmesh_face_idx); + } + + } else { + + // + // need to triangulate face + // + + // List of indices which define all triangles that result from the CDT + // These indices are _local_ to the vertex list of the face being + // triangulated! + std::vector cc_face_triangulation; + + triangulate_face(cc_face_triangulation, context_ptr, cc_face_vcount, cc_face_vertices, cc.get()[0], *cc_face_iter); + + // + // Change local triangle indices to global index values (in CC) and save + // + + const uint32_t cc_face_triangulation_index_count = (uint32_t)cc_face_triangulation.size(); + cc_uptr->cdt_index_cache.reserve( + cc_uptr->cdt_index_cache.size() + cc_face_triangulation_index_count); + + for (uint32_t i = 0; i < cc_face_triangulation_index_count; ++i) { + const uint32_t local_idx = cc_face_triangulation[i]; // id local within the current face that we are triangulating + const uint32_t global_idx = (uint32_t)cc_face_vertices[local_idx]; + + cc_uptr->cdt_index_cache.push_back(global_idx); + + if (user_requested_cdt_face_maps) { + if ((i % 3) == 0) { // every three indices constitute one triangle + // map every CDT triangle in "*cc_face_iter" to the index value of "*cc_face_iter" (in the user input mesh) + const uint32_t internal_inputmesh_face_idx = (uint32_t)cc_uptr->kernel_hmesh_data->data_maps.face_map[(uint32_t)*cc_face_iter]; + const uint32_t user_inputmesh_face_idx = map_internal_inputmesh_face_idx_to_user_inputmesh_face_idx( + internal_inputmesh_face_idx, + cc_uptr); + cc_uptr->cdt_face_map_cache.push_back(user_inputmesh_face_idx); + } + } + } + } // if (cc_face_vcount == 3) + + face_indices_offset += cc_face_vcount; + } + + MCUT_ASSERT(cc_uptr->cdt_index_cache.size() >= 3); +#endif // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + cc_uptr->cdt_index_cache_initialized = true; + + if (user_requested_cdt_face_maps) { + MCUT_ASSERT(cc_uptr->cdt_face_map_cache.empty() == false); + cc_uptr->cdt_face_map_cache_initialized = true; + } + + } // if(cc_uptr->indexArrayMesh.numTriangleIndices == 0) + + MCUT_ASSERT(cc_uptr->cdt_index_cache_initialized == true); + + if (user_requested_cdt_face_maps) { + MCUT_ASSERT(!cc_uptr->cdt_face_map_cache.empty()); + } + + // i.e. pMem is a pointer allocated by the user and not one that we allocated + // here inside the API e.g. fool us into just computing the CDT triangulation + // indices and face map caches. + // See also the case for MC_CONNECTED_COMPONENT_DATA_FACE_TRIANGULATION_MAP below: + const bool proceed_and_copy_to_output_ptr = user_requested_cdt_face_maps == false || pMem != cc_uptr->cdt_face_map_cache.data(); + + if (proceed_and_copy_to_output_ptr) { + const uint32_t num_triangulation_indices = (uint32_t)cc_uptr->cdt_index_cache.size(); + + if (pMem == nullptr) // client pointer is null (asking for size) + { + MCUT_ASSERT(num_triangulation_indices >= 3); + *pNumBytes = num_triangulation_indices * sizeof(uint32_t); // each each vertex has a map value (intersection point == uint_max) + } else { + MCUT_ASSERT(num_triangulation_indices >= 3); + + if (bytes > num_triangulation_indices * sizeof(uint32_t)) { + throw std::invalid_argument("out of bounds memory access"); + } + + if (bytes % (sizeof(uint32_t)) != 0 || (bytes / sizeof(uint32_t)) % 3 != 0) { + throw std::invalid_argument("invalid number of bytes"); + } + + memcpy(pMem, reinterpret_cast(cc_uptr->cdt_index_cache.data()), bytes); + } + } + } break; + case MC_CONNECTED_COMPONENT_DATA_FACE_TRIANGULATION_MAP: { + SCOPED_TIMER("MC_CONNECTED_COMPONENT_DATA_FACE_TRIANGULATION_MAP"); + // The default/standard non-tri face map. If this is defined then the tri-face map must also be defined + const uint32_t face_map_size = cc_uptr->kernel_hmesh_data->data_maps.face_map.size(); + + if (face_map_size == 0) { + throw std::invalid_argument("face map not available"); // user probably forgot to set the dispatch flag + } else { + // Face maps are available (because they were requested by user) and + // so it follows that the triangulated-face maps should be available too. + MCUT_ASSERT(cc_uptr->cdt_face_map_cache_initialized == true); + } + + MCUT_ASSERT(face_map_size == (uint32_t)cc_uptr->kernel_hmesh_data->mesh->number_of_faces()); + + // Did the user request the triangulated-face map BEFORE the triangulated face indices? + // That is call mcGetConnectedComponentData with MC_CONNECTED_COMPONENT_DATA_FACE_TRIANGULATION_MAP before calling with MC_CONNECTED_COMPONENT_DATA_FACE_TRIANGULATION + // If so, we need to compute the triangulated face indices anyway (and cache then) since + // that API call also compute the triangulated face maps (cache) + if (cc_uptr->cdt_face_map_cache_initialized == false) { + + // recursive Internal API call to compute CDT and populate caches and also set "cc_uptr->cdt_face_map_cache_initialized" to true + get_connected_component_data_impl_detail( + context_ptr, + connCompId, + MC_CONNECTED_COMPONENT_DATA_FACE_TRIANGULATION, + /*The next two parameters are actually unused (in the sense of writing data to them). + They must be provided however, in order to fool the (internal) API call into deducing that we + want to query triangulation data but what we really want to compute the CDT (cache) and + populated the triangulated face maps (cache) */ + sizeof(uint32_t), // ** + cc_uptr->cdt_face_map_cache.data(), // value of pointer will also be used to infer that no memcpy is actually performed + NULL); + + MCUT_ASSERT(cc_uptr->cdt_face_map_cache_initialized == true); + } + + const uint32_t triangulated_face_map_size = cc_uptr->cdt_face_map_cache.size(); + + MCUT_ASSERT(triangulated_face_map_size >= face_map_size); + + if (pMem == nullptr) { + *pNumBytes = triangulated_face_map_size * sizeof(uint32_t); // each face has a map value (intersection point == uint_max) + } else { + if (bytes > (triangulated_face_map_size * sizeof(uint32_t))) { + throw std::invalid_argument("out of bounds memory access"); + } + + if ((bytes % sizeof(uint32_t)) != 0) { + throw std::invalid_argument("invalid number of bytes"); + } + + uint32_t* casted_ptr = reinterpret_cast(pMem); + + memcpy(casted_ptr, &cc_uptr->cdt_face_map_cache[0], bytes); + } + } break; + default: + throw std::invalid_argument("invalid enum flag"); + } +} + +void get_connected_component_data_impl( + const McContext contextHandle, + const McConnectedComponent connCompId, + McFlags flags, + McSize bytes, + McVoid* pMem, + McSize* pNumBytes, + uint32_t numEventsInWaitlist, + const McEvent* pEventWaitList, + McEvent* pEvent) +{ + std::shared_ptr context_ptr = g_contexts.find_first_if([=](const std::shared_ptr cptr) { return cptr->m_user_handle == contextHandle; }); + + if (context_ptr == nullptr) { + throw std::invalid_argument("invalid context"); + } + + std::weak_ptr context_weak_ptr(context_ptr); + + const McEvent event_handle = context_ptr->prepare_and_submit_API_task( + MC_COMMAND_GET_CONNECTED_COMPONENT_DATA, numEventsInWaitlist, pEventWaitList, + [=]() { + if (!context_weak_ptr.expired()) { + std::shared_ptr context = context_weak_ptr.lock(); + if (context) { + // asynchronously get the data and write to user provided pointer + get_connected_component_data_impl_detail( + context, + connCompId, + flags, + bytes, + pMem, + pNumBytes); + } + } + }); + + *pEvent = event_handle; +} + +void release_event_impl( + McEvent eventHandle) +{ + std::shared_ptr event_ptr = g_events.find_first_if([=](const std::shared_ptr eptr) { return eptr->m_user_handle == eventHandle; }); + + if (event_ptr == nullptr) { + throw std::invalid_argument("invalid event handle"); + } + + { + g_events.remove_if([=](const std::shared_ptr eptr) { return eptr->m_user_handle == eventHandle; }); + } +} + +void release_events_impl(uint32_t numEvents, const McEvent* pEvents) +{ + for (uint32_t i = 0; i < numEvents; ++i) { + McEvent eventHandle = pEvents[i]; + release_event_impl(eventHandle); + } +} + +void release_connected_components_impl( + const McContext contextHandle, + uint32_t numConnComps, + const McConnectedComponent* pConnComps) +{ + std::shared_ptr context_ptr = g_contexts.find_first_if([=](const std::shared_ptr cptr) { return cptr->m_user_handle == contextHandle; }); + + if (context_ptr == nullptr) { + throw std::invalid_argument("invalid context"); + } + + bool freeAll = numConnComps == 0 && pConnComps == NULL; + + if (freeAll) { + context_ptr->connected_components.remove_if([](const std::shared_ptr) { return true; }); + } else { + for (int i = 0; i < (int)numConnComps; ++i) { + McConnectedComponent connCompId = pConnComps[i]; + + // report error if cc is not valid + std::shared_ptr cc_ptr = context_ptr->connected_components.find_first_if([=](const std::shared_ptr ccptr) { return ccptr->m_user_handle == connCompId; }); + + if (cc_ptr == nullptr) { + throw std::invalid_argument("invalid connected component handle"); + } + + context_ptr->connected_components.remove_if([=](const std::shared_ptr ccptr) { return ccptr->m_user_handle == connCompId; }); + } + } +} + +void release_context_impl( + McContext contextHandle) +{ + std::shared_ptr context_ptr = g_contexts.find_first_if([=](const std::shared_ptr cptr) { return cptr->m_user_handle == contextHandle; }); + + if (context_ptr == nullptr) { + throw std::invalid_argument("invalid context handle"); + } + + g_contexts.remove_if([=](const std::shared_ptr cptr) { return cptr->m_user_handle == contextHandle; }); +} \ No newline at end of file diff --git a/src/mcut/source/hmesh.cpp b/src/mcut/source/hmesh.cpp new file mode 100644 index 0000000000..cfc3d219dc --- /dev/null +++ b/src/mcut/source/hmesh.cpp @@ -0,0 +1,1411 @@ +/** + * 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: + * (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 "mcut/internal/hmesh.h" + +#include +#include + +#define ENABLE_EDGE_DESCRIPTOR_TRICK 1 + +// +// array_iterator_t +// +template <> +vertex_array_iterator_t vertex_array_iterator_t::cbegin(bool account_for_removed_elems, id_) +{ + return mesh_ptr->vertices_begin(account_for_removed_elems); +} + +template <> +vertex_array_iterator_t vertex_array_iterator_t::cend(id_) +{ + return mesh_ptr->vertices_end(); +} + +template <> +edge_array_iterator_t edge_array_iterator_t::cbegin(bool account_for_removed_elems, id_) +{ + return mesh_ptr->edges_begin(account_for_removed_elems); +} + +template <> +edge_array_iterator_t edge_array_iterator_t::cend(id_) +{ + return mesh_ptr->edges_end(); +} + +template <> +halfedge_array_iterator_t halfedge_array_iterator_t::cbegin(bool account_for_removed_elems, id_) +{ + return mesh_ptr->halfedges_begin(account_for_removed_elems); +} + +template <> +halfedge_array_iterator_t halfedge_array_iterator_t::cend(id_) +{ + return mesh_ptr->halfedges_end(); +} + +template <> +face_array_iterator_t face_array_iterator_t::cbegin(bool account_for_removed_elems, id_) +{ + return mesh_ptr->faces_begin(account_for_removed_elems); +} +template <> +face_array_iterator_t face_array_iterator_t::cend(id_) +{ + return mesh_ptr->faces_end(); +} + +// +// hmesh_t +// + +hmesh_t::hmesh_t() +{ +} +hmesh_t::~hmesh_t() { } + +// static member functions +// ----------------------- + +vertex_descriptor_t hmesh_t::null_vertex() +{ + return vertex_descriptor_t(); +} + +halfedge_descriptor_t hmesh_t::null_halfedge() +{ + return halfedge_descriptor_t(); +} + +edge_descriptor_t hmesh_t::null_edge() +{ + return edge_descriptor_t(); +} + +face_descriptor_t hmesh_t::null_face() +{ + return face_descriptor_t(); +} + +// regular member functions +// ------------------------ + +int hmesh_t::number_of_vertices() const +{ + return number_of_internal_vertices() - number_of_vertices_removed(); +} + +int hmesh_t::number_of_edges() const +{ + return number_of_internal_edges() - number_of_edges_removed(); +} + +int hmesh_t::number_of_halfedges() const +{ + return number_of_internal_halfedges() - number_of_halfedges_removed(); +} + +int hmesh_t::number_of_faces() const +{ + return number_of_internal_faces() - number_of_faces_removed(); +} + +vertex_descriptor_t hmesh_t::source(const halfedge_descriptor_t& h) const +{ + MCUT_ASSERT((size_t)h < m_halfedges.size() /*h != null_halfedge()*/); + const halfedge_data_t& hd = m_halfedges[h]; + MCUT_ASSERT((size_t)hd.o < m_halfedges.size() /*hd.o != null_halfedge()*/); + const halfedge_data_t& ohd = m_halfedges[hd.o]; // opposite + return ohd.t; +} + +vertex_descriptor_t hmesh_t::target(const halfedge_descriptor_t& h) const +{ + MCUT_ASSERT(h != null_halfedge()); + MCUT_ASSERT((size_t)h < m_halfedges.size() /*m_halfedges.count(h) == 1*/); + const halfedge_data_t& hd = m_halfedges[h]; + return hd.t; +} + +halfedge_descriptor_t hmesh_t::opposite(const halfedge_descriptor_t& h) const +{ + MCUT_ASSERT(h != null_halfedge()); + MCUT_ASSERT((size_t)h < m_halfedges.size() /*m_halfedges.count(h) == 1*/); +#if ENABLE_EDGE_DESCRIPTOR_TRICK + return halfedge_descriptor_t((h % 2 == 0) ? h + 1 : h - 1); +#else + const halfedge_data_t& hd = m_halfedges[h]; + return hd.o; +#endif +} + +halfedge_descriptor_t hmesh_t::prev(const halfedge_descriptor_t& h) const +{ + MCUT_ASSERT(h != null_halfedge()); + MCUT_ASSERT((size_t)h < m_halfedges.size() /*m_halfedges.count(h) == 1*/); + const halfedge_data_t& hd = m_halfedges[h]; + return hd.p; +} + +halfedge_descriptor_t hmesh_t::next(const halfedge_descriptor_t& h) const +{ + MCUT_ASSERT(h != null_halfedge()); + MCUT_ASSERT((size_t)h < m_halfedges.size() /*m_halfedges.count(h) == 1*/); + const halfedge_data_t& hd = m_halfedges[h]; + return hd.n; +} + +void hmesh_t::set_next(const halfedge_descriptor_t& h, const halfedge_descriptor_t& nxt) +{ + MCUT_ASSERT(h != null_halfedge()); + MCUT_ASSERT(nxt != null_halfedge()); + MCUT_ASSERT((size_t)h < m_halfedges.size() /*m_halfedges.count(h) == 1*/); + halfedge_data_t& hd = m_halfedges[h]; + hd.n = nxt; + set_previous(nxt, h); +} + +void hmesh_t::set_previous(const halfedge_descriptor_t& h, const halfedge_descriptor_t& prev) +{ + MCUT_ASSERT(h != null_halfedge()); + MCUT_ASSERT(prev != null_halfedge()); + MCUT_ASSERT((size_t)h < m_halfedges.size() /*m_halfedges.count(h) == 1*/); + halfedge_data_t& hd = m_halfedges[h]; + hd.p = prev; +} + +edge_descriptor_t hmesh_t::edge(const halfedge_descriptor_t& h) const +{ + MCUT_ASSERT(h != null_halfedge()); + MCUT_ASSERT((size_t)h < m_halfedges.size() /*m_halfedges.count(h) == 1*/); +#if ENABLE_EDGE_DESCRIPTOR_TRICK + return edge_descriptor_t(h / 2); +#else + const halfedge_data_t& hd = m_halfedges[h]; + return hd.e; +#endif +} + +face_descriptor_t hmesh_t::face(const halfedge_descriptor_t& h) const +{ + MCUT_ASSERT(h != null_halfedge()); + MCUT_ASSERT((size_t)h < m_halfedges.size() /*m_halfedges.count(h) == 1*/); + const halfedge_data_t& hd = m_halfedges[h]; + return hd.f; +} + +vertex_descriptor_t hmesh_t::vertex(const edge_descriptor_t e, const int v) const +{ + MCUT_ASSERT(e != null_edge()); + MCUT_ASSERT(v == 0 || v == 1); + MCUT_ASSERT((size_t)e < m_edges.size() /*m_edges.count(e) == 1*/); +#if ENABLE_EDGE_DESCRIPTOR_TRICK + return target(halfedge_descriptor_t((e * 2) + v)); +#else + const edge_data_t& ed = m_edges[e]; + const halfedge_descriptor_t h = ed.h; + MCUT_ASSERT((size_t)h < m_halfedges.size() /*m_halfedges.count(h) == 1*/); + const halfedge_data_t& hd = m_halfedges[h]; + vertex_descriptor_t v_out = hd.t; // assuming v ==0 + + if (v == 1) { + const halfedge_descriptor_t opp = hd.o; + MCUT_ASSERT((size_t)opp < m_halfedges.size() /*m_halfedges.count(opp) == 1*/); + const halfedge_data_t& ohd = m_halfedges[opp]; + v_out = ohd.t; + } + + return v_out; +#endif +} + +bool hmesh_t::is_border(const halfedge_descriptor_t h) +{ + MCUT_ASSERT(h != null_halfedge()); + return face(h) == null_face(); +} + +bool hmesh_t::is_border(const edge_descriptor_t e) +{ + MCUT_ASSERT(e != null_edge()); + halfedge_descriptor_t h0 = halfedge(e, 0); + MCUT_ASSERT(h0 != null_halfedge()); + halfedge_descriptor_t h1 = halfedge(e, 1); + MCUT_ASSERT(h1 != null_halfedge()); + + return is_border(h0) || is_border(h1); +} + +halfedge_descriptor_t hmesh_t::halfedge(const edge_descriptor_t e, const int i) const +{ + MCUT_ASSERT(i == 0 || i == 1); + MCUT_ASSERT(e != null_edge()); + MCUT_ASSERT((size_t)e < m_edges.size() /*m_edges.count(e) == 1*/); +#if ENABLE_EDGE_DESCRIPTOR_TRICK + return halfedge_descriptor_t(e * 2 + i); +#else + const edge_data_t& ed = m_edges[e]; + halfedge_descriptor_t h = ed.h; // assuming i ==0 + + MCUT_ASSERT(h != null_halfedge()); + + if (i == 1) { + MCUT_ASSERT((size_t)h < m_halfedges.size() /*m_halfedges.count(h) == 1*/); + + const halfedge_data_t& hd = m_halfedges[h]; + h = hd.o; + + MCUT_ASSERT(h != null_halfedge()); + } + + return h; +#endif +} + +// TODO: maybe a caching mechanism might help with this lookup e,g. "std::unordered_map, halfedge_descriptor_t>" +// But this cache need to be invalidated if we delete and edge +halfedge_descriptor_t hmesh_t::halfedge(const vertex_descriptor_t s, const vertex_descriptor_t t, bool strict_check) const +{ + MCUT_ASSERT((size_t)s < m_vertices.size()); // MCUT_ASSERT(m_vertices.count(s) == 1); + const vertex_data_t& svd = m_vertices[s]; + const std::vector& s_halfedges = svd.m_halfedges; + MCUT_ASSERT((size_t)t < m_vertices.size()); // MCUT_ASSERT(m_vertices.count(t) == 1); + const vertex_data_t& tvd = m_vertices[t]; + const std::vector& t_halfedges = tvd.m_halfedges; + + std::vector t_edges(t_halfedges.size()); + //t_edges.resize(0); + //t_edges.resize(t_halfedges.size()); + uint32_t counter = 0; + for (std::vector::const_iterator i = t_halfedges.cbegin(); i != t_halfedges.cend(); i++) { + edge_descriptor_t e = edge(*i); + MCUT_ASSERT(e != null_edge()); + t_edges[counter++] = (e); + } + + halfedge_descriptor_t result = null_halfedge(); + for (std::vector::const_iterator i = s_halfedges.cbegin(); i != s_halfedges.cend(); ++i) { + edge_descriptor_t s_edge = edge(*i); + if (std::find(t_edges.cbegin(), t_edges.cend(), s_edge) != t_edges.cend()) // belong to same edge? + { + + + // check if we need to return the opposite halfedge + if ((source(*i) == s && target(*i) == t) == false) { + MCUT_ASSERT(source(*i) == t); + MCUT_ASSERT(target(*i) == s); + + halfedge_descriptor_t h = opposite(*i); + + if (strict_check || face(h) != null_face()) { // "strict_check" ensures that we return the halfedge matching the input vertices + result = h; + } + } + else + { + MCUT_ASSERT(source(*i) == s); // confirm our assumption + MCUT_ASSERT(target(*i) == t); + + if (strict_check || face(*i) != null_face()) { // "strict_check" ensures that we return the halfedge matching the input vertices + result = *i; // assume source(*i) and target(*i) match "s" and "t" + } + + } + break; + } + } + return result; +} + +edge_descriptor_t hmesh_t::edge(const vertex_descriptor_t s, const vertex_descriptor_t t, bool strict_check) const +{ + halfedge_descriptor_t h = halfedge(s, t, strict_check); + return (h == hmesh_t::null_halfedge() ? hmesh_t::null_edge() : edge(h)); +} + +vertex_descriptor_t hmesh_t::add_vertex(const vec3& point) +{ + const double& x = point.x(); + const double& y = point.y(); + const double& z = point.z(); + return add_vertex(x, y, z); +} + +vertex_descriptor_t hmesh_t::add_vertex(const double& x, const double& y, const double& z) +{ + vertex_descriptor_t vd = hmesh_t::null_vertex(); + vertex_data_t* data_ptr = nullptr; + bool reusing_removed_descr = (!m_vertices_removed.empty()); + + if (reusing_removed_descr) // can we re-use a slot? + { + std::vector::iterator it = m_vertices_removed.begin(); // take the oldest unused slot (NOTE: important for user data mapping) + vd = *it; + m_vertices_removed.erase(it); + MCUT_ASSERT((size_t)vd < m_vertices.size()); // MCUT_ASSERT(m_vertices.find(vd) != m_vertices.cend()); + data_ptr = &m_vertices[vd]; + } else { + vd = static_cast(number_of_vertices()); + + // std::pair::iterator, bool> ret = m_vertices.insert(std::make_pair(vd, vertex_data_t())); + // MCUT_ASSERT(ret.second == true); + m_vertices.emplace_back(vertex_data_t()); + + MCUT_ASSERT((size_t)vd <= (m_vertices.size() - 1)); + + data_ptr = &m_vertices[vd]; // &ret.first->second; + } + + MCUT_ASSERT(vd != hmesh_t::null_vertex()); + + data_ptr->p[0] = x; + data_ptr->p[1] = y; + data_ptr->p[2] = z; + + return vd; +} + +halfedge_descriptor_t hmesh_t::add_edge(const vertex_descriptor_t v0, const vertex_descriptor_t v1) +{ + MCUT_ASSERT(v0 != null_vertex()); + MCUT_ASSERT(v1 != null_vertex()); + + // primary halfedge(0) of edge + halfedge_descriptor_t h0_idx(static_cast(number_of_halfedges())); // primary halfedge of new edge to be created + bool reusing_removed_h0_descr = (!m_halfedges_removed.empty()); + + if (reusing_removed_h0_descr) // can we re-use a slot? + { + std::vector::iterator hIter = m_halfedges_removed.begin(); // take the oldest unused slot (NOTE: important for user data mapping) + h0_idx = *hIter; + m_halfedges_removed.erase(hIter); + MCUT_ASSERT((size_t)h0_idx < m_halfedges.size() /*m_halfedges.find(h0_idx) != m_halfedges.cend()*/); + } + + halfedge_data_t* halfedge0_data_ptr = nullptr; + if (reusing_removed_h0_descr) { + // halfedge0_data_ptr = &m_halfedges[h0_idx); + } else { + // create new halfedge --> h0 + // std::pair::iterator, bool> h0_ret = m_halfedges.insert(std::make_pair(h0_idx, halfedge_data_t())); + // MCUT_ASSERT(h0_ret.second == true); + // halfedge0_data_ptr = &h0_ret.first->second; + m_halfedges.emplace_back(halfedge_data_t()); + } + + // second halfedge(1) of edge + halfedge_descriptor_t h1_idx(static_cast(number_of_halfedges())); // second halfedge of new edge to be created (opposite of h0_idx) + bool reusing_removed_h1_descr = (!m_halfedges_removed.empty()); + + if (reusing_removed_h1_descr) // can we re-use a slot? + { + std::vector::iterator hIter = m_halfedges_removed.begin() /*+ (m_halfedges_removed.size() - 1)*/; // take the most recently removed + h1_idx = *hIter; + m_halfedges_removed.erase(hIter); + MCUT_ASSERT((size_t)h1_idx < m_halfedges.size() /*m_halfedges.find(h1_idx) != m_halfedges.cend()*/); + } + + halfedge_data_t* halfedge1_data_ptr = nullptr; + if (reusing_removed_h1_descr) { + // halfedge1_data_ptr = &m_halfedges[h1_idx); + } else { + // create new halfedge --> h1 + // std::pair::iterator, bool> h1_ret = m_halfedges.insert(std::make_pair(h1_idx, halfedge_data_t())); + // MCUT_ASSERT(h1_ret.second == true); + // halfedge1_data_ptr = &h1_ret.first->second; + m_halfedges.emplace_back(halfedge_data_t()); + } + + // https://stackoverflow.com/questions/34708189/c-vector-of-pointer-loses-the-reference-after-push-back + halfedge0_data_ptr = &m_halfedges[h0_idx]; + halfedge1_data_ptr = &m_halfedges[h1_idx]; + + // edge + + edge_descriptor_t e_idx(static_cast(number_of_edges())); // index of new edge + bool reusing_removed_edge_descr = (!m_edges_removed.empty()); + + if (reusing_removed_edge_descr) // can we re-use a slot? + { + std::vector::iterator eIter = m_edges_removed.begin(); // take the oldest unused slot (NOTE: important for user data mapping) + e_idx = *eIter; + m_edges_removed.erase(eIter); + MCUT_ASSERT((size_t)e_idx < m_edges.size() /*m_edges.find(e_idx) != m_edges.cend()*/); + } + + edge_data_t* edge_data_ptr = nullptr; + if (reusing_removed_edge_descr) { + edge_data_ptr = &m_edges[e_idx]; + } else { + // std::pair::iterator, bool> eret = m_edges.insert(std::make_pair(e_idx, edge_data_t())); // create a new edge + // MCUT_ASSERT(eret.second == true); + // edge_data_ptr = &eret.first->second; + m_edges.emplace_back(edge_data_t()); + edge_data_ptr = &m_edges.back(); + } + + // update incidence information + + // edge_data_t& edge_data = eret.first->second; + edge_data_ptr->h = h0_idx; // even/primary halfedge + // eret.first->h = h0_idx; // even/primary halfedge + + // halfedge_data_t& halfedge0_data = h0_ret.first->second; + halfedge0_data_ptr->t = v1; // target vertex of h0 + halfedge0_data_ptr->o = h1_idx; // ... because opp has idx differing by 1 + halfedge0_data_ptr->e = e_idx; + + // halfedge_data_t& halfedge1_data = h1_ret.first->second; + halfedge1_data_ptr->t = v0; // target vertex of h1 + halfedge1_data_ptr->o = h0_idx; // ... because opp has idx differing by 1 + halfedge1_data_ptr->e = e_idx; + + // MCUT_ASSERT(h1_ret.first->first == halfedge0_data_ptr->o); // h1 comes just afterward (its index) + + // update vertex incidence + + // v0 + MCUT_ASSERT((size_t)v0 < m_vertices.size()); // MCUT_ASSERT(m_vertices.count(v0) == 1); + vertex_data_t& v0_data = m_vertices[v0]; + // MCUT_ASSERT(); + if (std::find(v0_data.m_halfedges.cbegin(), v0_data.m_halfedges.cend(), h1_idx) == v0_data.m_halfedges.cend()) { + v0_data.m_halfedges.emplace_back(h1_idx); // halfedge whose target is v0 + } + // v1 + MCUT_ASSERT((size_t)v1 < m_vertices.size()); // MCUT_ASSERT(m_vertices.count(v1) == 1); + vertex_data_t& v1_data = m_vertices[v1]; + // MCUT_ASSERT(); + if (std::find(v1_data.m_halfedges.cbegin(), v1_data.m_halfedges.cend(), h0_idx) == v1_data.m_halfedges.cend()) { + v1_data.m_halfedges.emplace_back(h0_idx); // halfedge whose target is v1 + } + + return static_cast(h0_idx); // return halfedge whose target is v1 +} + +face_descriptor_t hmesh_t::add_face(const std::vector& vi) +{ + const int face_vertex_count = static_cast(vi.size()); + MCUT_ASSERT(face_vertex_count >= 3); + + const int face_count = number_of_faces(); + face_data_t new_face_data; + face_descriptor_t new_face_idx(static_cast(face_count)); + bool reusing_removed_face_descr = (!m_faces_removed.empty()); + + if (reusing_removed_face_descr) // can we re-use a slot? + { + std::vector::iterator fIter = m_faces_removed.begin(); // take the oldest unused slot (NOTE: important for user data mapping) + new_face_idx = *fIter; + m_faces_removed.erase(fIter); // slot is going to be used again + + MCUT_ASSERT((size_t)new_face_idx < m_faces.size() /*m_faces.find(new_face_idx) != m_faces.cend()*/); + } + + face_data_t* face_data_ptr = reusing_removed_face_descr ? &m_faces[new_face_idx] : &new_face_data; + face_data_ptr->m_halfedges.clear(); + face_data_ptr->m_halfedges.reserve(face_vertex_count); + + for (int i = 0; i < face_vertex_count; ++i) { + const vertex_descriptor_t v0 = vi[i]; // i.e. src + + MCUT_ASSERT(v0 != null_vertex()); + + const vertex_descriptor_t v1 = vi[(i + 1) % face_vertex_count]; // i.e. tgt + + MCUT_ASSERT(v1 != null_vertex()); + + // check if edge exists between v0 and v1 (using halfedges incident to either v0 or v1) + // TODO: use the halfedge(..., true) function + // vertex_data_t& v0_data = m_vertices[v0]; + // vertex_data_t& v1_data = m_vertices[v1]; + +#if 0 + vertex_data_t& v0_data = m_vertices[v0]; + vertex_data_t& v1_data = m_vertices[v1]; + + bool connecting_edge_exists = false; + halfedge_descriptor_t v0_h = null_halfedge(); + halfedge_descriptor_t v1_h = null_halfedge(); + + for (int v0_h_iter = 0; v0_h_iter < static_cast(v0_data.m_halfedges.size()); ++v0_h_iter) { + v0_h = v0_data.m_halfedges[v0_h_iter]; + const edge_descriptor_t v0_e = edge(v0_h); + + for (int v1_h_iter = 0; v1_h_iter < static_cast(v1_data.m_halfedges.size()); ++v1_h_iter) { + + v1_h = v1_data.m_halfedges[v1_h_iter]; + const edge_descriptor_t v1_e = edge(v1_h); + const bool same_edge = (v0_e == v1_e); + + if (same_edge) { + connecting_edge_exists = true; + break; + } + } + + if (connecting_edge_exists) { + break; + } + } +#endif + halfedge_descriptor_t v1_h = halfedge(v0, v1, true); + bool connecting_edge_exists = v1_h != null_halfedge(); + + // halfedge_descriptor_t v1_h = null_halfedge(); + + // we use v1 in the following since v1 is the target (vertices are associated with halfedges which point to them) + + halfedge_data_t* v1_hd_ptr = nullptr; // refer to halfedge whose tgt is v1 + + if (connecting_edge_exists) // edge connecting v0 and v1 + { + MCUT_ASSERT((size_t)v1_h < m_halfedges.size() /*m_halfedges.count(v1_h) == 1*/); + + v1_hd_ptr = &m_halfedges[v1_h]; + } else { // there exists no edge between v0 and v1, so we create it + v1_h = add_edge(v0, v1); + MCUT_ASSERT((size_t)v1_h < m_halfedges.size() /*m_halfedges.count(h) == 1*/); + v1_hd_ptr = &m_halfedges[v1_h]; // add to vertex list since v1 is the target of h + } + + MCUT_ASSERT(source(v1_h) == v0); + MCUT_ASSERT(target(v1_h) == v1); + + if (v1_hd_ptr->f != null_face()) { + #if 0 // used for debugging triangulation + printf("face f%d uses halfedge: v%d v%d\n", (int)v1_hd_ptr->f, (int)v0, (int)v1); + const auto verts = get_vertices_around_face(v1_hd_ptr->f); + for (auto v : verts) + printf("p%d ", (int)v); + printf("\n"); + printf("h%d.opp = h%d; =%d \n", (int)v1_h, (int)opposite(v1_h), (int)face(opposite(v1_h))); + #endif + return null_face(); // face is incident to a non-manifold edge + } + + v1_hd_ptr->f = new_face_idx; // associate halfedge with face + face_data_ptr->m_halfedges.emplace_back(v1_h); + } + + if (!reusing_removed_face_descr) { + MCUT_ASSERT((size_t)new_face_idx == m_faces.size() /*m_faces.count(new_face_idx) == 0*/); + // m_faces.insert(std::make_pair(new_face_idx, *face_data_ptr)); + m_faces.emplace_back(*face_data_ptr); + } + + // update halfedges (next halfedge) + // const std::vector& halfedges_around_new_face = get_halfedges_around_face(new_face_idx); + const int num_halfedges = static_cast(face_data_ptr->m_halfedges.size()); + + for (int i = 0; i < num_halfedges; ++i) { + const halfedge_descriptor_t h = face_data_ptr->m_halfedges[i]; + const halfedge_descriptor_t nh = face_data_ptr->m_halfedges[(i + 1) % num_halfedges]; + set_next(h, nh); + } + + return new_face_idx; +} + +bool hmesh_t::is_insertable(const std::vector& vi) const +{ + const int face_vertex_count = static_cast(vi.size()); + MCUT_ASSERT(face_vertex_count >= 3); + + for (int i = 0; i < face_vertex_count; ++i) { + const vertex_descriptor_t v0 = vi[i]; // i.e. src + + MCUT_ASSERT(v0 != null_vertex()); + + const vertex_descriptor_t v1 = vi[(i + 1) % face_vertex_count]; // i.e. tgt + + MCUT_ASSERT(v1 != null_vertex()); + + // halfedge from v0 to v1 + const halfedge_descriptor_t v1_h = halfedge(v0, v1, false); + const bool connecting_edge_exists = v1_h != null_halfedge(); + + // we use v1 in the following since v1 is the target (vertices are associated with halfedges which point to them) + + if (connecting_edge_exists) // edge connecting v0 and v1 + { + MCUT_ASSERT((size_t)v1_h < m_halfedges.size() /*m_halfedges.count(v1_h) == 1*/); + + const halfedge_data_t* const v1_hd_ptr = &m_halfedges[v1_h]; + + if (v1_hd_ptr->f != null_face()) { + //printf("h%d(v%d -> v%d) = f%d\n", (int)v1_h, (int)v0, (int)v1, (int)v1_hd_ptr->f); + return false; // face is incident to a non-manifold edge + } + } + } + return true; +} + +const vec3& hmesh_t::vertex(const vertex_descriptor_t& vd) const +{ + MCUT_ASSERT(vd != null_vertex()); + MCUT_ASSERT((size_t)vd < m_vertices.size()); + const vertex_data_t& vdata = m_vertices[vd]; + return vdata.p; +} + +uint32_t hmesh_t::get_num_vertices_around_face(const face_descriptor_t f) const +{ + MCUT_ASSERT(f != null_face()); + + const std::vector& halfedges_on_face = get_halfedges_around_face(f); + + return (uint32_t)halfedges_on_face.size(); +} + +std::vector hmesh_t::get_vertices_around_face(const face_descriptor_t f, uint32_t prepend_offset) const +{ + MCUT_ASSERT(f != null_face()); + + const std::vector& halfedges_on_face = get_halfedges_around_face(f); + + std::vector vertex_descriptors(halfedges_on_face.size()); + + for (int i = 0; i < (int)halfedges_on_face.size(); ++i) { + const halfedge_descriptor_t h = halfedges_on_face[i]; + // MCUT_ASSERT((size_t)h < m_halfedges.size() /*m_halfedges.count(h) == 1*/); + // const halfedge_data_t& hd = m_halfedges[h]; + vertex_descriptors[i] = vertex_descriptor_t(prepend_offset + target(h) /*hd.t*/); + } + return vertex_descriptors; +} + +void hmesh_t::get_vertices_around_face(std::vector& vertex_descriptors, const face_descriptor_t f, uint32_t prepend_offset) const +{ + MCUT_ASSERT(f != null_face()); + + const std::vector& halfedges_on_face = get_halfedges_around_face(f); + vertex_descriptors.resize(halfedges_on_face.size()); + + for (int i = 0; i < (int)halfedges_on_face.size(); ++i) { + const halfedge_descriptor_t h = halfedges_on_face[i]; + vertex_descriptors[i] = vertex_descriptor_t(prepend_offset + target(h) /*hd.t*/); + } +} + +std::vector hmesh_t::get_vertices_around_vertex(const vertex_descriptor_t v) const +{ + MCUT_ASSERT(v != null_vertex()); + // halfedges whoe target is 'v' + const std::vector& halfedges = get_halfedges_around_vertex(v); + std::vector out(halfedges.size()); + //out.resize(halfedges.size()); + uint32_t counter = 0; + for (std::vector::const_iterator h = halfedges.cbegin(); h != halfedges.cend(); ++h) { + vertex_descriptor_t src = source(*h); + out[counter++] = src; + } + return out; +} + +void hmesh_t::get_vertices_around_vertex(std::vector& vertices_around_vertex, const vertex_descriptor_t v) const +{ + MCUT_ASSERT(v != null_vertex()); + // halfedges whoe target is 'v' + const std::vector& halfedges = get_halfedges_around_vertex(v); + + vertices_around_vertex.reserve(halfedges.size()); + for (std::vector::const_iterator h = halfedges.cbegin(); h != halfedges.cend(); ++h) { + vertex_descriptor_t src = source(*h); + vertices_around_vertex.emplace_back(src); + } +} + +const std::vector& hmesh_t::get_halfedges_around_face(const face_descriptor_t f) const +{ + MCUT_ASSERT(f != null_face()); + MCUT_ASSERT((size_t)f < m_faces.size() /*m_faces.count(f) == 1*/); + return m_faces[f].m_halfedges; +} + +const std::vector hmesh_t::get_faces_around_face(const face_descriptor_t f, const std::vector* halfedges_around_face_) const +{ + MCUT_ASSERT(f != null_face()); + + std::vector faces_around_face; + + const std::vector& halfedges_on_face = (halfedges_around_face_ != nullptr) ? *halfedges_around_face_ : get_halfedges_around_face(f); + + for (int i = 0; i < (int)halfedges_on_face.size(); ++i) { + + const halfedge_descriptor_t h = halfedges_on_face[i]; + MCUT_ASSERT((size_t)h < m_halfedges.size() /*m_halfedges.count(h) == 1*/); + const halfedge_data_t& hd = m_halfedges[h]; + + if (hd.o != null_halfedge()) { + MCUT_ASSERT((size_t)hd.o < m_halfedges.size() /*m_halfedges.count(hd.o) == 1*/); + const halfedge_data_t& ohd = m_halfedges[hd.o]; + + if (ohd.f != null_face()) { + faces_around_face.emplace_back(ohd.f); + } + } + } + return faces_around_face; +} +// NOTE: we have a lot of dupication here +void hmesh_t::get_faces_around_face(std::vector& faces_around_face, const face_descriptor_t f, const std::vector* halfedges_around_face_) const +{ + MCUT_ASSERT(f != null_face()); + + faces_around_face.clear(); + + const std::vector& halfedges_on_face = (halfedges_around_face_ != nullptr) ? *halfedges_around_face_ : get_halfedges_around_face(f); + + for (int i = 0; i < (int)halfedges_on_face.size(); ++i) { + + const halfedge_descriptor_t h = halfedges_on_face[i]; + MCUT_ASSERT((size_t)h < m_halfedges.size() /*m_halfedges.count(h) == 1*/); + const halfedge_data_t& hd = m_halfedges[h]; + + if (hd.o != null_halfedge()) { + MCUT_ASSERT((size_t)hd.o < m_halfedges.size() /*m_halfedges.count(hd.o) == 1*/); + const halfedge_data_t& ohd = m_halfedges[hd.o]; + + if (ohd.f != null_face()) { + faces_around_face.emplace_back(ohd.f); + } + } + } +} + +uint32_t hmesh_t::get_num_faces_around_face(const face_descriptor_t f, const std::vector* halfedges_around_face_) const +{ + MCUT_ASSERT(f != null_face()); + + uint32_t num_faces_around_face = 0; + + const std::vector& halfedges_on_face = (halfedges_around_face_ != nullptr) ? *halfedges_around_face_ : get_halfedges_around_face(f); + + for (uint32_t i = 0; i < (uint32_t)halfedges_on_face.size(); ++i) { + + const halfedge_descriptor_t h = halfedges_on_face[i]; + + MCUT_ASSERT((size_t)h < m_halfedges.size() /*m_halfedges.count(h) == 1*/); + + const halfedge_data_t& hd = m_halfedges[h]; + + if (hd.o != null_halfedge()) { + + MCUT_ASSERT((size_t)hd.o < m_halfedges.size() /*m_halfedges.count(hd.o) == 1*/); + + const halfedge_data_t& ohd = m_halfedges[hd.o]; + + if (ohd.f != null_face()) { + ++num_faces_around_face; + } + } + } + return num_faces_around_face; +} + +const std::vector& hmesh_t::get_halfedges_around_vertex(const vertex_descriptor_t v) const +{ + MCUT_ASSERT(v != hmesh_t::null_vertex()); + MCUT_ASSERT((size_t)v < m_vertices.size()); + const vertex_data_t& vd = m_vertices[v]; + const std::vector& incoming_halfedges = vd.m_halfedges; + return incoming_halfedges; +} + +vertex_array_iterator_t hmesh_t::vertices_begin(bool account_for_removed_elems) const +{ + vertex_array_t::const_iterator it = m_vertices.cbegin(); + if (account_for_removed_elems) { + uint32_t index = 0; + while (it != m_vertices.cend() && is_removed(vertex_descriptor_t(index++)) /*is_removed(it)*/) { + ++it; // shift the pointer to the first valid mesh element + } + } + return vertex_array_iterator_t(it, this); +} + +vertex_array_iterator_t hmesh_t::vertices_end() const +{ + return vertex_array_iterator_t(m_vertices.cend(), this); +} + +edge_array_iterator_t hmesh_t::edges_begin(bool account_for_removed_elems) const +{ + edge_array_t::const_iterator it = m_edges.cbegin(); + if (account_for_removed_elems) { + uint32_t index = 0; + while (it != m_edges.cend() && is_removed(edge_descriptor_t(index++) /*it->first*/)) { + ++it; // shift the pointer to the first valid mesh element + } + } + return edge_array_iterator_t(it, this); +} + +edge_array_iterator_t hmesh_t::edges_end() const +{ + return edge_array_iterator_t(m_edges.cend(), this); +} + +halfedge_array_iterator_t hmesh_t::halfedges_begin(bool account_for_removed_elems) const +{ + halfedge_array_t::const_iterator it = m_halfedges.cbegin(); + if (account_for_removed_elems) { + uint32_t index = 0; + while (it != m_halfedges.cend() && is_removed(halfedge_descriptor_t(index++) /*it->first*/)) { + ++it; // shift the pointer to the first valid mesh element + } + } + return halfedge_array_iterator_t(it, this); +} + +halfedge_array_iterator_t hmesh_t::halfedges_end() const +{ + return halfedge_array_iterator_t(m_halfedges.cend(), this); +} + +face_array_iterator_t hmesh_t::faces_begin(bool account_for_removed_elems) const +{ + face_array_t::const_iterator it = m_faces.cbegin(); + if (account_for_removed_elems) { + uint32_t index = 0; + while (it != m_faces.cend() && is_removed(face_descriptor_t(index++) /*it->first*/)) { + ++it; // shift the pointer to the first valid mesh element + } + } + return face_array_iterator_t(it, this); +} + +face_array_iterator_t hmesh_t::faces_end() const +{ + return face_array_iterator_t(m_faces.cend(), this); +} + +// also disassociates (not remove) any halfedges(s) and vertices incident to face +void hmesh_t::remove_face(const face_descriptor_t f) +{ + MCUT_ASSERT(f != null_face()); + MCUT_ASSERT(std::find(m_faces_removed.cbegin(), m_faces_removed.cend(), f) == m_faces_removed.cend()); + + face_data_t& fd = m_faces[f]; + + static thread_local std::vector face_vertices; // ... that are used by face + face_vertices.resize(0); + + // disassociate halfedges + + for (std::vector::const_iterator it = fd.m_halfedges.cbegin(); it != fd.m_halfedges.cend(); ++it) { + halfedge_data_t& hd = m_halfedges[*it]; + MCUT_ASSERT(hd.f != null_face()); + hd.f = null_face(); + + // NOTE: "next" and "previous" are only meaningful when the halfedge is used by a + // face. So we reset that information here since the halfedge is not longer used + // by a face + if (hd.n != null_halfedge()) { // disassociate "next" + const halfedge_descriptor_t hn = hd.n; + halfedge_data_t& hnd = m_halfedges[hn]; + MCUT_ASSERT(hnd.p == *it); + hnd.p = null_halfedge(); + // + hd.n = null_halfedge(); + } + + if (hd.p != null_halfedge()) { // disassociate "previous" + const halfedge_descriptor_t hp = hd.p; + halfedge_data_t& hpd = m_halfedges[hp]; + MCUT_ASSERT(hpd.n == *it); + hpd.n = null_halfedge(); + // + hd.p = null_halfedge(); + } + + face_vertices.emplace_back(hd.t); + } + + // disassociate vertices +#if 0 + // for each vertex used by face + for (std::vector::const_iterator it = face_vertices.cbegin(); it != face_vertices.cend(); ++it) { + vertex_descriptor_t face_vertex = *it; + vertex_data_t& vd = m_vertices[face_vertex]; + + std::vector::iterator fIter = std::find(vd.m_faces.begin(), vd.m_faces.end(), f); + + if (fIter != vd.m_faces.cend()) { + vd.m_faces.erase(fIter); // remove association + } + } +#endif + m_faces_removed.emplace_back(f); +} + +// also disassociates (not remove) the halfedges(s) and vertex incident to this halfedge +void hmesh_t::remove_halfedge(halfedge_descriptor_t h) +{ + MCUT_ASSERT(h != null_halfedge()); + MCUT_ASSERT(std::find(m_halfedges_removed.cbegin(), m_halfedges_removed.cend(), h) == m_halfedges_removed.cend()); + + halfedge_data_t& hd = m_halfedges[h]; + + MCUT_ASSERT(hd.e == null_edge()); // there must not be an edge dependent on h if we are to remove h + MCUT_ASSERT(hd.f == null_face()); // there must not be a face dependent on h if we are to remove h + + if (hd.n != null_halfedge()) { // disassociate next + const halfedge_descriptor_t hn = hd.n; + halfedge_data_t& hnd = m_halfedges[hn]; + MCUT_ASSERT(hnd.p == h); + hnd.p = null_halfedge(); + // + hd.n = null_halfedge(); + } + + if (hd.o != null_halfedge()) { // disassociate opposite + const halfedge_descriptor_t ho = hd.o; + halfedge_data_t& hod = m_halfedges[ho]; + MCUT_ASSERT(hod.o == h); + hod.o = null_halfedge(); + // + hd.o = null_halfedge(); + } + + if (hd.p != null_halfedge()) { // disassociate previous + const halfedge_descriptor_t hp = hd.p; + halfedge_data_t& hpd = m_halfedges[hp]; + MCUT_ASSERT(hpd.n == h); + hpd.n = null_halfedge(); + // + hd.p = null_halfedge(); + } + + MCUT_ASSERT(hd.t != null_vertex()); // every h has a target vertex which is effectively dependent on h + + // disassociate target vertex + vertex_data_t& htd = m_vertices[hd.t]; + std::vector::iterator hIter = std::find(htd.m_halfedges.begin(), htd.m_halfedges.end(), h); + + MCUT_ASSERT(hIter != htd.m_halfedges.end()); // because not yet removed h + + htd.m_halfedges.erase(hIter); // remove association + + m_halfedges_removed.emplace_back(h); +} + +// also disassociates (not remove) any face(s) incident to edge via its halfedges, and also disassociates the halfedges +void hmesh_t::remove_edge(const edge_descriptor_t e, bool remove_halfedges) +{ + MCUT_ASSERT(e != null_edge()); + MCUT_ASSERT(std::find(m_edges_removed.cbegin(), m_edges_removed.cend(), e) == m_edges_removed.cend()); + + edge_data_t& ed = m_edges[e]; + std::vector halfedges = { ed.h, opposite(ed.h) }; // both halfedges incident to edge must be disassociated + + for (std::vector::const_iterator it = halfedges.cbegin(); it != halfedges.cend(); ++it) { + const halfedge_descriptor_t h = *it; + MCUT_ASSERT(h != null_halfedge()); + + // disassociate halfedge + halfedge_data_t& hd = m_halfedges[h]; + MCUT_ASSERT(hd.e == e); + hd.e = null_edge(); + if (remove_halfedges) { + remove_halfedge(h); + } + } + + ed.h = null_halfedge(); // we are removing the edge so every associated data element must be nullified + + m_edges_removed.emplace_back(e); +} + +void hmesh_t::remove_vertex(const vertex_descriptor_t v) +{ + MCUT_ASSERT(v != null_vertex()); + MCUT_ASSERT((size_t)v < m_vertices.size()); + MCUT_ASSERT(std::find(m_vertices_removed.cbegin(), m_vertices_removed.cend(), v) == m_vertices_removed.cend()); + //MCUT_ASSERT(m_vertices[v].m_faces.empty()); + MCUT_ASSERT(m_vertices[v].m_halfedges.empty()); + + m_vertices_removed.emplace_back(v); +} + +void hmesh_t::remove_elements() +{ + for (face_array_iterator_t i = faces_begin(); i != faces_end(); ++i) { + remove_face(*i); + } + + for (edge_array_iterator_t i = edges_begin(); i != edges_end(); ++i) { + remove_edge(*i); + } + + for (halfedge_array_iterator_t i = halfedges_begin(); i != halfedges_end(); ++i) { + remove_halfedge(*i); + } + + for (vertex_array_iterator_t i = vertices_begin(); i != vertices_end(); ++i) { + remove_vertex(*i); + } +} + +void hmesh_t::reset() +{ + m_vertices.clear(); + m_vertices.shrink_to_fit(); + m_vertices_removed.clear(); + m_vertices_removed.shrink_to_fit(); + m_halfedges.clear(); + m_halfedges.shrink_to_fit(); + m_halfedges_removed.clear(); + m_halfedges_removed.shrink_to_fit(); + m_edges.clear(); + m_edges.shrink_to_fit(); + m_edges_removed.clear(); + m_edges_removed.shrink_to_fit(); + m_faces.clear(); + m_faces.shrink_to_fit(); + m_faces_removed.clear(); + m_faces_removed.shrink_to_fit(); +} + +int hmesh_t::number_of_internal_faces() const +{ + return static_cast(m_faces.size()); +} + +int hmesh_t::number_of_internal_edges() const +{ + return static_cast(m_edges.size()); +} + +int hmesh_t::number_of_internal_halfedges() const +{ + return static_cast(m_halfedges.size()); +} + +int hmesh_t::number_of_internal_vertices() const +{ + return static_cast(m_vertices.size()); +} + +// +int hmesh_t::number_of_vertices_removed() const +{ + return (int)this->m_vertices_removed.size(); +} + +int hmesh_t::number_of_edges_removed() const +{ + return (int)this->m_edges_removed.size(); +} + +int hmesh_t::number_of_halfedges_removed() const +{ + return (int)this->m_halfedges_removed.size(); +} + +int hmesh_t::number_of_faces_removed() const +{ + return (int)this->m_faces_removed.size(); +} + +bool hmesh_t::is_removed(face_descriptor_t f) const +{ + return std::find(m_faces_removed.cbegin(), m_faces_removed.cend(), f) != m_faces_removed.cend(); +} + +bool hmesh_t::is_removed(edge_descriptor_t e) const +{ + return std::find(m_edges_removed.cbegin(), m_edges_removed.cend(), e) != m_edges_removed.cend(); +} + +bool hmesh_t::is_removed(halfedge_descriptor_t h) const +{ + return std::find(m_halfedges_removed.cbegin(), m_halfedges_removed.cend(), h) != m_halfedges_removed.cend(); +} + +bool hmesh_t::is_removed(vertex_descriptor_t v) const +{ + return std::find(m_vertices_removed.cbegin(), m_vertices_removed.cend(), v) != m_vertices_removed.cend(); +} + +void hmesh_t::reserve_for_additional_vertices(std::uint32_t n) +{ + m_vertices.reserve((std::uint64_t)number_of_internal_vertices() + n); +} + +void hmesh_t::reserve_for_additional_edges(std::uint32_t n) +{ + m_edges.reserve((std::uint64_t)number_of_internal_edges() + n); +} + +void hmesh_t::reserve_for_additional_halfedges(std::uint32_t n) +{ + m_halfedges.reserve((std::uint64_t)number_of_internal_halfedges() + n); +} + +void hmesh_t::reserve_for_additional_faces(std::uint32_t n) +{ + m_faces.reserve((std::uint64_t)number_of_internal_faces() + n); +} + +void hmesh_t::reserve_for_additional_elements(std::uint32_t n) +{ + const std::uint32_t nv = n; + reserve_for_additional_vertices(nv); + const std::uint32_t nf = nv * 2; + reserve_for_additional_faces(nf); + const std::uint32_t ne = std::uint32_t((3.0 / 2.0) * (double)nf); + reserve_for_additional_edges(ne); + const std::uint32_t nh = ne * 2; + reserve_for_additional_halfedges(nh); +} + +const std::vector& hmesh_t::get_removed_elements(id_>) const +{ + return get_removed_vertices(); +} + +const std::vector& hmesh_t::get_removed_elements(id_>) const +{ + return get_removed_edges(); +} + +const std::vector& hmesh_t::get_removed_elements(id_>) const +{ + return get_removed_halfedges(); +} + +const std::vector& hmesh_t::get_removed_elements(id_>) const +{ + return get_removed_faces(); +} + +const std::vector& hmesh_t::get_removed_vertices() const +{ + return m_vertices_removed; +} + +const std::vector& hmesh_t::get_removed_edges() const +{ + return m_edges_removed; +} + +const std::vector& hmesh_t::get_removed_halfedges() const +{ + return m_halfedges_removed; +} + +const std::vector& hmesh_t::get_removed_faces() const +{ + return m_faces_removed; +} + +const vertex_array_iterator_t hmesh_t::elements_begin_(id_>, bool account_for_removed_elems) const +{ + return vertices_begin(account_for_removed_elems); +} + +const edge_array_iterator_t hmesh_t::elements_begin_(id_>, bool account_for_removed_elems) const +{ + return edges_begin(account_for_removed_elems); +} + +const halfedge_array_iterator_t hmesh_t::elements_begin_(id_>, bool account_for_removed_elems) const +{ + return halfedges_begin(account_for_removed_elems); +} + +const face_array_iterator_t hmesh_t::elements_begin_(id_>, bool account_for_removed_elems) const +{ + return faces_begin(account_for_removed_elems); +} + +void write_off(const char* fpath, const hmesh_t& mesh) +{ + + std::ofstream outfile(fpath); + + if (!outfile.is_open()) { + printf("error: could not open file: %s\n", fpath); + std::exit(1); + } + + // + // file header + // + outfile << "OFF\n"; + + // + // #vertices, #faces, #edges + // + outfile << mesh.number_of_vertices() << " " << mesh.number_of_faces() << " " << 0 /*mesh.number_of_edges()*/ << "\n"; + + // + // vertices + // + for (vertex_array_iterator_t iter = mesh.vertices_begin(); iter != mesh.vertices_end(); ++iter) { + // const vertex_data_t& vdata = iter.second; + const vec3& point = mesh.vertex(*iter); + outfile << (double)point.x() << " " << (double)point.y() << " " << (double)point.z() << "\n"; + } + + // + // edges + // + +#if 0 + for (typename hmesh_t::edge_iterator_t iter = mesh.edges_begin(); iter != mesh.edges_end(); ++iter) { + const hmesh_t::edge_descriptor_t ed = iter.first; + const hmesh_t::vertex_descriptor_t& v0 = vertex(ed, 0); + const hmesh_t::vertex_descriptor_t& v1 = vertex(ed, 1); + // TODO + } +#endif + + // + // faces + // + for (face_array_iterator_t iter = mesh.faces_begin(); iter != mesh.faces_end(); ++iter) { + // const typename hmesh_t::face_descriptor_t& fd = iter.first; + const std::vector vertices_around_face = mesh.get_vertices_around_face(*iter); + + MCUT_ASSERT(!vertices_around_face.empty()); + + outfile << vertices_around_face.size() << " "; + + for (std::vector::const_iterator i = vertices_around_face.cbegin(); i != vertices_around_face.cend(); ++i) { + outfile << (*i) << " "; + } + outfile << " \n"; + } + + outfile.close(); +} + +void read_off(hmesh_t& mesh, const char* fpath) +{ + auto next_line = [&](std::ifstream& f, std::string& s) -> bool { + while (getline(f, s)) { + if (s.length() > 1 && s[0] != '#') { + return true; + } + } + return false; + }; + + std::ifstream infile(fpath); + + if (!infile.is_open()) { + printf("error: could not open file: %s\n", fpath); + std::exit(1); + } + + // + // file header + // + std::string header; + if (!next_line(infile, header)) { + printf("error: .off file header not found\n"); + std::exit(1); + } + + if (header != "OFF") { + printf("error: unrecognised .off file header\n"); + std::exit(1); + } + + // + // #vertices, #faces, #edges + // + std::string info; + if (!next_line(infile, info)) { + printf("error: .off element count not found\n"); + std::exit(1); + } + + std::istringstream info_stream; + info_stream.str(info); + + int nvertices; + int nfaces; + int nedges; + info_stream >> nvertices >> nfaces >> nedges; + + // + // vertices + // + std::map vmap; + for (int i = 0; i < nvertices; ++i) { + if (!next_line(infile, info)) { + printf("error: .off vertex not found\n"); + std::exit(1); + } + std::istringstream vtx_line_stream(info); + + double x; + double y; + double z; + vtx_line_stream >> x >> y >> z; + vmap[i] = mesh.add_vertex(x, y, z); + } + + // + // edges + // + for (int i = 0; i < nedges; ++i) { + // TODO + } + + // + // faces + // + for (auto i = 0; i < nfaces; ++i) { + if (!next_line(infile, info)) { + printf("error: .off file face not found\n"); + std::exit(1); + } + std::istringstream face_line_stream(info); + int n; // number of vertices in face + int index; + face_line_stream >> n; + + if (n < 3) { + printf("error: invalid polygon vertex count in file (%d)\n", n); + std::exit(1); + } + + typename std::vector face; + face.resize(n); + for (int j = 0; j < n; ++j) { + info_stream >> index; + face[j] = vmap[index]; + } + + mesh.add_face(face); + } + + infile.close(); +} diff --git a/src/mcut/source/kernel.cpp b/src/mcut/source/kernel.cpp new file mode 100644 index 0000000000..8199053e15 --- /dev/null +++ b/src/mcut/source/kernel.cpp @@ -0,0 +1,10634 @@ +/** + * 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: + * (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 +#include +#include +#include // std::iota +#include +#include +#include +#include +#include + +#include "mcut/internal/bvh.h" +#include "mcut/internal/hmesh.h" +#include "mcut/internal/kernel.h" +#include "mcut/internal/math.h" +#include "mcut/internal/utils.h" +#include "mcut/internal/timer.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& halfedges_around_face = mesh.get_halfedges_around_face(*face_iter); + + // int num_halfedges = ; + MCUT_ASSERT((int)halfedges_around_face.size()); + + // + for (std::vector::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 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& visited, int connected_component_id) +{ + std::vector verts = mesh.get_vertices_around_vertex(u); + for (std::vector::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& fccmap, + const hmesh_t& mesh, + std::vector& cc_to_vertex_count, + std::vector& 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 visited(mesh.number_of_vertices(), -1); // if vertex does not exist, then its not visited + int connected_component_id = -1; + std::vector queued(mesh.number_of_vertices(), false); + std::queue queue; // .. to discover all vertices of current connected component + + std::vector vertices_of_v; + std::vector 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::iterator block_start_, std::vector::iterator block_end_) { + for (std::vector::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 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 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 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& mesh_seam_vertices, + hmesh_t& mesh, + const int ps_num_vertices, + const int m1_num_vertices_after_srcmesh_partitioning = std::numeric_limits::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(*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, connected_component_info_t>>>& connected_components, + const hmesh_t& in, + const int traced_polygons_base_offset, + const std::vector>& mX_traced_polygons, // "m0" or "m1" (dependent on function-call location) + const std::vector& sm_polygons_below_cs, + const std::vector& sm_polygons_above_cs, + const std::vector& mesh_vertex_to_seam_flag, + // vertex & face data mapping parameters + // const std::map &m1_to_m0_sm_ovtx_colored, + const std::vector& m1_to_m0_sm_ovtx_colored, + const std::unordered_map& m1_to_m0_cm_ovtx_colored, + /*const*/ std::unordered_map& m1_to_m0_face_colored, + // const std::map &m0_to_ps_vtx, + const std::vector& m0_to_ps_vtx, + /*const*/ std::unordered_map& m0_to_ps_face, + // const std::map &ps_to_sm_vtx, + const std::vector& ps_to_sm_vtx, + // const std::map &ps_to_sm_face, + const std::vector& ps_to_sm_face, + // const std::map &ps_to_cm_vtx, + const std::vector& ps_to_cm_vtx, + // const std::map &ps_to_cm_face, + const std::vector& 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> // "mesh" faces + > + OutputStorageTypesTuple; + typedef std::vector>::const_iterator InputStorageIteratorType; + + auto fn_compute_inserted_faces = [&mesh](InputStorageIteratorType block_start_, InputStorageIteratorType block_end_) -> OutputStorageTypesTuple { + OutputStorageTypesTuple local_output; + std::vector>& 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& mX_traced_polygon = *mX_traced_polygons_iter; + + faces_LOCAL.push_back(std::vector()); + std::vector& polygon_vertices = faces_LOCAL.back(); + polygon_vertices.reserve(mX_traced_polygon.size()); + + // for each halfedge in polygon + for (std::vector::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> 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>& faces_MASTER_THREAD_LOCAL = std::get<0>(partial_res); + + auto merge_local_faces = [&mesh](const std::vector>& faces_) { + for (std::vector>::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& f = futures[i]; + MCUT_ASSERT(f.valid()); + OutputStorageTypesTuple future_result = f.get(); // "get()" is a blocking function + + const std::vector>& 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>::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& mX_traced_polygon = *mX_traced_polygons_iter; + + // + // gather polygon's vertices + // + + std::vector polygon_vertices; + polygon_vertices.reserve(mX_traced_polygon.size()); + + // for each halfedge in polygon + for (std::vector::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> ccID_to_mesh; + // location of each connected component w.r.t cut-mesh (above | below | undefined) + std::map 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> ccID_to_mX_to_cc_vertex; + // std::map> ccID_to_cc_to_mX_vertex; + std::map> ccID_to_cc_to_mX_vertex; + // the vertex descriptors [in the cc] which are seam vertices! + std::map> cc_to_seam_vertices; + // here we create a map to tag each polygon in "mesh" with the connected component it belongs to. + std::vector fccmap; + + TIMESTACK_PUSH("Extract CC: find connected components"); + std::vector cc_to_vertex_count; + std::vector 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>::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>::iterator, bool> p = ccID_to_mesh.insert(std::make_pair(face_cc_id, std::shared_ptr(new hmesh_t))); + ccID_to_mesh_fiter = p.first; + } + + std::shared_ptr cc_mesh = ccID_to_mesh_fiter->second; + + std::map>::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>::iterator, bool> p = ccID_to_mX_to_cc_vertex.insert(std::make_pair(face_cc_id, std::unordered_map())); + ccID_to_mX_to_cc_vertex_fiter = p.first; + } + + std::unordered_map& mX_to_cc_vertex = ccID_to_mX_to_cc_vertex_fiter->second; + + std::map>::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>::iterator, bool> p = ccID_to_cc_to_mX_vertex.insert(std::make_pair(face_cc_id, std::vector())); + ccID_to_cc_to_mX_vertex_fiter = p.first; + } + + std::vector& cc_to_mX_vertex = ccID_to_cc_to_mX_vertex_fiter->second; + + std::map>::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>::iterator, bool> p = cc_to_seam_vertices.insert(std::make_pair(face_cc_id, std::vector())); + cc_to_seam_vertices_fiter = p.first; + } + + std::vector& 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(fd)); + + if (cc_is_below_cs) { + // try to save the fact that the current connected component is "below" + std::pair::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(fd)); + + if (cc_is_above_cs) { + // try to save the fact that the current connected component is tagged "above" + std::pair::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 + // + + // for each vertex around the current face + const std::vector vertices_around_face = mesh.get_vertices_around_face(fd); + + for (std::vector::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::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); + } + } + } + } // 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 ccID_to_keepFlag; + for (std::map>::const_iterator it = ccID_to_mesh.cbegin(); it != ccID_to_mesh.cend(); ++it) { + int ccID = (int)it->first; + std::map::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> ccID_to_cc_to_mX_face; + for (std::map>::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(); + } + + TIMESTACK_PUSH("Extract CC: Map faces"); +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + { + typedef std::tuple< + std::vector>, // remapped_faces, // using remapped cc descriptors + std::vector, // the cc id of each remapped + std::vector // 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>& remapped_faces_LOCAL = std::get<0>(local_output); + remapped_faces_LOCAL.reserve(num_elems); + std::vector& local_remapped_face_to_ccID = std::get<1>(local_output); + local_remapped_face_to_ccID.reserve(num_elems); + std::vector& 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()); + std::vector& 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>::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 &cc_to_mX_face = ccID_to_cc_to_mX_face_fiter->second; + + // std::map>::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& mX_to_cc_vertex = SAFE_ACCESS(ccID_to_mX_to_cc_vertex, cc_id); + + // for each vertex around face + const std::vector vertices_around_face = mesh.get_vertices_around_face(fd); + + for (std::vector::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& 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> 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>& remapped_faces_MASTER_THREAD_LOCAL = std::get<0>(partial_res); + const std::vector& local_remapped_face_to_ccID_MASTER_THREAD_LOCAL = std::get<1>(partial_res); + const std::vector& local_remapped_face_to_mX_face_MASTER_THREAD_LOCAL = std::get<2>(partial_res); + + auto merge_local_remapped_cc_faces = []( + const std::vector>& remapped_faces_, + const std::vector& local_remapped_face_to_ccID_, + const std::vector& local_remapped_face_to_mX_face_, + const bool popuplate_face_maps, + std::map>& ccID_to_mesh, + std::map>& ccID_to_cc_to_mX_face) { + for (std::vector>::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 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& 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& f = futures[i]; + MCUT_ASSERT(f.valid()); + OutputStorageTypesTuple future_result = f.get(); // "get()" is a blocking function + + const std::vector>& remapped_faces_FUTURE = std::get<0>(future_result); + const std::vector& local_remapped_face_to_ccID_FUTURE = std::get<1>(future_result); + const std::vector& 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 remapped_face; // using remapped cc descriptors + std::map>::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& cc_to_mX_face = ccID_to_cc_to_mX_face_fiter->second; + + // std::map>::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& mX_to_cc_vertex = SAFE_ACCESS(ccID_to_mX_to_cc_vertex, cc_id); + + // for each vertex around face + /*const*/ std::vector vertices_around_face = mesh.get_vertices_around_face(fd); + + for (std::vector::/*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& 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 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>::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 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& 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::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::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::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(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& 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(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(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(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(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>& 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>::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>::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>& m0_h_to_ply, + /*const*/ std::unordered_map>& ivtx_to_incoming_hlist, + /*const*/ std::unordered_map& m0_sm_ihe_to_flag, + const std::vector>& m0_ivtx_to_intersection_registry_entry, + /*const*/ std::unordered_map& m0_to_m1_ihe, + // const std::map &m0_to_ps_vtx, + const std::vector& 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& 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 halfedges_across_cut_path = incoming; + + // for each halfedge across the cut-path + for (std::vector::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 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 ps_get_ivtx_registry_entry_faces(const hmesh_t& ps, const std::pair& 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>& m0_ivtx_to_intersection_registry_entry, + std::unordered_map>& ps_iface_to_m0_edge_list, + const std::vector& 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 neighbouring_ifaces; + for (auto neigh_face : { sm_face, cs_face }) { + const std::vector 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& src_vertex_ipair = SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, (std::size_t)src_vertex - ps_vtx_cnt); + const std::vector 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& tgt_vertex_ipair = SAFE_ACCESS(m0_ivtx_to_intersection_registry_entry, (std::size_t)tgt_vertex - ps_vtx_cnt); + const std::vector 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::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>::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>::iterator, bool> p = ps_iface_to_m0_edge_list.insert(std::make_pair(iface, std::vector())); + 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& 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 traced_polygon_t; + +bool mesh_is_closed( +#if 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 linear_projection_sort(const std::vector>& 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>::const_iterator origin = points.cbegin(); + const std::vector>::const_iterator dst = points.cbegin() + 1; + + vec3 orig_to_dst_vec = normalize(origin->second - dst->second); + + std::vector> point_projections; + + for (std::vector>::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& a, const std::pair& b) { + return a.second < b.second; + }); + + std::vector sorted_descriptors; + for (std::vector>::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 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 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 ps_to_sm_vtx; + std::vector 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 ps_to_sm_face; + std::vector 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 cs_to_ps_vtx; + // std::map ps_to_cm_vtx; + // std::vector cs_to_ps_vtx(cs.number_of_vertices()); + std::vector 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 ps_to_cm_face; + std::vector 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>> 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>& p = result[counter++]; + std::vector& remapped_face_vertices = p.second; + cs.get_vertices_around_face(remapped_face_vertices, *i, sm_vtx_cnt); + p.first = *i; + } + + return result; + }; + + std::vector>>>> futures; + std::vector>> 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>>& remapped_faces) { + for (std::vector>>::const_iterator it = remapped_faces.cbegin(); it != remapped_faces.cend(); ++it) { + const std::pair>& 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>> f_res = futures[i].get(); + add_faces(f_res); + } + + add_faces(master_thread_res); + } +#else // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + + std::vector remapped_face_vertices_tmp; + for (face_array_iterator_t i = cs.faces_begin(); i != cs.faces_end(); ++i) { + // std::vector fv = get_vertices_on_face(cs, *i); + + cs.get_vertices_around_face(remapped_face_vertices_tmp, *i, sm_vtx_cnt); + const std::vector& 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 m0_to_ps_vtx; + std::vector m0_to_ps_vtx; // NOTE: only ps vertices are stored here + // std::map ps_to_m0_vtx; + std::vector 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> 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> OutputStorageType; + typedef std::map>::const_iterator InputStorageIteratorType; + + std::vector> futures; + + auto fn_compute_ps_edge_to_faces_map = [&](InputStorageIteratorType block_start_, InputStorageIteratorType block_end_) { + std::unordered_map> 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& halfedges = ps.get_halfedges_around_face(intersecting_edge_face); + + for (std::vector::const_iterator hIter = halfedges.cbegin(); hIter != halfedges.cend(); ++hIter) { + const ed_t edge = ps.edge(*hIter); + std::vector& 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::const_iterator iface_iter = iter->second.cbegin(); + iface_iter != iter->second.cend(); + ++iface_iter) { + std::vector::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& 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::const_iterator j = i->second.cbegin(); j != i->second.cend(); ++j) { + std::vector::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 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>& kv) { return kv.first; }); + + std::vector ps_iface_enqueued(ps.number_of_faces(), false); + + std::vector ps_edge_visited(ps.number_of_edges(), false); + // initially null + std::map>::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>::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>::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>::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::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 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& cur_ps_face_halfedges = ps.get_halfedges_around_face(cc_iface->first); + // all neighbours + const std::vector cur_ps_face_neigh_faces = ps.get_faces_around_face(cc_iface->first, &cur_ps_face_halfedges); + // neighbours [which are intersecting faces] + std::vector cur_ps_face_neigh_ifaces; + cur_ps_face_neigh_ifaces.reserve(cur_ps_face_neigh_faces.size()); + + for (std::vector::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::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>::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& existing_edge_ifaces = ps_edge_face_intersection_pairs[halfedge_edge]; // sorted list + + for (std::vector::const_iterator i = cur_ps_face_ifaces_sorted.cbegin(); i != cur_ps_face_ifaces_sorted.cend(); ++i) { + std::vector::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> ps_edge_face_intersection_pairs; +#endif // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + TIMESTACK_POP(); + + // + // build bounding boxes for each intersecting edge + // + + TIMESTACK_PUSH("Build edge bounding boxes"); + + // http://gamma.cs.unc.edu/RTRI/i3d08_RTRI.pdf + std::unordered_map> ps_edge_to_bbox; + +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + { + typedef std::unordered_map> OutputStorageType; + typedef std::unordered_map>::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>::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& 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> 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& 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>::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& 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>::iterator InputStorageIteratorType; + + auto fn_compute_edgefair_pair_culling = [&](InputStorageIteratorType block_start_, InputStorageIteratorType block_end_) { + for (std::unordered_map>::iterator iedge_iter = block_start_; iedge_iter != block_end_; iedge_iter++) { + const ed_t edge = iedge_iter->first; + const bounding_box_t& edge_bbox = ps_edge_to_bbox[edge]; + std::vector& edge_ifaces = iedge_iter->second; + + for (std::vector::iterator iface_iter = edge_ifaces.begin(); iface_iter != edge_ifaces.end(); /*increment inside loop*/) { + const bounding_box_t* 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> 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& f = futures[i]; + MCUT_ASSERT(f.valid()); + f.wait(); // simply wait for result to be done + } + } +#else + for (std::unordered_map>::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& edge_bbox = ps_edge_to_bbox[edge]; + std::vector& edge_ifaces = iedge_iter->second; + + for (std::vector::iterator iface_iter = edge_ifaces.begin(); iface_iter != edge_ifaces.end(); /*increment inside loop*/) { + const bounding_box_t* 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()); + + TIMESTACK_PUSH("Compute intersecting face properties"); + // compute/extract geometry properties of each tested face + //-------------------------------------------------------- + + std::unordered_map ps_tested_face_to_plane_normal; + std::unordered_map ps_tested_face_to_plane_normal_d_param; + std::unordered_map ps_tested_face_to_plane_normal_max_comp; + std::unordered_map> ps_tested_face_to_vertices; + +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + { + typedef std::tuple< + std::unordered_map, // ps_tested_face_to_plane_normal; + std::unordered_map, // ps_tested_face_to_plane_normal_d_param; + std::unordered_map, // ps_tested_face_to_plane_normal_max_comp; + std::unordered_map> // ps_tested_face_to_vertices; + > + OutputStorageTypesTuple; + typedef std::map>::const_iterator InputStorageIteratorType; + + auto fn_compute_intersecting_face_properties = [&](InputStorageIteratorType block_start_, InputStorageIteratorType block_end_) -> OutputStorageTypesTuple { + OutputStorageTypesTuple output_res; + std::unordered_map& ps_tested_face_to_plane_normal_LOCAL = std::get<0>(output_res); + std::unordered_map& ps_tested_face_to_plane_normal_d_param_LOCAL = std::get<1>(output_res); + std::unordered_map& ps_tested_face_to_plane_normal_max_comp_LOCAL = std::get<2>(output_res); + std::unordered_map>& ps_tested_face_to_vertices_LOCAL = std::get<3>(output_res); + std::vector tested_face_descriptors_tmp; + for (std::map>::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& tested_face_descriptors = tested_face_descriptors_tmp; + std::vector& tested_face_vertices = ps_tested_face_to_vertices_LOCAL[tested_faces_iter->first]; // insert and get reference + + for (std::vector::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()); + } + return output_res; + }; + + std::vector> 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& 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(); + + std::unordered_map& ps_tested_face_to_plane_normal_FUTURE = std::get<0>(future_res); + std::unordered_map& ps_tested_face_to_plane_normal_d_param_FUTURE = std::get<1>(future_res); + std::unordered_map& ps_tested_face_to_plane_normal_max_comp_FUTURE = std::get<2>(future_res); + std::unordered_map>& 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()); + } + + } // 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 tested_face_descriptors_tmp; + for (std::map>::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& tested_face_descriptors = tested_face_descriptors_tmp; + std::vector& tested_face_vertices = ps_tested_face_to_vertices[tested_faces_iter->first]; // insert and get reference + + for (std::vector::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()); + } + } +#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 // 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 // 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 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> 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, // pair of intersecting polygons (source-mesh polygon, cut-mesh polygon) + std::vector // resulting intersection points + > + cutpath_edge_creation_info; + + std::unordered_map< + fd_t, // intersectiong face + std::vector // 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>::const_iterator InputStorageIteratorType; + typedef std::tuple< + std::vector>, // m0_ivtx_to_intersection_registry_entry + std::vector, // cm_border_reentrant_ivtx_list + std::unordered_map>, // ps_intersecting_edges + std::map, std::vector>, // cutpath_edge_creation_info + std::unordered_map>, // ps_iface_to_ivtx_list + bool, // partial_cut_detected + std::vector // list of intersection points computed in a future + > + OutputStorageTypesTuple; + + auto fn_compute_intersection_points = [&]( + std::unordered_map>::const_iterator block_start_, + std::unordered_map>::const_iterator block_end_) -> OutputStorageTypesTuple { + OutputStorageTypesTuple local_output; + + std::vector>& m0_ivtx_to_intersection_registry_entry_LOCAL = std::get<0>(local_output); + std::vector& cm_border_reentrant_ivtx_list_LOCAL = std::get<1>(local_output); + std::unordered_map>& ps_intersecting_edges_LOCAL = std::get<2>(local_output); + std::map, std::vector>& cutpath_edge_creation_info_LOCAL = std::get<3>(local_output); + std::unordered_map>& ps_iface_to_ivtx_list_LOCAL = std::get<4>(local_output); + bool& partial_cut_detected_LOCAL = std::get<5>(local_output); + std::vector& 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>::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& 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::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& 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 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_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::const_iterator intersected_faces_iter = intersected_faces.cbegin(); intersected_faces_iter != intersected_faces.cend(); ++intersected_faces_iter) { + } // for (std::map>::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> 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 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::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>::iterator i = ps_intersecting_edges.begin(); + i != ps_intersecting_edges.end(); ++i) { + for (std::vector::iterator j = i->second.begin(); j != i->second.end(); j++) { + *j += ps_vtx_cnt; + } + } + + for (std::unordered_map>::iterator i = ps_iface_to_ivtx_list.begin(); + i != ps_iface_to_ivtx_list.end(); ++i) { + for (std::vector::iterator j = i->second.begin(); j != i->second.end(); j++) { + *j += ps_vtx_cnt; + } + } + + for (std::map, std::vector>::iterator i = cutpath_edge_creation_info.begin(); + i != cutpath_edge_creation_info.end(); ++i) { + for (std::vector::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& 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>& m0_ivtx_to_intersection_registry_entry_FUTURE = std::get<0>(future_result); + const std::vector& cm_border_reentrant_ivtx_list_FUTURE = std::get<1>(future_result); + const std::unordered_map>& ps_intersecting_edges_FUTURE = std::get<2>(future_result); + const std::map, std::vector>& cutpath_edge_creation_info_FUTURE = std::get<3>(future_result); + const std::unordered_map>& 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& 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::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::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>::const_iterator i = ps_intersecting_edges_FUTURE.cbegin(); + i != ps_intersecting_edges_FUTURE.cend(); + ++i) { + std::unordered_map>::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::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::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, std::vector>::const_iterator i = cutpath_edge_creation_info_FUTURE.cbegin(); + i != cutpath_edge_creation_info_FUTURE.cend(); + ++i) { + std::map, std::vector>::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::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::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>::const_iterator i = ps_iface_to_ivtx_list_FUTURE.cbegin(); + i != ps_iface_to_ivtx_list_FUTURE.cend(); + ++i) { + std::unordered_map>::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::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::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>::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& 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::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 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& 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 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_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 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::const_iterator intersected_faces_iter = intersected_faces.cbegin(); intersected_faces_iter != intersected_faces.cend(); ++intersected_faces_iter) { + + } // for (std::map>::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>::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 // 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 m0_cutpath_edges; + + // A mapping from and ivertex to the incoming halfedges + std::unordered_map< + vd_t, // intersection point + std::vector // list of halfedges whose target is the intersection point + > + ivtx_to_incoming_hlist; +#if 1 // 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, std::vector>::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& 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 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 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, std::vector>::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& 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 sorted_descriptors = linear_projection_sort(ivertex_coords); + + // for (std::vector>::const_iterator iter = ivertex_coords.cbegin() + 1; iter != ivertex_coords.cend(); ++iter) { + for (std::vector::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>::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 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 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 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 shared_faces_containing_edge; + // for each shared face + for (std::vector::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& 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::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> m0_ivtx_to_cutpath_edges; + + for (std::vector::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> m0_cutpath_sequences; + std::unordered_map m0_ivtx_to_cutpath_sequence; + std::unordered_map 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>::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>& 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>& 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()); + std::vector& 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& 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::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& 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::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>::const_iterator> m0_cutpath_terminal_vertices; + + for (std::vector>::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> m0_cutpath_sequence_to_properties; + + for (std::vector>::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& cutpath = *iter; + + MCUT_ASSERT(m0_cutpath_sequence_to_properties.count(cutpath_index) == 0); + m0_cutpath_sequence_to_properties[cutpath_index] = std::tuple(); + + MCUT_ASSERT(m0_cutpath_sequence_to_properties.count(cutpath_index) == 1); + + std::tuple& 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>::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::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 explicit_cutpaths_making_holes; + std::vector explicit_cutpaths_severing_srcmesh; + + for (std::map>::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& 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::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& 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& 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::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>::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> ivtx_to_cp_edges; + + for (std::vector::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>::const_iterator cur = ivtx_to_cp_edges.cend(); + std::unordered_map>::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& evec = SAFE_ACCESS(ivtx_to_cp_edges, cur->first); + std::vector::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 // intersection point which involve the intersecting face + > + ps_iface_to_ivtx_list; // faces which intersect with another + + for (std::map>::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 entry_faces = ps_get_ivtx_registry_entry_faces(ps, ireg_entry_iter->second); // faces in registry entry + + // update face vertex-registry + + for (std::vector::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>::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 &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>::iterator, bool> pair = ps_iface_to_ivtx_list.insert(std::make_pair(*entry_face_iter, std::vector())); + MCUT_ASSERT(pair.second == true); + + find_iter = pair.first; + std::vector &face_vertex_registry = find_iter->second; + face_vertex_registry.push_back(intersection_point); // associate intersection point with intersecting face + } + } + } + + for (std::map>::const_iterator i = ps_iface_to_ivtx_list.cbegin(); i != ps_iface_to_ivtx_list.cend(); ++i) + { + + + + // log + + for (std::vector::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> m0_to_m1_poly_ext_int_edge_vertex; + + std::unordered_map>> ps_edge_to_vertices; // stores ps-edges with more-than 3 coincident vertices + + for (std::unordered_map>::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 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>())); + + for (std::vector::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())); + // } + } + } + } + + // In the next for-loop, we sort each list of vertices on each ps-edge + // which more than 3 coincident vertices + + std::unordered_map> ps_edge_to_sorted_descriptors; // sorted vertex that lie on each edge with > 3 vertices + + for (std::unordered_map>>::iterator edge_vertices_iter = ps_edge_to_vertices.begin(); edge_vertices_iter != ps_edge_to_vertices.end(); ++edge_vertices_iter) { + std::vector>& 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& a, const std::pair& 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& a, const std::pair& 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& a, const std::pair& 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 // 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>::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& 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::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 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, // ps_to_m0_non_intersecting_edge + std::unordered_map>, // ps_iface_to_m0_edge_list + std::unordered_map>, // ivtx_to_incoming_hlist + // locally computed edge + // local-edge-id to the m0-vertex-descriptors that are used to create edge + std::vector>> + OutputStorageTypesTuple; + + auto fn_compute_polygon_boundary_edges = [&]( + InputStorageIteratorType block_start_, + InputStorageIteratorType block_end_) -> OutputStorageTypesTuple { + OutputStorageTypesTuple local_output; + + std::unordered_map& ps_to_m0_non_intersecting_edge_LOCAL = std::get<0>(local_output); + std::unordered_map>& ps_iface_to_m0_edge_list_LOCAL = std::get<1>(local_output); + std::unordered_map>& ivtx_to_incoming_hlist_LOCAL = std::get<2>(local_output); + std::vector>& 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 vertices_on_ps_edge = { ps_v0, ps_v1 }; + std::unordered_map>::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> 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 ps_to_m0_non_intersecting_edge_MASTER_THREAD_LOCAL = std::get<0>(partial_res); + const std::unordered_map>& ps_iface_to_m0_edge_list_MASTER_THREAD_LOCAL = std::get<1>(partial_res); + const std::unordered_map>& 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>& 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& ps_to_m0_non_intersecting_edge_FUTURE, + const std::unordered_map>& ps_iface_to_m0_edge_list_FUTURE, + const std::unordered_map>& ivtx_to_incoming_hlist_FUTURE, + const std::vector>& edge_create_info_FUTURE, + std::unordered_map& ps_to_m0_non_intersecting_edge, + std::unordered_map>& ps_iface_to_m0_edge_list, + std::unordered_map>& ivtx_to_incoming_hlist) { + std::vector emap(edge_create_info_FUTURE.size()); + // add edges using edge_create_info + for (std::vector>::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::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>::const_iterator it = ps_iface_to_m0_edge_list_FUTURE.cbegin(); + it != ps_iface_to_m0_edge_list_FUTURE.cend(); + ++it) { + for (std::vector::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>::const_iterator it = ivtx_to_incoming_hlist_FUTURE.cbegin(); + it != ivtx_to_incoming_hlist_FUTURE.cend(); + ++it) { + for (std::vector::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& f = futures[i]; + MCUT_ASSERT(f.valid()); + OutputStorageTypesTuple future_result = f.get(); // "get()" is a blocking function + + const std::unordered_map& ps_to_m0_non_intersecting_edge_FUTURE = std::get<0>(future_result); + const std::unordered_map>& ps_iface_to_m0_edge_list_FUTURE = std::get<1>(future_result); + const std::unordered_map>& ivtx_to_incoming_hlist_FUTURE = std::get<2>(future_result); + const std::vector>& 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 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>::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 m0_polygons; + + // m0 polygons adjacent to cutpath from source-mesh faces + std::vector m0_sm_cutpath_adjacent_polygons; + // m0 polygons adjacent to cutpath from cut-mesh faces + std::vector m0_cm_cutpath_adjacent_polygons; + + int traced_sm_polygon_count = 0; + + std::unordered_map 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, // m0_polygons; + std::vector, // m0_sm_cutpath_adjacent_polygons + std::vector, // m0_cm_cutpath_adjacent_polygons; + int, // traced_sm_polygon_count + std::unordered_map> // 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& m0_polygons_LOCAL = std::get<0>(local_output); + m0_polygons_LOCAL.reserve(num_elems); + std::vector& 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& 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& 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>::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 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 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::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& ps_iface_m0_edge_list = ps_iface_to_m0_edge_list_fiter->second; + static thread_local std::vector ps_coincident_vertices_tmp; + + ps.get_vertices_around_face(ps_coincident_vertices_tmp, ps_face); + const std::vector& ps_coincident_vertices = ps_coincident_vertices_tmp; + static thread_local std::vector 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>::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& 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 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& 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& 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 incident_halfedges; + hd_t first_boundary_halfedge = hmesh_t::null_halfedge(); + + for (std::vector::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& 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& 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& 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>::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& sorted_m0_edges = ps_to_m0_edges_find_iter->second; + std::vector 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::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::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 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::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& 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& 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::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 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::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 premptive_candidate_halfedges; + for (std::vector::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::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::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 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::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::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::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> 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& m0_polygons_FUTURE, + const std::vector& m0_sm_cutpath_adjacent_polygons_FUTURE, + const std::vector& m0_cm_cutpath_adjacent_polygons_FUTURE, + const int& traced_sm_polygon_count_FUTURE, + const std::unordered_map& m0_to_ps_face_FUTURE, + std::vector& m0_polygons, + std::vector& m0_sm_cutpath_adjacent_polygons, + std::vector& m0_cm_cutpath_adjacent_polygons, + int& traced_sm_polygon_count, + std::unordered_map& 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::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& f = futures[i]; + MCUT_ASSERT(f.valid()); + OutputStorageTypesTuple future_result = f.get(); // "get()" is a blocking function + + const std::vector& m0_polygons_FUTURE = std::get<0>(future_result); + const std::vector& m0_sm_cutpath_adjacent_polygons_FUTURE = std::get<1>(future_result); + const std::vector& 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& 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& m0_polygons_MASTER_THREAD_LOCAL = std::get<0>(partial_res); + const std::vector& m0_sm_cutpath_adjacent_polygons_MASTER_THREAD_LOCAL = std::get<1>(partial_res); + const std::vector& 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& 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>::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 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 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::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& 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 ps_coincident_vertices_tmp; + ps.get_vertices_around_face(ps_coincident_vertices_tmp, ps_face); + const std::vector& ps_coincident_vertices = ps_coincident_vertices_tmp; + static thread_local std::vector 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>::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& 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::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 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& 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& 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::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 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::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& 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& 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& 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>::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& 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 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::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::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 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::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& 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& 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::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 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::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 premptive_candidate_halfedges; + for (std::vector::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::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::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 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::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::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::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::iterator traced_sm_polygons_iter_end = m0_polygons.begin() + traced_sm_polygon_count; + // const std::vector::iterator& traced_cs_polygons_iter_begin = traced_sm_polygons_iter_end; + const std::vector::const_iterator m0_traced_sm_polygons_iter_cend = m0_polygons.cbegin() + traced_sm_polygon_count; + const std::vector::const_iterator& traced_cs_polygons_iter_cbegin = traced_sm_polygons_iter_end; + + TIMESTACK_PUSH("Mark seam edges *"); + // extract the seam vertices + std::vector 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, connected_component_info_t>>> separated_src_mesh_fragments; + std::unordered_map _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(m0_polygons.begin(), traced_sm_polygons_iter_end), + std::vector(), // sm_polygons_below_cs + std::vector(), // sm_polygons_above_cs + m0_vertex_to_seam_flag, + std::vector(), // Unused ... because we are extracting from "m0" + std::unordered_map(), // 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(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, connected_component_info_t>>> separated_cut_mesh_fragments; + std::unordered_map _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_cs_polygons_iter_cbegin, m0_polygons.cend()), + std::vector(), + std::vector(), + m0_vertex_to_seam_flag, + std::vector(), // Unused ... because we are extracting from "m0" + std::unordered_map(), // 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(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 // list of indices of traced polygons that are traced with the halfedge + // > + // m0_h_to_ply; + std::vector> m0_h_to_ply(m0.number_of_halfedges()); + + // for each traced polygon + for (std::vector::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>::iterator, bool> pair = m0_h_to_ply.insert(std::make_pair(traced_polygon_halfedge, std::vector())); + + 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>::const_iterator i = m0_h_to_ply.cbegin(); i != m0_h_to_ply.cend(); ++i) { + + for (std::vector::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 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::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::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::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::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& 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& 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 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& registry_entry_faces = SAFE_ACCESS(m0_ivtx_to_ps_faces, cs_poly_he_tgt); + const std::vector 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::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 sm_polygons_below_cs; + std::vector sm_polygons_above_cs; + + // for each traced source-mesh polygon along cutpath + for (std::vector::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::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::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& 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 &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 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::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& coincident_polys = SAFE_ACCESS(m0_h_to_ply, opp_of_sm_poly_next_he); // coincident polygons (one cs and one sm) + const std::vector::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>::const_iterator ivtx_iter = ivtx_to_incoming_hlist.cbegin(); ivtx_iter != ivtx_to_incoming_hlist.cend(); ++ivtx_iter) { + for (std::vector::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::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 &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::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 &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::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::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 m0_to_m1_vtx(m0.number_of_vertices()); + std::vector 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::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 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::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&)> check_if_halfedge_is_transformed = [&](const std::pair& 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& 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::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 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::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 > >::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 &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 &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>>::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& 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>>::iterator, bool> p = m1_ivtx_to_h.emplace(m1_cur_h_src, std::vector>()); + 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& 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>>::iterator, bool> p = m1_ivtx_to_h.emplace(m1_cur_h_tgt, std::vector>()); + 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& 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 &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 &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>::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::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 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 m0_to_m1_face; // std::map m0_to_m1_face; + std::unordered_map m1_to_m0_face; + + // for each traced polygon (in "m0") + for (std::vector::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 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, 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(), // ... 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, 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(cc_iter->first); + std::vector, 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, connected_component_info_t>& md = mesh_data.front(); + std::shared_ptr omi = std::shared_ptr(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) { + + return; // exit + } + + 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> patch_discovery_seeds; + + // for each cutpath that makes a hole + for (std::vector::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& 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& h_polygons = SAFE_ACCESS(m0_h_to_ply, h); + + // get the cut-mesh polygon using h0 + std::vector::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& h_polygon = SAFE_ACCESS(m0_polygons, h_polygon_idx); + // find the index of h0 in the polygon + std::vector::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> 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 // 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 // list of patches of that color + > + color_to_patch = { { 'A', std::vector() }, { 'B', std::vector() } }; + + // 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 patch_poly_enqueued(m0_polygons.size(), false); + std::queue 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 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>::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& 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::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 &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 &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>::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::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 // 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& 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& 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>::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::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 graph_patch_coloring_queue; + // start coloring with the first patch + graph_patch_coloring_queue.push_back(0); + // "red" chosen arbitrarilly + std::vector& 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& blue_nodes = SAFE_ACCESS(color_to_patch, 'B'); // i.e. blue patches + + for (std::unordered_map>::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 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 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::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::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::const_iterator next_he_poly_iter = current_cs_border_he_poly_find_iter; + std::vector::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::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 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>::const_iterator color_to_ccw_patches_find_iter = std::find_if( + color_to_patch.cbegin(), + color_to_patch.cend(), + [&](const std::pair>& 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> known_exterior_cm_polygons; + const std::unordered_map::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>::const_iterator color_to_ccw_patches_find_iter = std::find_if( + color_to_patch.cbegin(), + color_to_patch.cend(), + [&](const std::pair>& 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::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>::const_iterator known_exterior_cs_polygons_find_iter = std::find_if( + known_exterior_cm_polygons.cbegin(), + known_exterior_cm_polygons.cend(), + [&](const std::pair& 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>::const_iterator color_to_ccw_patches_find_iter = std::find_if( + color_to_patch.cbegin(), + color_to_patch.cend(), + [&](const std::pair>& 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>::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 // 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>::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(); + // std::pair>::iterator, bool> color_to_cw_patch_insertion = color_to_cw_patch.insert(std::make_pair(color_to_ccw_patches_iter->first, std::vector())); + // 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& cw_patch_color = SAFE_ACCESS(color_to_cw_patch, color_to_ccw_patches_iter->first); + + // for each patch with current color + for (std::vector::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& 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(); + // std::pair>::iterator, bool> patch_insertion = patches.insert(std::make_pair(cw_patch_idx, std::vector())); + // MCUT_ASSERT(patch_insertion.second == true); + MCUT_ASSERT(patches.count(cw_patch_idx) == 1); + + std::vector& 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 &coincident_polys = SAFE_ACCESS(m0_h_to_ply, coincident_halfedge_opp); + // find coincident cut-mesh polygon + const std::vector::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>::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>::iterator, bool> m0_he_to_poly_idx_insertion = m0_h_to_ply.insert(std::make_pair(patch_poly_he_opp, std::vector())); + + 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>::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& colored_cw_patches = color_to_cw_patch_iter->second; + // get normal patches + std::vector& 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::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& 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::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>::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::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 patch_mesh = std::shared_ptr(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& patch = SAFE_ACCESS(patches, cur_patch_idx); + + // + // add vertices into patch mesh + // + + std::vector seam_vertices; // vertices along cutpath + + std::unordered_map patch_to_m0_vertex; + patch_to_m0_vertex.reserve(cs_face_count); + + // for each polygon in the patch + for (std::vector::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 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::const_iterator block_start_, std::vector::const_iterator block_end_) { + std::vector>> result; + result.resize(std::distance(block_start_, block_end_)); + + uint32_t counter = 0; + // for each polygon + for (std::vector::const_iterator patch_poly_iter = block_start_; patch_poly_iter != block_end_; ++patch_poly_iter) { + std::vector& 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>>>> futures; + std::vector>> 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>& remapped_poly_info) { + const std::vector& 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>>>& f = futures[i]; + MCUT_ASSERT(f.valid()); + std::vector>> 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>& 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>& remapped_poly_info = partial_res[j]; + add_face_and_save_mapping(remapped_poly_info); + } + +#else // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + std::vector remapped_poly_vertices; // redefined face using "patch_mesh" descriptors + // for each polygon + for (std::vector::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 omi = std::shared_ptr(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(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(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(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(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>::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& colored_cw_patches = color_to_cw_patch_iter->second; + + // for each patch + for (std::vector::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& 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::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& coincident_polys = SAFE_ACCESS(m0_h_to_ply, ccw_patch_seed_interior_ihalfedge_opp); + + std::vector::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::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::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, // actual mesh data structure + connected_component_info_t // information about mesh + >>>> + color_to_separated_connected_ccsponents; + + std::map< + char, // color tag + std::vector // 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 // 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 // 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> + colour_to_m1_to_m0_cm_ovtx; + + std::map< + char, // color value + std::vector // 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> patch_poly_stitching_queue; + + // enough space for cut-mesh polygons + std::vector 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>::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>())); + // ref to entry + std::unordered_map>& 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>::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& 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, connected_component_info_t>>>& separated_stitching_CCs = color_to_separated_connected_ccsponents[color_id]; // insert + + std::unordered_map& 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& 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& 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& 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::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& 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& 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>::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::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& 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::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& 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& 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& m0_poly_he_coincident_polys = m0_h_to_ply[m0_cs_next_patch_polygon_he]; + const std::vector::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>::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::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::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& m0_incoming_halfedges = m0.get_halfedges_around_vertex(m0_cur_patch_cur_poly_cur_he_tgt); + + for (std::vector::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::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::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>> + + // 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>::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>::iterator, bool> pair = m0_to_m1_he_instances.insert(std::make_pair(m0_cur_patch_cur_poly_cur_he, std::map())); + + 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& patch_to_m1_he = m0_to_m1_he_instances_find_iter->second; + // const std::map::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& 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> 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& 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& 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>::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& patch_to_m1_he = m0_to_m1_he_instances_find_iter->second; + std::map::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& 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 m0_poly_he_opp_coincident_polys = SAFE_ACCESS(m0_h_to_ply, m0_cur_patch_cur_poly_cur_he_opp); + const std::vector::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& 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::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 &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& 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, 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, 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& 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& 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& 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& 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>>>& out = output.connected_components; + int idx = 0; + for (std::map, 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, connected_component_info_t>>>& separated_sealed_CCs = color_to_separated_CCs_iter->second; + + for (std::map, 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, 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, connected_component_info_t>>::iterator cc_instance_iter = cc_instances.begin(); + cc_instance_iter != cc_instances.end(); + ++cc_instance_iter) { + + std::pair, 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 omi = std::shared_ptr(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 diff --git a/src/mcut/source/math.cpp b/src/mcut/source/math.cpp new file mode 100644 index 0000000000..8fb42268ac --- /dev/null +++ b/src/mcut/source/math.cpp @@ -0,0 +1,903 @@ +/** + * 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: + * (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 "mcut/internal/math.h" +#include // std::sort +#include +#include // std::make_tuple std::get<> + + + double square_root(const double& number) + { +#if !defined(MCUT_WITH_ARBITRARY_PRECISION_NUMBERS) + return std::sqrt(number); +#else + arbitrary_precision_number_t out(number); + mpfr_sqrt(out.get_mpfr_handle(), number.get_mpfr_handle(), arbitrary_precision_number_t::get_default_rounding_mode()); + return out; +#endif // #if !defined(MCUT_WITH_ARBITRARY_PRECISION_NUMBERS) + } + + double absolute_value(const double& number) + { +#if !defined(MCUT_WITH_ARBITRARY_PRECISION_NUMBERS) + return std::fabs(number); +#else + double out(number); + mpfr_abs(out.get_mpfr_handle(), number.get_mpfr_handle(), arbitrary_precision_number_t::get_default_rounding_mode()); + return out; +#endif // #if defined(MCUT_WITH_ARBITRARY_PRECISION_NUMBERS) + } + + sign_t sign(const double& number) + { +#if !defined(MCUT_WITH_ARBITRARY_PRECISION_NUMBERS) + int s = (double(0) < number) - (number < double(0)); + sign_t result = sign_t::ZERO; + if (s > 0) { + result = sign_t::POSITIVE; + } else if (s < 0) { + result = sign_t::NEGATIVE; + } + return result; +#else + double out(number); + int s = mpfr_sgn(number.get_mpfr_handle()); + sign_t result = sign_t::ZERO; + if (s > 0) { + result = sign_t::POSITIVE; + } else if (s < 0) { + result = sign_t::NEGATIVE; + } + return result; +#endif // #if defined(MCUT_WITH_ARBITRARY_PRECISION_NUMBERS) + } + + std::ostream& operator<<(std::ostream& os, const vec3& v) + { + return os << static_cast(v.x()) << ", " << static_cast(v.y()) << ", " << static_cast(v.z()); + } + + bool operator==(const vec3& a, const vec3& b) + { + return (a.x() == b.x()) && (a.y() == b.y()) && (a.z() == b.z()); + } + + vec3 cross_product(const vec3& a, const vec3& b) + { + return vec3( + a.y() * b.z() - a.z() * b.y(), + a.z() * b.x() - a.x() * b.z(), + a.x() * b.y() - a.y() * b.x()); + } + + double orient2d(const vec2& pa, const vec2& pb, const vec2& pc) + { + const double pa_[2] = { static_cast(pa.x()), static_cast(pa.y()) }; + const double pb_[2] = { static_cast(pb.x()), static_cast(pb.y()) }; + const double pc_[2] = { static_cast(pc.x()), static_cast(pc.y()) }; + + return ::orient2d(pa_, pb_, pc_); // shewchuk predicate + } + + double orient3d(const vec3& pa, const vec3& pb, const vec3& pc, + const vec3& pd) + { + const double pa_[3] = { static_cast(pa.x()), static_cast(pa.y()), static_cast(pa.z()) }; + const double pb_[3] = { static_cast(pb.x()), static_cast(pb.y()), static_cast(pb.z()) }; + const double pc_[3] = { static_cast(pc.x()), static_cast(pc.y()), static_cast(pc.z()) }; + const double pd_[3] = { static_cast(pd.x()), static_cast(pd.y()), static_cast(pd.z()) }; + + return ::orient3d(pa_, pb_, pc_, pd_); // shewchuk predicate + } + +#if 0 + void polygon_normal(vec3& normal, const vec3* vertices, const int num_vertices) + { + normal = vec3(0.0); + for (int i = 0; i < num_vertices; ++i) { + normal = normal + cross_product(vertices[i] - vertices[0], vertices[(i + 1) % num_vertices] - vertices[0]); + } + normal = normalize(normal); + } +#endif + + int compute_polygon_plane_coefficients(vec3& normal, double& d_coeff, + const vec3* polygon_vertices, const int polygon_vertex_count) + { + // compute polygon normal (http://cs.haifa.ac.il/~gordon/plane.pdf) + normal = vec3(0.0); + for (int i = 0; i < polygon_vertex_count - 1; ++i) { + normal = normal + cross_product(polygon_vertices[i] - polygon_vertices[0], polygon_vertices[(i + 1) % polygon_vertex_count] - polygon_vertices[0]); + } + + // In our calculations we need the normal be be of unit length so that the d-coeff + // represents the distance of the plane from the origin + // normal = normalize(normal); + + d_coeff = dot_product(polygon_vertices[0], normal); + + double largest_component(0.0); + double tmp(0.0); + int largest_component_idx = 0; + + for (int i = 0; i < 3; ++i) { + tmp = absolute_value(normal[i]); + if (tmp > largest_component) { + largest_component = tmp; + largest_component_idx = i; + } + } + + return largest_component_idx; + } + + // Intersect a line segment with a plane + // + // Return values: + // 'p': The segment lies wholly within the plane. + // 'q': The(first) q endpoint is on the plane (but not 'p'). + // 'r' : The(second) r endpoint is on the plane (but not 'p'). + // '0' : The segment lies strictly to one side or the other of the plane. + // '1': The segment intersects the plane, and none of {p, q, r} hold. + char compute_segment_plane_intersection(vec3& p, const vec3& normal, const double& d_coeff, + const vec3& q, const vec3& r) + { + + // vec3 planeNormal; + // double planeDCoeff; + // planeNormalLargestComponent = compute_polygon_plane_coefficients(planeNormal, planeDCoeff, polygonVertices, + // polygonVertexCount); + + double num = d_coeff - dot_product(q, normal); + const vec3 rq = (r - q); + double denom = dot_product(rq, normal); + + if (denom == double(0.0) /* Segment is parallel to plane.*/) { + if (num == double(0.0)) { // 'q' is on plane. + return 'p'; // The segment lies wholly within the plane + } else { + return '0'; + } + } + + double t = num / denom; + + for (int i = 0; i < 3; ++i) { + p[i] = q[i] + t * (r[i] - q[i]); + } + + if ((double(0.0) < t) && (t < double(1.0))) { + return '1'; // The segment intersects the plane, and none of {p, q, r} hold + } else if (num == double(0.0)) // t==0 + { + return 'q'; // The (first) q endpoint is on the plane (but not 'p'). + } else if (num == denom) // t==1 + { + return 'r'; // The (second) r endpoint is on the plane (but not 'p'). + } else { + return '0'; // The segment lies strictly to one side or the other of the plane + } + } + + bool determine_three_noncollinear_vertices(int& i, int& j, int& k, const std::vector& polygon_vertices, + + const vec3& polygon_normal, const int polygon_normal_largest_component) + { + const int polygon_vertex_count = (int)polygon_vertices.size(); + MCUT_ASSERT(polygon_vertex_count >= 3); + + std::vector x; + project_to_2d(x, polygon_vertices, polygon_normal, polygon_normal_largest_component); + MCUT_ASSERT(x.size() == (size_t)polygon_vertex_count); + + /* + NOTE: We cannot just use _any_/the first result of "colinear(x[i], x[j], x[k])" which returns true since + any three points that are _nearly_ colinear (in the limit of floating point precision) + will be found to be not colinear when using the exact predicate "orient2d". + This would have implications "further down the line", where e.g. the three nearly colinear points + are determined to be non-colinear (in the exact sense) but using them to evaluate + operations like segment-plane intersection would then give a false positive. + To overcome this, must find the three vertices of the polygon, which maximise non-colinearity. + + NOTE: if the polygon has 3 vertices and they are indeed nearly colinear then answer is determined by the + exact predicate (i.e. i j k = 0 1 2) + */ + + // get any three vertices that are not collinear + i = 0; + j = i + 1; + k = j + 1; + std::vector> non_colinear_triplets; + + for (; i < polygon_vertex_count; ++i) { + for (; j < polygon_vertex_count; ++j) { + for (; k < polygon_vertex_count; ++k) { + double predRes; + if (!collinear(x[i], x[j], x[k], predRes)) { + non_colinear_triplets.emplace_back(std::make_tuple(i, j, k, predRes)); + } + } + } + } + + std::sort(non_colinear_triplets.begin(), non_colinear_triplets.end(), + [](const std::tuple& a, const std::tuple& b) { + return std::fabs(std::get<3>(a)) > std::fabs(std::get<3>(b)); + }); + + std::tuple best_triplet = non_colinear_triplets.front(); // maximising non-colinearity + + i = std::get<0>(best_triplet); + j = std::get<1>(best_triplet); + k = std::get<2>(best_triplet); + + return !non_colinear_triplets.empty(); // need at least one non-colinear triplet + } + + char compute_segment_plane_intersection_type(const vec3& q, const vec3& r, + const std::vector& polygon_vertices, + const vec3& polygon_normal, + const int polygon_normal_largest_component) + { + // TODO: we could also return i,j and k so that "determine_three_noncollinear_vertices" is not called multiple times, + // which we do to determine the type of intersection and the actual intersection point + const int polygon_vertex_count = (int)polygon_vertices.size(); + // ... any three vertices that are not collinear + int i = 0; + int j = 1; + int k = 2; + if (polygon_vertex_count > 3) { // case where we'd have the possibility of noncollinearity + bool b = determine_three_noncollinear_vertices(i, j, k, polygon_vertices, polygon_normal, + polygon_normal_largest_component); + + if (!b) { + return '0'; // all polygon points are collinear + } + } + + double qRes = orient3d(polygon_vertices[i], polygon_vertices[j], polygon_vertices[k], q); + double rRes = orient3d(polygon_vertices[i], polygon_vertices[j], polygon_vertices[k], r); + + if (qRes == double(0.0) && rRes == double(0.0)) { + return 'p'; + } else if (qRes == double(0.0)) { + return 'q'; + } else if (rRes == double(0.0)) { + return 'r'; + } else if ((rRes < double(0.0) && qRes < double(0.0)) || (rRes > double(0.0) && qRes > double(0.0))) { + return '0'; + } else { + return '1'; + } + } + + char compute_segment_line_plane_intersection_type(const vec3& q, const vec3& r, + const std::vector& polygon_vertices, + + const int polygon_normal_max_comp, + const vec3& polygon_plane_normal) + { + const int polygon_vertex_count = (int)polygon_vertices.size(); + // ... any three vertices that are not collinear + int i = 0; + int j = 1; + int k = 2; + if (polygon_vertex_count > 3) { // case where we'd have the possibility of noncollinearity + bool b = determine_three_noncollinear_vertices(i, j, k, polygon_vertices, polygon_plane_normal, + polygon_normal_max_comp); + + if (!b) { + return '0'; // all polygon points are collinear + } + } + + double qRes = orient3d(polygon_vertices[i], polygon_vertices[j], polygon_vertices[k], q); + double rRes = orient3d(polygon_vertices[i], polygon_vertices[j], polygon_vertices[k], r); + + if (qRes == double(0.0) && rRes == double(0.0)) { + // both points used to define line lie on plane therefore we have an in-plane intersection + // or the polygon is a degenerate triangle + return 'p'; + } else { + if ((rRes < double(0.0) && qRes < double(0.0)) || (rRes > double(0.0) && qRes > double(0.0))) { // both points used to define line lie on same side of plane + // check if line is parallel to plane + // const double num = polygon_plane_d_coeff - dot_product(q, polygon_plane_normal); + const vec3 rq = (r - q); + const double denom = dot_product(rq, polygon_plane_normal); + + if (denom == double(0.0) /* Segment is parallel to plane.*/) { + // MCUT_ASSERT(num != 0.0); // implies 'q' is on plane (see: "compute_segment_plane_intersection(...)") + // but we have already established that q and r are on same side. + return '0'; + } + } + } + + // q and r are on difference sides of the plane, therefore we have an intersection + return '1'; + } + + // Compute the intersection point between a line (not a segment) and a plane defined by a polygon. + // + // Parameters: + // 'p' : output intersection point (computed if line does indeed intersect the plane) + // 'q' : first point defining your line + // 'r' : second point defining your line + // 'polygon_vertices' : the vertices of the polygon defineing the plane (assumed to not be degenerate) + // 'polygon_vertex_count' : number of olygon vertices + // 'polygon_normal_max_comp' : largest component of polygon normal. + // 'polygon_plane_normal' : normal of the given polygon + // 'polygon_plane_d_coeff' : the distance coefficient of the plane equation corresponding to the polygon's plane + // + // Return values: + // '0': line is parallel to plane (or polygon is degenerate ... within available precision) + // '1': an intersection exists. + // 'p': q and r lie in the plane (technically they are parallel to the plane too but we need to report this because it + // violates GP). + char compute_line_plane_intersection(vec3& p, // intersection point + const vec3& q, const vec3& r, + const std::vector& polygon_vertices, const int polygon_normal_max_comp, + const vec3& polygon_plane_normal, + const double& polygon_plane_d_coeff) + { + + const int polygon_vertex_count = (int)polygon_vertices.size(); + // ... any three vertices that are not collinear + int i = 0; + int j = 1; + int k = 2; + if (polygon_vertex_count > 3) { // case where we'd have the possibility of noncollinearity + bool b = determine_three_noncollinear_vertices(i, j, k, polygon_vertices, polygon_plane_normal, + polygon_normal_max_comp); + + if (!b) { + return '0'; // all polygon points are collinear + } + } + + double qRes = orient3d(polygon_vertices[i], polygon_vertices[j], polygon_vertices[k], q); + double rRes = orient3d(polygon_vertices[i], polygon_vertices[j], polygon_vertices[k], r); + + if (qRes == double(0.) && rRes == double(0.)) { + return 'p'; // both points used to define line lie on plane therefore we have an in-plane intersection + } else { + + const double num = polygon_plane_d_coeff - dot_product(q, polygon_plane_normal); + const vec3 rq = (r - q); + const double denom = dot_product(rq, polygon_plane_normal); + + if ((rRes < double(0.) && qRes < double(0.)) || (rRes > double(0.) && qRes > double(0.))) { // both q an r are on same side of plane + if (denom == double(0.) /* line is parallel to plane.*/) { + return '0'; + } + } + + // q and r are on difference sides of the plane, therefore we have an intersection + + // compute the intersection point + const double t = num / denom; + + for (int it = 0; it < 3; ++it) { + p[it] = q[it] + t * (r[it] - q[it]); + } + + return '1'; + } + } + + // Count the number ray crossings to determine if a point 'q' lies inside or outside a given polygon. + // + // Return values: + // 'i': q is strictly interior + // 'o': q is strictly exterior (outside). + // 'e': q is on an edge, but not an endpoint. + // 'v': q is a vertex. + char compute_point_in_polygon_test(const vec2& q, const std::vector& polygon_vertices) + { + const int polygon_vertex_count = (int)polygon_vertices.size(); + +#if 0 // Algorithm 1 in : + // https://pdf.sciencedirectassets.com/271512/1-s2.0-S0925772100X00545/1-s2.0-S0925772101000128/main.pdf?X-Amz-Security-Token=IQoJb3JpZ2luX2VjEAEaCXVzLWVhc3QtMSJHMEUCIBr6Fu%2F%2FtscErn%2Fl4pn2UGNA45sAw9vggQscK7Tnl0ssAiEAzfnyy4B4%2BXkZp8xcEk7utDBrxgmGyH7pyqk0efKOUEoq%2BgMIKhAEGgwwNTkwMDM1NDY4NjUiDHQT4kOfk4%2B6kBB0xCrXAzd8SWlnELJbM5l93HSvv4nUgtO85%2FGyx5%2BOoHmYoUuVwJjCXChmLJmlz%2BxYUQE%2Fr8vQa2hPlUEPfiTVGgoHaK8NkSMP6LRs%2F3WjyZ9OxzHbqSwZixNceW34OAkq0E1QgYLdGVjpPKxNK1haXDfBTMN6bF2IOU9dKi9wTv3uTlep0kHDa1BNNl6M6yZk5QlF2bPF9XmNjjZCpFQLhr%2BPoHo%2Bx4xy39aH8hCkkTqGdy2KrKGN6lv0%2FduIaItyZfqalYS%2BwW6cll2F5G11g0tSu7yKu6mug94LUTzsRmzD0UhzeGl2WV6Ev2qhw26mwFEKgnTMqGst8XAHjFjjAQyMzXNHCQqNBRIevHIzVWuUY4QZMylSRsodo0dfwwCFhzl0%2BJA1ZXb0%2BoyB8c11meQBO8FpMSshngNcbiAYUtIOFcHNCTCUMk0JPOZ%2FxvANsopnivbrPARL71zU4PaTujg5jfC2zoO6ZDUL8E6Vn%2BNtfb%2BuQV7DwtIH51Bv%2F%2F1m6h5mjbpRiGYcH%2F5SDD%2Fk%2BpHfKRQzKu7jJc%2B0XO0bQvoLSrAte0Qk10PwOvDl5jMIMdmxTBDDiDGErRykYxMQhq5EwjyiWPXzM3ll9uK59Vy0bAEj5Qemj5by1jCDht6IBjqlAV4okAPQO5wWdWojCYeKvluKfXCvudrUxrLhsxb7%2BTZNMODTG%2Fn%2Fbw875Yr6fOQ42u40pOsnPB%2FTP3cWwjiB%2BEHzDqN8AhCVQyoedw7QrU3OBWlSl6lB%2BGLAVqrhcizgFUiM2nj3HaVP2m7S%2FXqpv%2FoWlEXt4gR8iI9XsIlh6L6SBE22FqbsU5ewCxXaqip19VXhAGnlvjTihXUg6yZGWhExHj%2BKcA%3D%3D&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20210814T103958Z&X-Amz-SignedHeaders=host&X-Amz-Expires=300&X-Amz-Credential=ASIAQ3PHCVTY3PPQSW2L%2F20210814%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=94403dce5c12ede9507e9612c000db5772607e59a2134d30d4e5b44212056dc7&hash=cada083b165f9786e6042e21d3d22147ec08b1e2c8fa2572ceeb2445cb5e730a&host=68042c943591013ac2b2430a89b270f6af2c76d8dfd086a07176afe7c76c2c61&pii=S0925772101000128&tid=spdf-f9e7c4b7-808d-4a8e-a474-6430ff687798&sid=8dddff5b32fff2499908bf6501965aec3d0fgxrqb&type=client + + const double zero(0.0); + int i = 0; + int k = 0; + + double f = zero; + double u1 = 0.0; + double v1 = 0.0; + double u2 = 0.0; + double v2 = 0.0; + const int n = polygon_vertex_count; + for (i = 0; i < n; ++i) + { + v1 = polygon_vertices[i].y() - q.y(); + v2 = polygon_vertices[(i + 1) % n].y() - q.y(); + + if ((v1 < zero && v2 < zero) || (v1 > zero && v2 > zero)) + { + continue; + } + + u1 = polygon_vertices[i].x() - q.x(); + u2 = polygon_vertices[(i + 1) % n].x() - q.x(); + + if (v2 > zero && v1 <= zero) + { + f = u1 * v2 - u2 * v1; + if (f > zero) + { + k = k + 1; + } + else if (f == zero) + { + return 'e'; //-1; // boundary + } + } + else if (v1 > zero && v2 <= zero) + { + f = u1 * v2 - u2 * v1; + if (f < zero) + { + k = k + 1; + } + else if (f == zero) + { + return 'e'; //-1; // boundary + } + } + else if (v2 == zero && v1 < zero) + { + f = u1 * v2 - u2 * v1; + if (f == zero) + { + return 'e'; //-1; // // boundary + } + } + else if (v1 == zero && v2 < zero) + { + f = u1 * v2 - u2 * v1; + if (f == zero) + { + return 'e'; //-1; // boundary + } + } + else if (v1 == zero && v2 == zero) + { + if (u2 <= zero && u1 >= zero) + { + return 'e'; //-1; // boundary + } + else if (u1 <= zero && u2 >= zero) + { + return 'e'; //-1; // boundary + } + } + } + if (k % 2 == 0) + { + return 'o'; //0; // outside + } + else + { + return 'i'; //1; // inside + } +#else // http://www.science.smith.edu/~jorourke/books/compgeom.html + + std::vector vertices(polygon_vertex_count, vec2()); + // Shift so that q is the origin. Note this destroys the polygon. + // This is done for pedagogical clarity. + for (int i = 0; i < polygon_vertex_count; i++) { + for (int d = 0; d < 2; d++) { + const double& a = polygon_vertices[i][d]; + const double& b = q[d]; + double& c = vertices[i][d]; + c = a - b; + } + } + + int Rcross = 0; /* number of right edge/ray crossings */ + int Lcross = 0; /* number ofleft edge/ray crossings */ + + /* For each edge e = (i�lj), see if crosses ray. */ + for (int i = 0; i < polygon_vertex_count; i++) { + + /* First check if q = (0, 0) is a vertex. */ + if (vertices[i].x() == double(0.) && vertices[i].y() == double(0.)) { + return 'v'; + } + + int il = (i + polygon_vertex_count - 1) % polygon_vertex_count; + + // Check if e straddles x axis, with bias above/below. + + // Rstrad is TRUE iff one endpoint of e is strictly above the x axis and the other is not (i.e., the other is on + // or below) + bool Rstrad = (vertices[i].y() > double(0.)) != (vertices[il].y() > double(0.)); + bool Lstrad = (vertices[i].y() < double(0.)) != (vertices[il].y() < double(0.)); + + if (Rstrad || Lstrad) { + /* Compute intersection of e with x axis. */ + + // The computation of x is needed whenever either of these straddle variables is TRUE, which + // only excludes edges passing through q = (0, 0) (and incidentally protects against division by 0). + double x = (vertices[i].x() * vertices[il].y() - vertices[il].x() * vertices[i].y()) / (vertices[il].y() - vertices[i].y()); + if (Rstrad && x > double(0.)) { + Rcross++; + } + if (Lstrad && x < double(0.)) { + Lcross++; + } + } /* end straddle computation*/ + } // end for + + /* q on an edge if L/Rcross counts are not the same parity.*/ + if ((Rcross % 2) != (Lcross % 2)) { + return 'e'; + } + + /* q inside iff an odd number of crossings. */ + if ((Rcross % 2) == 1) { + return 'i'; + } else { + return 'o'; + } +#endif + } + + // given a normal vector (not necessarily normalized) and its largest component, calculate + // a matrix P that will project any 3D vertex to 2D by removing the 3D component that + // corresponds to the largest component of the normal.. + // https://math.stackexchange.com/questions/180418/calculate-rotation-matrix-to-align-vector-a-to-vector-b-in-3d/2672702#2672702 + matrix_t calculate_projection_matrix(const vec3& polygon_normal, + const int polygon_normal_largest_component) + { + MCUT_ASSERT(squared_length(polygon_normal) > double(0.0)); + MCUT_ASSERT(polygon_normal_largest_component >= 0); + MCUT_ASSERT(polygon_normal_largest_component <= 2); + + // unit length normal vector of polygon + const vec3 a = normalize(polygon_normal); + // unit length basis vector corresponding to the largest component of the normal vector + const vec3 b = [&]() { + vec3 x(double(0.0)); + const sign_t s = sign(polygon_normal[polygon_normal_largest_component]); + MCUT_ASSERT(s != sign_t::ZERO); // implies that the normal vector has a magnitude of zero + // The largest component of the normal is the one we will "remove" + // NOTE: we multiple by the sign here to ensure that "a_plus_b" below is not zero when a == b + x[polygon_normal_largest_component] = double((1.0) * static_cast(s)); + return x; + }(); + + matrix_t I(3, 3); // 3x3 identity + I(0, 0) = 1.0; + I(1, 1) = 1.0; + I(2, 2) = 1.0; + + matrix_t R = I; + + // NOTE: While this will map vectors a to b, it can add a lot of unnecessary "twist". + // For example, if a=b=e_{z} this formula will produce a 180-degree rotation about the z-axis rather + // than the identity one might expect. + if ((a[0] != b[0]) || (a[1] != b[1]) || (a[2] != b[2])) // a != b + { + const vec3 a_plus_b = a + b; + const double a_dot_b = dot_product(a, b); + + // this will never be zero because we set 'b' as the canonical basis vector + // that has the largest projection onto the normal + MCUT_ASSERT(a_dot_b != double(0.0)); + + const matrix_t outer = outer_product(a_plus_b, a_plus_b); + + // compute the 3x3 rotation matrix R to orient the polygon into the canonical axes "i" and "j", + // where "i" and "j" != "polygon_normal_largest_component" + R = ((outer / a_dot_b) * 2.0) - I; // rotation + } + + // compute the 2x3 selector matrix K to select the polygon-vertex components that correspond + // to canonical axes "i" and "j". + matrix_t K = matrix_t(2, 3); + + if (polygon_normal_largest_component == 0) // project by removing x-component + { + // 1st row + K(0, 0) = 0.0; // col 0 + K(0, 1) = 1.0; // col 1 + K(0, 2) = 0.0; // col 2 + // 2nd row + K(1, 0) = 0.0; // col 0 + K(1, 1) = 0.0; // col 1 + K(1, 2) = 1.0; // col 2 + } else if (polygon_normal_largest_component == 1) // project by removing y-component + { + // 1st row + K(0, 0) = 1.0; // col 0 + K(0, 1) = 0.0; // col 1 + K(0, 2) = 0.0; // col 2 + // 2nd row + K(1, 0) = 0.0; // col 0 + K(1, 1) = 0.0; // col 1 + K(1, 2) = 1.0; // col 2 + } else if (polygon_normal_largest_component == 2) // project by removing z-component + { + // 1st row + K(0, 0) = 1.0; // col 0 + K(0, 1) = 0.0; // col 1 + K(0, 2) = 0.0; // col 2 + // 2nd row + K(1, 0) = 0.0; // col 0 + K(1, 1) = 1.0; // col 1 + K(1, 2) = 0.0; // col 2 + } + + return K * R; + } + + // https://answers.unity.com/questions/1522620/converting-a-3d-polygon-into-a-2d-polygon.html + void project_to_2d(std::vector& out, const std::vector& polygon_vertices, const vec3& polygon_normal) + { + const uint32_t N = polygon_vertices.size(); + out.resize(N); + + const vec3 normal = normalize(polygon_normal); + + // first unit vector on plane (rotation by 90 degrees) + vec3 u(-normal.y(), normal.x(), normal.z()); + vec3 v = normalize(cross_product(u, normal)); + + for(uint32_t i =0; i < N; ++i) + { + const vec3 point = polygon_vertices[i]; + const vec2 projected( + dot_product(point, u), + dot_product(point, v) + ); + + out[i] = projected; + } + } + + void project_to_2d(std::vector& out, const std::vector& polygon_vertices, + const vec3& polygon_normal, const int polygon_normal_largest_component) + { + const int polygon_vertex_count = (int)polygon_vertices.size(); + out.clear(); + out.resize(polygon_vertex_count); + +#if 1 + // 3x3 matrix for projecting a point to 2D + matrix_t P = calculate_projection_matrix(polygon_normal, polygon_normal_largest_component); + for (int i = 0; i < polygon_vertex_count; ++i) { // for each vertex + const vec3& x = polygon_vertices[i]; + out[i] = P * x; // vertex in xz plane + } +#else // This code is not reliable because it shadow-projects a polygons which can skew computations + for (int i = 0; i < polygon_vertex_count; ++i) { // for each vertex + vec2& Tp = out[i]; + int k = 0; + for (int j = 0; j < 3; j++) { // for each component + if (j != polygon_plane_normal_largest_component) { /* skip largest coordinate */ + + Tp[k] = polygon_vertices[i][j]; + k++; + } + } + } +#endif + } + + // TODO: update this function to use "project_to_2d" for projection step + char compute_point_in_polygon_test(const vec3& p, const std::vector& polygon_vertices, + const vec3& polygon_normal, const int polygon_normal_largest_component) + { + const int polygon_vertex_count = (int)polygon_vertices.size(); + /* Project out coordinate m in both p and the triangular face */ + vec2 pp; /*projected p */ +#if 0 + int k = 0; + + for (int j = 0; j < 3; j++) + { // for each component + if (j != polygon_plane_normal_largest_component) + { /* skip largest coordinate */ + pp[k] = p[j]; + k++; + } + } +#endif + + const matrix_t P = calculate_projection_matrix(polygon_normal, polygon_normal_largest_component); + + pp = P * p; + + std::vector polygon_vertices2d(polygon_vertex_count, vec2()); + + for (int i = 0; i < polygon_vertex_count; ++i) { // for each vertex + const vec3& x = polygon_vertices[i]; + polygon_vertices2d[i] = P * x; // vertex in xz plane + } + +#if 0 + for (int i = 0; i < polygon_vertex_count; ++i) + { // for each vertex + vec2 &Tp = polygon_vertices2d[i]; + k = 0; + for (int j = 0; j < 3; j++) + { // for each component + if (j != polygon_plane_normal_largest_component) + { /* skip largest coordinate */ + + Tp[k] = polygon_vertices[i][j]; + k++; + } + } + } +#endif + + return compute_point_in_polygon_test(pp, polygon_vertices2d); + } + + inline bool Between(const vec2& a, const vec2& b, const vec2& c) + { + vec2 ba, ca; + /* If ab not vertical check betweenness on x; else on y. */ + if (a[0] != b[0]) + return ((a[0] <= c[0]) && (c[0] <= b[0])) || // + ((a[0] >= c[0]) && (c[0] >= b[0])); + else + return ((a[1] <= c[1]) && (c[1] <= b[1])) || // + ((a[1] >= c[1]) && (c[1] >= b[1])); + } + + bool coplaner(const vec3& pa, const vec3& pb, const vec3& pc, + const vec3& pd) + { + const double val = orient3d(pa, pb, pc, pd); + // typedef std::numeric_limits dbl; + + // double d = 3.14159265358979; + // std::cout.precision(dbl::max_digits10); + // std::cout << "value=" << (double)val << std::endl; + + // NOTE: thresholds are chosen based on benchmark meshes that are used for testing. + // It is extremely difficult to get this right because of intermediate conversions + // between exact and fixed precision representations during cutting. + // Thus, general advise is for user to ensure that the input polygons are really + // co-planer. It might be possible for MCUT to help here (see eps used during poly + // partitioning). + return absolute_value(val) <= double(4e-7); + } + + bool collinear(const vec2& a, const vec2& b, const vec2& c, double& predResult) + { + predResult = orient2d(a, b, c); + return predResult == double(0.); + } + + bool collinear(const vec2& a, const vec2& b, const vec2& c) + { + return orient2d(a, b, c) == double(0.); + } + + char Parallellnt(const vec2& a, const vec2& b, const vec2& c, const vec2& d, vec2& p) + { + if (!collinear(a, b, c)) { + return '0'; + } + + if (Between(a, b, c)) { + p = c; + return 'e'; + } + + if (Between(a, b, d)) { + p = d; + return 'e'; + } + + if (Between(c, d, a)) { + p = a; + return 'e'; + } + + if (Between(c, d, b)) { + p = b; + return 'e'; + } + + return '0'; + } + + char compute_segment_intersection( + const vec2& a, const vec2& b, const vec2& c, + const vec2& d, vec2& p, double& s, double& t) + { + // double s, t; /* The two parameters of the parametric eqns. */ + double num, denom; /* Numerator and denominator of equations. */ + char code = '?'; /* Return char characterizing intersection.*/ + + denom = a[0] * (d[1] - c[1]) + // + b[0] * (c[1] - d[1]) + // + d[0] * (b[1] - a[1]) + // + c[0] * (a[1] - b[1]); + + /* If denom is zero, then segments are parallel: handle separately. */ + if (denom == double(0.0)) { + return Parallellnt(a, b, c, d, p); + } + + num = a[0] * (d[1] - c[1]) + // + c[0] * (a[1] - d[1]) + // + d[0] * (c[1] - a[1]); + + if ((num == double(0.0)) || (num == denom)) { + code = 'v'; + } + + s = num / denom; + + num = -(a[0] * (c[1] - b[1]) + // + b[0] * (a[1] - c[1]) + // + c[0] * (b[1] - a[1])); + + if ((num == double(0.0)) || (num == denom)) { + code = 'v'; + } + + t = num / denom; + + if ((double(0.0) < s) && (s < double(1.0)) && (double(0.0) < t) && (t < double(1.0))) { + code = '1'; + } else if ((double(0.0) > s) || (s > double(1.0)) || (double(0.0) > t) || (t > double(1.0))) { + code = '0'; + } + + p[0] = a[0] + s * (b[0] - a[0]); + p[1] = a[1] + s * (b[1] - a[1]); + + return code; + } + + inline bool point_in_bounding_box(const vec2& point, const bounding_box_t& bbox) + { + if ((point.x() < bbox.m_minimum.x() || point.x() > bbox.m_maximum.x()) || // + (point.y() < bbox.m_minimum.y() || point.y() > bbox.m_maximum.y())) { + return false; + } else { + return true; + } + } + + inline bool point_in_bounding_box(const vec3& point, const bounding_box_t& bbox) + { + if ((point.x() < bbox.m_minimum.x() || point.x() > bbox.m_maximum.x()) || // + (point.y() < bbox.m_minimum.y() || point.y() > bbox.m_maximum.y()) || // + (point.z() < bbox.m_minimum.z() || point.z() > bbox.m_maximum.z())) { // + return false; + } else { + return true; + } + } + + diff --git a/src/mcut/source/mcut.cpp b/src/mcut/source/mcut.cpp new file mode 100644 index 0000000000..8937a0f946 --- /dev/null +++ b/src/mcut/source/mcut.cpp @@ -0,0 +1,764 @@ +/** + * 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: + * (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 "mcut/mcut.h" + +#include "mcut/internal/frontend.h" +#include "mcut/internal/timer.h" +#include "mcut/internal/utils.h" + +#include +#include + +#if defined(MCUT_BUILD_WINDOWS) +#pragma warning(disable : 26812) +#endif + +MCAPI_ATTR McResult MCAPI_CALL mcCreateContext(McContext* pOutContext, McFlags contextFlags) +{ + McResult return_value = McResult::MC_NO_ERROR; + per_thread_api_log_str.clear(); + + if (pOutContext == nullptr) { + per_thread_api_log_str = "context ptr undef (NULL)"; + return_value = McResult::MC_INVALID_VALUE; + } else { + try { + // no helper threads + // only manager threads (2 managers if context is created with MC_OUT_OF_ORDER_EXEC_MODE_ENABLE, otherwise 1 manager) + create_context_impl(pOutContext, contextFlags, 0); + } + CATCH_POSSIBLE_EXCEPTIONS(per_thread_api_log_str); + } + + if (return_value != McResult::MC_NO_ERROR) { + std::fprintf(stderr, "%s(...) -> %s\n", __FUNCTION__, per_thread_api_log_str.c_str()); + } + + return return_value; +} + +MCAPI_ATTR McResult MCAPI_CALL mcCreateContextWithHelpers(McContext* pOutContext, McFlags contextFlags, uint32_t helperThreadCount) +{ + McResult return_value = McResult::MC_NO_ERROR; + per_thread_api_log_str.clear(); + + if (pOutContext == nullptr) { + per_thread_api_log_str = "context ptr undef (NULL)"; + return_value = McResult::MC_INVALID_VALUE; + } else { + try { + create_context_impl(pOutContext, contextFlags, helperThreadCount); + } + CATCH_POSSIBLE_EXCEPTIONS(per_thread_api_log_str); + } + + if (return_value != McResult::MC_NO_ERROR) { + std::fprintf(stderr, "%s(...) -> %s\n", __FUNCTION__, per_thread_api_log_str.c_str()); + } + + return return_value; +} + +MCAPI_ATTR McResult MCAPI_CALL mcDebugMessageCallback(McContext pContext, pfn_mcDebugOutput_CALLBACK cb, const McVoid* userParam) +{ + McResult return_value = McResult::MC_NO_ERROR; + per_thread_api_log_str.clear(); + + if (pContext == nullptr) { + per_thread_api_log_str = "context ptr (param0) undef (NULL)"; + } else if (cb == nullptr) { + per_thread_api_log_str = "callback function ptr (param1) undef (NULL)"; + } else { + try { + debug_message_callback_impl(pContext, cb, userParam); + } + CATCH_POSSIBLE_EXCEPTIONS(per_thread_api_log_str); + } + + if (!per_thread_api_log_str.empty()) { + std::fprintf(stderr, "%s(...) -> %s\n", __FUNCTION__, per_thread_api_log_str.c_str()); + + if (return_value == McResult::MC_NO_ERROR) // i.e. problem with basic local parameter checks + { + return_value = McResult::MC_INVALID_VALUE; + } + } + + return return_value; +} + +MCAPI_ATTR McResult MCAPI_CALL mcGetDebugMessageLog( + McContext context, + McUint32 count, McSize bufSize, + McDebugSource* sources, McDebugType* types, McDebugSeverity* severities, + McSize* lengths, McChar* messageLog, McUint32* numFetched) +{ + McResult return_value = McResult::MC_NO_ERROR; + per_thread_api_log_str.clear(); + + if (context == nullptr) { + per_thread_api_log_str = "context ptr (param0) undef (NULL)"; + } else if ( + count == 0) { + per_thread_api_log_str = "count must be > 0"; + } else if (bufSize == 0) { + per_thread_api_log_str = "bufSize must be > 0"; + } else if (numFetched == nullptr) { + per_thread_api_log_str = "numFetched undef (NULL)"; + } else { + try { + get_debug_message_log_impl(context, + count, bufSize, + sources, types, severities, + lengths, messageLog, *numFetched); + } + CATCH_POSSIBLE_EXCEPTIONS(per_thread_api_log_str); + } + + if (!per_thread_api_log_str.empty()) { + std::fprintf(stderr, "%s(...) -> %s\n", __FUNCTION__, per_thread_api_log_str.c_str()); + if (return_value == McResult::MC_NO_ERROR) // i.e. problem with basic local parameter checks + { + return_value = McResult::MC_INVALID_VALUE; + } + } + + return return_value; +} + +MCAPI_ATTR McResult MCAPI_CALL mcDebugMessageControl(McContext pContext, McDebugSource source, McDebugType type, McDebugSeverity severity, bool enabled) +{ + McResult return_value = McResult::MC_NO_ERROR; + per_thread_api_log_str.clear(); + + if (pContext == nullptr) { + per_thread_api_log_str = "context ptr (param0) undef (NULL)"; + } else if ( + false == (source == McDebugSource::MC_DEBUG_SOURCE_ALL || // + source == McDebugSource::MC_DEBUG_SOURCE_KERNEL || // + source == McDebugSource::MC_DEBUG_SOURCE_ALL)) { + per_thread_api_log_str = "debug source val (param1) invalid"; + } else if ( + false == (type == McDebugType::MC_DEBUG_TYPE_ALL || // + type == McDebugType::MC_DEBUG_TYPE_DEPRECATED_BEHAVIOR || // + type == McDebugType::MC_DEBUG_TYPE_ERROR || // + type == McDebugType::MC_DEBUG_TYPE_OTHER)) { + per_thread_api_log_str = "debug type val (param2) invalid"; + } else if ( + false == (severity == McDebugSeverity::MC_DEBUG_SEVERITY_HIGH || // + severity == McDebugSeverity::MC_DEBUG_SEVERITY_MEDIUM || // + severity == McDebugSeverity::MC_DEBUG_SEVERITY_LOW || // + severity == McDebugSeverity::MC_DEBUG_SEVERITY_NOTIFICATION || // + severity == McDebugSeverity::MC_DEBUG_SEVERITY_ALL)) { + per_thread_api_log_str = "debug severity val (param3) invalid"; + } else { + try { + debug_message_control_impl(pContext, source, type, severity, enabled); + } + CATCH_POSSIBLE_EXCEPTIONS(per_thread_api_log_str); + } + + if (!per_thread_api_log_str.empty()) { + std::fprintf(stderr, "%s(...) -> %s\n", __FUNCTION__, per_thread_api_log_str.c_str()); + if (return_value == McResult::MC_NO_ERROR) // i.e. problem with basic local parameter checks + { + return_value = McResult::MC_INVALID_VALUE; + } + } + + return return_value; +} + +MCAPI_ATTR McResult MCAPI_CALL mcGetInfo(const McContext context, McFlags info, McSize bytes, McVoid* pMem, McSize* pNumBytes) +{ + McResult return_value = McResult::MC_NO_ERROR; + per_thread_api_log_str.clear(); + + if (context == nullptr) { + per_thread_api_log_str = "context ptr (param0) undef (NULL)"; + } else if (bytes != 0 && pMem == nullptr) { + per_thread_api_log_str = "invalid specification (param2 & param3)"; + } else if (false == (info == MC_CONTEXT_FLAGS || info == MC_MAX_DEBUG_MESSAGE_LENGTH)) // check all possible values + { + per_thread_api_log_str = "invalid info flag val (param1)"; + } else if ((info == MC_CONTEXT_FLAGS) && (pMem != nullptr && bytes != sizeof(McFlags))) { + per_thread_api_log_str = "invalid byte size (param2)"; // leads to e.g. "out of bounds" memory access during memcpy + } else { + try { + get_info_impl(context, info, bytes, pMem, pNumBytes); + } + CATCH_POSSIBLE_EXCEPTIONS(per_thread_api_log_str); + } + + if (!per_thread_api_log_str.empty()) { + std::fprintf(stderr, "%s(...) -> %s\n", __FUNCTION__, per_thread_api_log_str.c_str()); + if (return_value == McResult::MC_NO_ERROR) // i.e. problem with basic local parameter checks + { + return_value = McResult::MC_INVALID_VALUE; + } + } + + return return_value; +} + +MCAPI_ATTR McResult MCAPI_CALL mcCreateUserEvent( + McEvent* event, + McContext context) +{ + McResult return_value = McResult::MC_NO_ERROR; + per_thread_api_log_str.clear(); + + if (event == nullptr) { + per_thread_api_log_str = "event ptr (param0) undef (NULL)"; + } else if (context == nullptr) { + per_thread_api_log_str = "context handle undefined (NULL)"; + } else { + try { + create_user_event_impl(event, context); + } + CATCH_POSSIBLE_EXCEPTIONS(per_thread_api_log_str); + } + + if (!per_thread_api_log_str.empty()) { + std::fprintf(stderr, "%s(...) -> %s\n", __FUNCTION__, per_thread_api_log_str.c_str()); + if (return_value == McResult::MC_NO_ERROR) // i.e. problem with basic local parameter checks + { + return_value = McResult::MC_INVALID_VALUE; + } + } + + return return_value; +} + +MCAPI_ATTR McResult MCAPI_CALL mcSetUserEventStatus( + McEvent event, + McInt32 execution_status) +{ + McResult return_value = McResult::MC_NO_ERROR; + per_thread_api_log_str.clear(); + + if (event == nullptr) { + per_thread_api_log_str = "event ptr (param0) undef (NULL)"; + } else { + try { + set_user_event_status_impl(event, execution_status); + } + CATCH_POSSIBLE_EXCEPTIONS(per_thread_api_log_str); + } + + if (!per_thread_api_log_str.empty()) { + std::fprintf(stderr, "%s(...) -> %s\n", __FUNCTION__, per_thread_api_log_str.c_str()); + if (return_value == McResult::MC_NO_ERROR) // i.e. problem with basic local parameter checks + { + return_value = McResult::MC_INVALID_VALUE; + } + } + + return return_value; +} + +MCAPI_ATTR McResult MCAPI_CALL mcGetEventInfo(const McEvent event, McFlags info, McSize bytes, McVoid* pMem, McSize* pNumBytes) +{ + McResult return_value = McResult::MC_NO_ERROR; + per_thread_api_log_str.clear(); + + if (event == nullptr) { + per_thread_api_log_str = "context ptr (param0) undef (NULL)"; + } else if (bytes != 0 && pMem == nullptr) { + per_thread_api_log_str = "invalid specification (param2 & param3)"; + } else if ((info == MC_EVENT_RUNTIME_EXECUTION_STATUS) && (pMem != nullptr && bytes != sizeof(McResult))) { + per_thread_api_log_str = "invalid byte size (param2)"; // leads to e.g. "out of bounds" memory access during memcpy + } else if ((info == MC_EVENT_COMMAND_EXECUTION_STATUS) && (pMem != nullptr && bytes != sizeof(McFlags))) { + per_thread_api_log_str = "invalid byte size (param2)"; // leads to e.g. "out of bounds" memory access during memcpy + } else if ((info == MC_EVENT_TIMESTAMP_SUBMIT || info == MC_EVENT_TIMESTAMP_START || info == MC_EVENT_TIMESTAMP_END) && (pMem != nullptr && bytes != sizeof(McSize))) { + per_thread_api_log_str = "invalid byte size (param2)"; // leads to e.g. "out of bounds" memory access during memcpy + } else { + try { + get_event_info_impl(event, info, bytes, pMem, pNumBytes); + } + CATCH_POSSIBLE_EXCEPTIONS(per_thread_api_log_str); + } + + if (!per_thread_api_log_str.empty()) { + std::fprintf(stderr, "%s(...) -> %s\n", __FUNCTION__, per_thread_api_log_str.c_str()); + if (return_value == McResult::MC_NO_ERROR) // i.e. problem with basic local parameter checks + { + return_value = McResult::MC_INVALID_VALUE; + } + } + + return return_value; +} + +MCAPI_ATTR McResult MCAPI_CALL mcWaitForEvents( + uint32_t numEventsInWaitlist, + const McEvent* pEventWaitList) +{ + McResult return_value = McResult::MC_NO_ERROR; + per_thread_api_log_str.clear(); + + if (pEventWaitList == nullptr && numEventsInWaitlist > 0) { + per_thread_api_log_str = "invalid event waitlist ptr (NULL)"; + } else if (pEventWaitList != nullptr && numEventsInWaitlist == 0) { + per_thread_api_log_str = "invalid event waitlist size (zero)"; + } else { + try { + McResult waitliststatus = MC_NO_ERROR; + wait_for_events_impl(numEventsInWaitlist, pEventWaitList, waitliststatus); + + if (waitliststatus != McResult::MC_NO_ERROR) { + per_thread_api_log_str = "event in waitlist has an error"; + } + } + CATCH_POSSIBLE_EXCEPTIONS(per_thread_api_log_str); + } + + if (!per_thread_api_log_str.empty()) { + + std::fprintf(stderr, "%s(...) -> %s\n", __FUNCTION__, per_thread_api_log_str.c_str()); + + if (return_value == McResult::MC_NO_ERROR) // i.e. problem with basic local parameter checks + { + return_value = McResult::MC_INVALID_VALUE; + } + } + + return return_value; +} + +MCAPI_ATTR McResult MCAPI_CALL mcSetEventCallback( + McEvent eventHandle, + pfn_McEvent_CALLBACK eventCallback, + McVoid* data) +{ + McResult return_value = McResult::MC_NO_ERROR; + per_thread_api_log_str.clear(); + + if (eventHandle == nullptr) { + per_thread_api_log_str = "invalid event ptr (NULL)"; + } + if (eventCallback == nullptr) { + per_thread_api_log_str = "invalid event callback function ptr (NULL)"; + } else { + try { + set_event_callback_impl(eventHandle, eventCallback, data); + } + CATCH_POSSIBLE_EXCEPTIONS(per_thread_api_log_str); + } + + if (!per_thread_api_log_str.empty()) { + + std::fprintf(stderr, "%s(...) -> %s\n", __FUNCTION__, per_thread_api_log_str.c_str()); + + if (return_value == McResult::MC_NO_ERROR) // i.e. problem with basic local parameter checks + { + return_value = McResult::MC_INVALID_VALUE; + } + } + + return return_value; +} + +MCAPI_ATTR McResult MCAPI_CALL mcEnqueueDispatch( + const McContext context, + McFlags dispatchFlags, + const McVoid* pSrcMeshVertices, + const uint32_t* pSrcMeshFaceIndices, + const uint32_t* pSrcMeshFaceSizes, + uint32_t numSrcMeshVertices, + uint32_t numSrcMeshFaces, + const McVoid* pCutMeshVertices, + const uint32_t* pCutMeshFaceIndices, + const uint32_t* pCutMeshFaceSizes, + uint32_t numCutMeshVertices, + uint32_t numCutMeshFaces, + uint32_t numEventsInWaitlist, + const McEvent* pEventWaitList, + McEvent* pEvent) +{ + TIMESTACK_RESET(); // reset tracking vars + + SCOPED_TIMER(__FUNCTION__); + + McResult return_value = McResult::MC_NO_ERROR; + per_thread_api_log_str.clear(); + + if (context == nullptr) { + per_thread_api_log_str = "context ptr (param0) undef (NULL)"; + } else if (dispatchFlags == 0) { + per_thread_api_log_str = "dispatch flags unspecified"; + } else if ((dispatchFlags & MC_DISPATCH_REQUIRE_THROUGH_CUTS) && // + (dispatchFlags & MC_DISPATCH_FILTER_FRAGMENT_LOCATION_UNDEFINED)) { + // The user states that she does not want a partial cut but yet also states that she + // wants to keep fragments with partial cuts. These two options are mutually exclusive! + per_thread_api_log_str = "use of mutually-exclusive flags: MC_DISPATCH_REQUIRE_THROUGH_CUTS & MC_DISPATCH_FILTER_FRAGMENT_LOCATION_UNDEFINED"; + } else if ((dispatchFlags & MC_DISPATCH_VERTEX_ARRAY_FLOAT) == 0 && (dispatchFlags & MC_DISPATCH_VERTEX_ARRAY_DOUBLE) == 0) { + per_thread_api_log_str = "dispatch vertex aray type unspecified"; + } else if (pSrcMeshVertices == nullptr) { + per_thread_api_log_str = "source-mesh vertex-position array ptr undef (NULL)"; + } else if (numSrcMeshVertices < 3) { + per_thread_api_log_str = "invalid source-mesh vertex count"; + } else if (pSrcMeshFaceIndices == nullptr) { + per_thread_api_log_str = "source-mesh face-index array ptr undef (NULL)"; + } /*else if (pSrcMeshFaceSizes == nullptr) { + per_thread_api_log_str = "source-mesh face-size array ptr undef (NULL)"; + }*/ + else if (numSrcMeshFaces < 1) { + per_thread_api_log_str = "invalid source-mesh vertex count"; + } else if (pCutMeshVertices == nullptr) { + per_thread_api_log_str = "cut-mesh vertex-position array ptr undef (NULL)"; + } else if (numCutMeshVertices < 3) { + per_thread_api_log_str = "invalid cut-mesh vertex count"; + } else if (pCutMeshFaceIndices == nullptr) { + per_thread_api_log_str = "cut-mesh face-index array ptr undef (NULL)"; + } /*else if (pCutMeshFaceSizes == nullptr) { + per_thread_api_log_str = "cut-mesh face-size array ptr undef (NULL)"; + } */ + else if (numCutMeshFaces < 1) { + per_thread_api_log_str = "invalid cut-mesh vertex count"; + } else if (pEventWaitList == nullptr && numEventsInWaitlist > 0) { + per_thread_api_log_str = "invalid event waitlist ptr (NULL)"; + } else if (pEventWaitList != nullptr && numEventsInWaitlist == 0) { + per_thread_api_log_str = "invalid event waitlist size (zero)"; + } else if (pEventWaitList == nullptr && numEventsInWaitlist == 0 && pEvent == nullptr) { + per_thread_api_log_str = "invalid event ptr (zero)"; + } else { + try { + dispatch_impl( + context, + dispatchFlags, + pSrcMeshVertices, + pSrcMeshFaceIndices, + pSrcMeshFaceSizes, + numSrcMeshVertices, + numSrcMeshFaces, + pCutMeshVertices, + pCutMeshFaceIndices, + pCutMeshFaceSizes, + numCutMeshVertices, + numCutMeshFaces, + numEventsInWaitlist, + pEventWaitList, + pEvent); + } + CATCH_POSSIBLE_EXCEPTIONS(per_thread_api_log_str); + } + + if (!per_thread_api_log_str.empty()) { + + std::fprintf(stderr, "%s(...) -> %s\n", __FUNCTION__, per_thread_api_log_str.c_str()); + + if (return_value == McResult::MC_NO_ERROR) // i.e. problem with basic local parameter checks + { + return_value = McResult::MC_INVALID_VALUE; + } + } + + return return_value; +} + +MCAPI_ATTR McResult MCAPI_CALL mcDispatch( + const McContext context, + McFlags dispatchFlags, + const McVoid* pSrcMeshVertices, + const uint32_t* pSrcMeshFaceIndices, + const uint32_t* pSrcMeshFaceSizes, + uint32_t numSrcMeshVertices, + uint32_t numSrcMeshFaces, + const McVoid* pCutMeshVertices, + const uint32_t* pCutMeshFaceIndices, + const uint32_t* pCutMeshFaceSizes, + uint32_t numCutMeshVertices, + uint32_t numCutMeshFaces) +{ + McEvent event = MC_NULL_HANDLE; + + McResult return_value = mcEnqueueDispatch( + context, + dispatchFlags, + pSrcMeshVertices, + pSrcMeshFaceIndices, + pSrcMeshFaceSizes, + numSrcMeshVertices, + numSrcMeshFaces, + pCutMeshVertices, + pCutMeshFaceIndices, + pCutMeshFaceSizes, + numCutMeshVertices, + numCutMeshFaces, + 0, + nullptr, + &event); + + if (return_value == MC_NO_ERROR) { // API parameter checks are fine + if (event != MC_NULL_HANDLE) // event must exist to wait on and query + { + McResult waitliststatus = MC_NO_ERROR; + + wait_for_events_impl(1, &event, waitliststatus); // block until event of mcEnqueueDispatch is completed! + + if (waitliststatus != McResult::MC_NO_ERROR) { + return_value = waitliststatus; + } + + release_events_impl(1, &event); // destroy + } + } + + return return_value; +} + +MCAPI_ATTR McResult MCAPI_CALL mcEnqueueGetConnectedComponents( + const McContext context, + const McConnectedComponentType connectedComponentType, + const uint32_t numEntries, + McConnectedComponent* pConnComps, + uint32_t* numConnComps, + uint32_t numEventsInWaitlist, + const McEvent* pEventWaitList, + McEvent* pEvent) +{ + McResult return_value = McResult::MC_NO_ERROR; + per_thread_api_log_str.clear(); + + if (context == nullptr) { + per_thread_api_log_str = "context ptr (param0) undef (NULL)"; + } else if (connectedComponentType == 0) { + per_thread_api_log_str = "invalid type-parameter (param1) (0)"; + } else if (numConnComps == nullptr && pConnComps == nullptr) { + per_thread_api_log_str = "output parameters undef (param3 & param4)"; + } else if (pEventWaitList == nullptr && numEventsInWaitlist > 0) { + per_thread_api_log_str = "invalid event waitlist ptr (NULL)"; + } else if (pEventWaitList != nullptr && numEventsInWaitlist == 0) { + per_thread_api_log_str = "invalid event waitlist size (zero)"; + } else if (pEventWaitList == nullptr && numEventsInWaitlist == 0 && pEvent == nullptr) { + per_thread_api_log_str = "invalid event ptr (zero)"; + } else { + try { + + get_connected_components_impl(context, connectedComponentType, numEntries, pConnComps, numConnComps, numEventsInWaitlist, pEventWaitList, pEvent); + } + CATCH_POSSIBLE_EXCEPTIONS(per_thread_api_log_str); + } + + if (!per_thread_api_log_str.empty()) { + + std::fprintf(stderr, "%s(...) -> %s\n", __FUNCTION__, per_thread_api_log_str.c_str()); + + if (return_value == McResult::MC_NO_ERROR) // i.e. problem with basic local parameter checks + { + return_value = McResult::MC_INVALID_VALUE; + } + } + + return return_value; +} + +MCAPI_ATTR McResult MCAPI_CALL mcGetConnectedComponents( + const McContext context, + const McConnectedComponentType connectedComponentType, + const uint32_t numEntries, + McConnectedComponent* pConnComps, + uint32_t* numConnComps) +{ + McEvent event = MC_NULL_HANDLE; + McResult return_value = mcEnqueueGetConnectedComponents(context, connectedComponentType, numEntries, pConnComps, numConnComps, 0, nullptr, &event); + if (event != MC_NULL_HANDLE) // event must exist to wait on and query + { + McResult waitliststatus = MC_NO_ERROR; + + wait_for_events_impl(1, &event, waitliststatus); // block until event of mcEnqueueDispatch is completed! + + if (waitliststatus != McResult::MC_NO_ERROR) { + return_value = waitliststatus; + } + release_events_impl(1, &event); // destroy + } + return return_value; +} + +MCAPI_ATTR McResult MCAPI_CALL mcEnqueueGetConnectedComponentData( + const McContext context, + const McConnectedComponent connCompId, + McFlags queryFlags, + McSize bytes, + McVoid* pMem, + McSize* pNumBytes, + uint32_t numEventsInWaitlist, + const McEvent* pEventWaitList, + McEvent* pEvent) +{ + McResult return_value = McResult::MC_NO_ERROR; + per_thread_api_log_str.clear(); + + if (context == nullptr) { + per_thread_api_log_str = "context ptr (param0) undef (NULL)"; + } + if (connCompId == nullptr) { + per_thread_api_log_str = "connected component ptr (param1) undef (NULL)"; + } else if (queryFlags == 0) { + per_thread_api_log_str = "flags (param1) undef (0)"; + } else if (bytes != 0 && pMem == nullptr) { + per_thread_api_log_str = "null parameter (param3 & param4)"; + } else if (pEventWaitList == nullptr && numEventsInWaitlist > 0) { + per_thread_api_log_str = "invalid event waitlist ptr (NULL)"; + } else if (pEventWaitList != nullptr && numEventsInWaitlist == 0) { + per_thread_api_log_str = "invalid event waitlist size (zero)"; + } else if (pEventWaitList == nullptr && numEventsInWaitlist == 0 && pEvent == nullptr) { + per_thread_api_log_str = "invalid event ptr (zero)"; + } else { + try { + get_connected_component_data_impl(context, connCompId, queryFlags, bytes, pMem, pNumBytes, numEventsInWaitlist, pEventWaitList, pEvent); + } + CATCH_POSSIBLE_EXCEPTIONS(per_thread_api_log_str); + } + + if (!per_thread_api_log_str.empty()) { + + std::fprintf(stderr, "%s(...) -> %s\n", __FUNCTION__, per_thread_api_log_str.c_str()); + + if (return_value == McResult::MC_NO_ERROR) // i.e. problem with basic local parameter checks + { + return_value = McResult::MC_INVALID_VALUE; + } + } + + return return_value; +} + +MCAPI_ATTR McResult MCAPI_CALL mcGetConnectedComponentData( + const McContext context, + const McConnectedComponent connCompId, + McFlags queryFlags, + McSize bytes, + McVoid* pMem, + McSize* pNumBytes) +{ + McEvent event = MC_NULL_HANDLE; + McResult return_value = mcEnqueueGetConnectedComponentData(context, connCompId, queryFlags, bytes, pMem, pNumBytes, 0, nullptr, &event); + if (event != MC_NULL_HANDLE) // event must exist to wait on and query + { + McResult waitliststatus = MC_NO_ERROR; + + wait_for_events_impl(1, &event, waitliststatus); // block until event of mcEnqueueDispatch is completed! + + if (waitliststatus != McResult::MC_NO_ERROR) { + return_value = waitliststatus; + } + + release_events_impl(1, &event); // destroy + } + return return_value; +} + +MCAPI_ATTR McResult MCAPI_CALL mcReleaseEvents( + uint32_t numEvents, + const McEvent* pEvents) +{ + McResult return_value = McResult::MC_NO_ERROR; + per_thread_api_log_str.clear(); + + if (numEvents > 0 && pEvents == NULL) { + per_thread_api_log_str = "invalid pointer to events"; + } else if (numEvents == 0 && pEvents != NULL) { + per_thread_api_log_str = "number of events not set"; + } else { + try { + release_events_impl(numEvents, pEvents); + } + CATCH_POSSIBLE_EXCEPTIONS(per_thread_api_log_str); + } + + if (!per_thread_api_log_str.empty()) { + + std::fprintf(stderr, "%s(...) -> %s\n", __FUNCTION__, per_thread_api_log_str.c_str()); + + if (return_value == McResult::MC_NO_ERROR) // i.e. problem with basic local parameter checks + { + return_value = McResult::MC_INVALID_VALUE; + } + } + + return return_value; +} + +MCAPI_ATTR McResult MCAPI_CALL mcReleaseConnectedComponents( + const McContext context, + uint32_t numConnComps, + const McConnectedComponent* pConnComps) +{ + McResult return_value = McResult::MC_NO_ERROR; + per_thread_api_log_str.clear(); + + if (context == nullptr) { + per_thread_api_log_str = "context ptr (param0) undef (NULL)"; + } else if (numConnComps > 0 && pConnComps == NULL) { + per_thread_api_log_str = "invalid pointer to connected components"; + } else if (numConnComps == 0 && pConnComps != NULL) { + per_thread_api_log_str = "number of connected components not set"; + } else { + try { + release_connected_components_impl(context, numConnComps, pConnComps); + } + CATCH_POSSIBLE_EXCEPTIONS(per_thread_api_log_str); + } + + if (!per_thread_api_log_str.empty()) { + + std::fprintf(stderr, "%s(...) -> %s\n", __FUNCTION__, per_thread_api_log_str.c_str()); + + if (return_value == McResult::MC_NO_ERROR) // i.e. problem with basic local parameter checks + { + return_value = McResult::MC_INVALID_VALUE; + } + } + + return return_value; +} + +MCAPI_ATTR McResult MCAPI_CALL mcReleaseContext(const McContext context) +{ + McResult return_value = McResult::MC_NO_ERROR; + per_thread_api_log_str.clear(); + + if (context == nullptr) { + per_thread_api_log_str = "context ptr (param0) undef (NULL)"; + } else { + try { + release_context_impl(context); + } + CATCH_POSSIBLE_EXCEPTIONS(per_thread_api_log_str); + } + + if (!per_thread_api_log_str.empty()) { + + std::fprintf(stderr, "%s(...) -> %s\n", __FUNCTION__, per_thread_api_log_str.c_str()); + + if (return_value == McResult::MC_NO_ERROR) // i.e. problem with basic local parameter checks + { + return_value = McResult::MC_INVALID_VALUE; + } + } + + return return_value; +} diff --git a/src/mcut/source/preproc.cpp b/src/mcut/source/preproc.cpp new file mode 100644 index 0000000000..fd63ae7135 --- /dev/null +++ b/src/mcut/source/preproc.cpp @@ -0,0 +1,2198 @@ +#include "mcut/internal/frontend.h" + +#include "mcut/internal/bvh.h" +#include "mcut/internal/hmesh.h" +#include "mcut/internal/kernel.h" +#include "mcut/internal/math.h" +#include "mcut/internal/utils.h" +#include "mcut/internal/timer.h" + +#include // std::partial_sum +#include +#include // for numerical perturbation + +// If the inputs are found to not be in general position, then we perturb the +// cut-mesh by this constant (scaled by bbox diag times a random variable [0.1-1.0]). +const double GENERAL_POSITION_ENFORCMENT_CONSTANT = 1e-4; +const int MAX_PERTUBATION_ATTEMPTS = 1 << 3; + +// this function converts an index array mesh (e.g. as recieved by the dispatch +// function) into a halfedge mesh representation for the kernel backend. +bool client_input_arrays_to_hmesh( + std::shared_ptr& context_ptr, + McFlags dispatchFlags, + hmesh_t& halfedgeMesh, + double& bboxDiagonal, + const void* pVertices, + const uint32_t* pFaceIndices, + const uint32_t* pFaceSizes, + const uint32_t numVertices, + const uint32_t numFaces, + const vec3* perturbation = NULL) +{ + SCOPED_TIMER(__FUNCTION__); + + context_ptr->dbg_cb(MC_DEBUG_SOURCE_API, MC_DEBUG_TYPE_OTHER, 0, MC_DEBUG_SEVERITY_NOTIFICATION, "construct halfedge mesh"); + + // minor optimization + halfedgeMesh.reserve_for_additional_elements(numVertices); + + TIMESTACK_PUSH("add vertices"); + + // did the user provide vertex arrays of 32-bit floats...? + if (dispatchFlags & MC_DISPATCH_VERTEX_ARRAY_FLOAT) { + const float* vptr = reinterpret_cast(pVertices); + + // for each input mesh-vertex + for (uint32_t i = 0; i < numVertices; ++i) { + const float& x = vptr[(i * 3) + 0]; + const float& y = vptr[(i * 3) + 1]; + const float& z = vptr[(i * 3) + 2]; + + // insert our vertex into halfedge mesh + vd_t vd = halfedgeMesh.add_vertex( + double(x) + (perturbation != NULL ? (*perturbation).x() : double(0.)), + double(y) + (perturbation != NULL ? (*perturbation).y() : double(0.)), + double(z) + (perturbation != NULL ? (*perturbation).z() : double(0.))); + + MCUT_ASSERT(vd != hmesh_t::null_vertex() && (uint32_t)vd < numVertices); + } + } + // did the user provide vertex arrays of 64-bit double...? + else if (dispatchFlags & MC_DISPATCH_VERTEX_ARRAY_DOUBLE) { + const double* vptr = reinterpret_cast(pVertices); + + // for each input mesh-vertex + for (uint32_t i = 0; i < numVertices; ++i) { + const double& x = vptr[(i * 3) + 0]; + const double& y = vptr[(i * 3) + 1]; + const double& z = vptr[(i * 3) + 2]; + + // insert our vertex into halfedge mesh + vd_t vd = halfedgeMesh.add_vertex( + double(x) + (perturbation != NULL ? (*perturbation).x() : double(0.)), + double(y) + (perturbation != NULL ? (*perturbation).y() : double(0.)), + double(z) + (perturbation != NULL ? (*perturbation).z() : double(0.))); + + MCUT_ASSERT(vd != hmesh_t::null_vertex() && (uint32_t)vd < numVertices); + } + } + + TIMESTACK_POP(); // TIMESTACK_PUSH("add vertices"); + + // compute the mesh bounding box while we are at it (for numerical perturbation) + vec3 bboxMin(1e10); + vec3 bboxMax(-1e10); + + TIMESTACK_PUSH("create bbox"); + for (vertex_array_iterator_t i = halfedgeMesh.vertices_begin(); i != halfedgeMesh.vertices_end(); ++i) { + const vec3& coords = halfedgeMesh.vertex(*i); + bboxMin = compwise_min(bboxMin, coords); + bboxMax = compwise_max(bboxMax, coords); + } + bboxDiagonal = length(bboxMax - bboxMin); + TIMESTACK_POP(); // TIMESTACK_PUSH("create bbox"); + + TIMESTACK_PUSH("create faces"); + + const bool assume_triangle_mesh = (pFaceSizes == nullptr); + +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + // init partial sums vec + std::vector partial_sums(numFaces, 3); // assume that "pFaceSizes" is filled with 3's (which is the implication if pFaceSizes is null) + + if (pFaceSizes != nullptr) // e.g. non-triangulated user mesh + { + for (uint32_t f = 0; f < numFaces; ++f) { + SAFE_ACCESS(partial_sums, f) = pFaceSizes[f]; + } + } + +#if 0 + std::partial_sum(partial_sums.begin(), partial_sums.end(), partial_sums.data()); +#else + parallel_partial_sum(context_ptr->get_shared_compute_threadpool() , partial_sums.begin(), partial_sums.end()); +#endif + { + typedef std::vector::const_iterator InputStorageIteratorType; + typedef std::pair OutputStorageType; // range of faces + std::atomic_int atm_result; + atm_result.store((int)McResult::MC_NO_ERROR); // 0 = ok;/ 1 = invalid face size; 2 invalid vertex index + + std::vector> faces(numFaces); + + auto fn_create_faces = [&]( + InputStorageIteratorType block_start_, + InputStorageIteratorType block_end_) -> OutputStorageType { + for (InputStorageIteratorType i = block_start_; i != block_end_; ++i) { + uint32_t faceID = (uint32_t)std::distance(partial_sums.cbegin(), i); + std::vector& faceVertices = faces[faceID]; + int face_vertex_count = assume_triangle_mesh ? 3 : ((uint32_t*)pFaceSizes)[faceID]; + + if (face_vertex_count < 3) { + int zero = (int)McResult::MC_NO_ERROR; + bool exchanged = atm_result.compare_exchange_strong(zero, 1); + if (exchanged) // first thread to detect error + { + context_ptr->dbg_cb( // + MC_DEBUG_SOURCE_API, // + MC_DEBUG_TYPE_ERROR, // + 0, // + MC_DEBUG_SEVERITY_HIGH, // + "invalid face-size for face - " + std::to_string(faceID) + " (size = " + std::to_string(face_vertex_count) + ")"); + } + break; + } + + faceVertices.resize(face_vertex_count); + int faceBaseOffset = (*i) - face_vertex_count; + + for (int j = 0; j < face_vertex_count; ++j) { + uint32_t idx = ((uint32_t*)pFaceIndices)[faceBaseOffset + j]; + + MCUT_ASSERT(idx < numVertices); + + const vertex_descriptor_t descr(idx); + const bool isDuplicate = std::find(faceVertices.cbegin(), faceVertices.cend(), descr) != faceVertices.cend(); + + if (isDuplicate) { + int zero = (int)McResult::MC_NO_ERROR; + bool exchanged = atm_result.compare_exchange_strong(zero, 2); + + if (exchanged) // first thread to detect error + { + context_ptr->dbg_cb( + MC_DEBUG_SOURCE_API, + MC_DEBUG_TYPE_ERROR, + 0, + MC_DEBUG_SEVERITY_HIGH, + "found duplicate vertex in face - " + std::to_string(faceID)); + } + break; + } + + faceVertices[j] = (descr); + } + } + return std::make_pair(block_start_, block_end_); + }; + + std::vector> futures; + OutputStorageType partial_res; + + parallel_for( + context_ptr->get_shared_compute_threadpool() , + partial_sums.cbegin(), + partial_sums.cend(), + fn_create_faces, + partial_res, // output computed by master thread + futures); + + auto add_faces = [&](InputStorageIteratorType block_start_, + InputStorageIteratorType block_end_) -> bool { + for (InputStorageIteratorType face_iter = block_start_; + face_iter != block_end_; ++face_iter) { + uint32_t faceID = (uint32_t)std::distance(partial_sums.cbegin(), face_iter); + const std::vector& faceVertices = SAFE_ACCESS(faces, faceID); + fd_t fd = halfedgeMesh.add_face(faceVertices); + + if (fd == hmesh_t::null_face()) { + + context_ptr->dbg_cb( // + MC_DEBUG_SOURCE_API, // + MC_DEBUG_TYPE_ERROR, // + 0, // + MC_DEBUG_SEVERITY_HIGH, // + "invalid vertices on face - " + std::to_string(faceID)); + return false; + } + } + return true; + }; + + bool okay = true; + for (int i = 0; i < (int)futures.size(); ++i) { + std::future& 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(); + + const int val = atm_result.load(); + okay = okay && val == 0; + if (!okay) { + continue; // just go on (to next iteration) in order to at-least wait for all tasks to finish before we return to user + } + + bool result = add_faces(future_res.first, future_res.second); + okay = okay && result == true; + } + + if (!okay) { + return false; + } + + // add lastly in order to maintain order + bool result = add_faces(partial_res.first, partial_res.second); + if (!result) { + return false; + } + } +#else // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + int faceSizeOffset = 0; + std::vector faceVertices; + + for (uint32_t i = 0; i < numFaces; ++i) { + faceVertices.clear(); + int face_vertex_count = assume_triangle_mesh ? 3 : ((uint32_t*)pFaceSizes)[i]; + + if (face_vertex_count < 3) { + + context_ptr->dbg_cb(MC_DEBUG_SOURCE_API, MC_DEBUG_TYPE_ERROR, 0, MC_DEBUG_SEVERITY_HIGH, "invalid face-size for face - " + std::to_string(i) + " (size = " + std::to_string(face_vertex_count) + ")"); + + return false; + } + + for (int j = 0; j < face_vertex_count; ++j) { + + uint32_t idx = ((uint32_t*)pFaceIndices)[faceSizeOffset + j]; + const vertex_descriptor_t descr(idx); // = fIter->second; //vmap[*fIter.first]; + const bool isDuplicate = std::find(faceVertices.cbegin(), faceVertices.cend(), descr) != faceVertices.cend(); + + if (isDuplicate) { + + context_ptr->dbg_cb(MC_DEBUG_SOURCE_API, MC_DEBUG_TYPE_ERROR, 0, MC_DEBUG_SEVERITY_HIGH, "found duplicate vertex in face - " + std::to_string(i)); + + return false; + } + + faceVertices.push_back(descr); + } + + fd_t fd = halfedgeMesh.add_face(faceVertices); + + if (fd == hmesh_t::null_face()) { + // Hint: this can happen when the mesh does not have a consistent + // winding order i.e. some faces are CCW and others are CW + context_ptr->dbg_cb(MC_DEBUG_SOURCE_API, MC_DEBUG_TYPE_ERROR, 0, MC_DEBUG_SEVERITY_HIGH, "non-manifold edge on face " + std::to_string(i)); + + return false; + } + + faceSizeOffset += face_vertex_count; + } +#endif + TIMESTACK_POP(); // TIMESTACK_PUSH("create faces"); + + return true; +} + +bool is_coplanar(const hmesh_t& m, const fd_t& f, int& fv_count) +{ + const std::vector vertices = m.get_vertices_around_face(f); + fv_count = (int)vertices.size(); + if (fv_count > 3) // non-triangle + { + for (int i = 0; i < (fv_count - 3); ++i) { + const int j = (i + 1) % fv_count; + const int k = (i + 2) % fv_count; + const int l = (i + 3) % fv_count; + + const vd_t& vi = vertices[i]; + const vd_t& vj = vertices[j]; + const vd_t& vk = vertices[k]; + const vd_t& vl = vertices[l]; + + const vec3& vi_coords = m.vertex(vi); + const vec3& vj_coords = m.vertex(vj); + const vec3& vk_coords = m.vertex(vk); + const vec3& vl_coords = m.vertex(vl); + + const bool are_coplaner = coplaner(vi_coords, vj_coords, vk_coords, vl_coords); + + if (!are_coplaner) { + return false; + } + } + } + return true; +} + +// check that the halfedge-mesh version of a user-provided mesh is valid (i.e. +// it is a non-manifold mesh containing a single connected component etc.) +bool check_input_mesh(std::shared_ptr& context_ptr, const hmesh_t& m) +{ + if (m.number_of_vertices() < 3) { + context_ptr->dbg_cb( + MC_DEBUG_SOURCE_API, + MC_DEBUG_TYPE_ERROR, + 0, + MC_DEBUG_SEVERITY_HIGH, + "Invalid vertex count (V=" + std::to_string(m.number_of_vertices()) + ")"); + return false; + } + + if (m.number_of_faces() < 1) { + context_ptr->dbg_cb( + MC_DEBUG_SOURCE_API, + MC_DEBUG_TYPE_ERROR, + 0, + MC_DEBUG_SEVERITY_HIGH, + "Invalid face count (F=" + std::to_string(m.number_of_faces()) + ")"); + return false; + } + + std::vector fccmap; + std::vector cc_to_vertex_count; + std::vector cc_to_face_count; + int n = find_connected_components( +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + context_ptr->get_shared_compute_threadpool() , +#endif + fccmap, m, cc_to_vertex_count, cc_to_face_count); + + if (n != 1) { + context_ptr->dbg_cb( + MC_DEBUG_SOURCE_API, + MC_DEBUG_TYPE_ERROR, + 0, + MC_DEBUG_SEVERITY_HIGH, + "Detected multiple connected components in mesh (N=" + std::to_string(n) + ")"); + return false; + } + + // check that the vertices of each face are co-planar + for (face_array_iterator_t f = m.faces_begin(); f != m.faces_end(); ++f) { + int fv_count = 0; + const bool face_is_coplanar = is_coplanar(m, *f, fv_count); + if (!face_is_coplanar) { + context_ptr->dbg_cb( + MC_DEBUG_SOURCE_API, + MC_DEBUG_TYPE_OTHER, + 0, + MC_DEBUG_SEVERITY_NOTIFICATION, + "Vertices (" + std::to_string(fv_count) + ") on face " + std::to_string(*f) + " not coplanar"); + // No need to return false, simply warn. It is difficult to + // know whether the non-coplanarity is severe enough to cause + // confusion when computing intersection points between two + // polygons (min=2 but sometimes can get 1 due to non-coplanarity + // of face vertices). + // In general, the more vertices on a face, the less likely + // they are to be co-planar. Faces with a low number of polygons + // are ideal (3 vertices being the best) + // result = false; + // break; + } + } + + return true; +} + +McResult convert(const status_t& v) +{ + McResult result = McResult::MC_RESULT_MAX_ENUM; + switch (v) { + case status_t::SUCCESS: + result = McResult::MC_NO_ERROR; + break; + case status_t::GENERAL_POSITION_VIOLATION: + case status_t::INVALID_MESH_INTERSECTION: + result = McResult::MC_INVALID_OPERATION; + break; + default: + std::fprintf(stderr, "[MCUT]: warning - conversion error (McResult=%d)\n", (int)v); + } + return result; +} + +McPatchLocation convert(const cm_patch_location_t& v) +{ + McPatchLocation result = McPatchLocation::MC_PATCH_LOCATION_ALL; + switch (v) { + case cm_patch_location_t::INSIDE: + result = McPatchLocation::MC_PATCH_LOCATION_INSIDE; + break; + case cm_patch_location_t::OUTSIDE: + result = McPatchLocation::MC_PATCH_LOCATION_OUTSIDE; + break; + case cm_patch_location_t::UNDEFINED: + result = McPatchLocation::MC_PATCH_LOCATION_UNDEFINED; + break; + default: + std::fprintf(stderr, "[MCUT]: warning - conversion error (McPatchLocation)\n"); + } + return result; +} + +McFragmentLocation convert(const sm_frag_location_t& v) +{ + McFragmentLocation result = McFragmentLocation::MC_FRAGMENT_LOCATION_ALL; + switch (v) { + case sm_frag_location_t::ABOVE: + result = McFragmentLocation::MC_FRAGMENT_LOCATION_ABOVE; + break; + case sm_frag_location_t::BELOW: + result = McFragmentLocation::MC_FRAGMENT_LOCATION_BELOW; + break; + case sm_frag_location_t::UNDEFINED: + result = McFragmentLocation::MC_FRAGMENT_LOCATION_UNDEFINED; + break; + default: + std::fprintf(stderr, "[MCUT]: warning - conversion error (McFragmentLocation)\n"); + } + return result; +} + +void resolve_floating_polygons( + bool& source_hmesh_modified, + bool& cut_hmesh_modified, + const std::map /*list of floating polys*/>& detected_floating_polygons, + const int source_hmesh_face_count_prev, + hmesh_t& source_hmesh, + hmesh_t& cut_hmesh, + std::unordered_map& source_hmesh_child_to_usermesh_birth_face, + std::unordered_map& cut_hmesh_child_to_usermesh_birth_face, + std::unordered_map& source_hmesh_new_poly_partition_vertices, + std::unordered_map& cut_hmesh_new_poly_partition_vertices) +{ + for (std::map>::const_iterator detected_floating_polygons_iter = detected_floating_polygons.cbegin(); + detected_floating_polygons_iter != detected_floating_polygons.cend(); + ++detected_floating_polygons_iter) { + + // get the [origin] input-mesh face index (Note: this index may be offsetted + // to distinguish between source-mesh and cut-mesh faces). + const fd_t parent_face_raw = detected_floating_polygons_iter->first; + + // NOTE: this boolean needs to be evaluated with "source_hmesh_face_count_prev" since the number of + // src-mesh faces might change as we add more polygons due to partitioning. + bool parent_face_from_source_hmesh = ((uint32_t)parent_face_raw < (uint32_t)source_hmesh_face_count_prev); + + // pointer to input mesh with face containing floating polygon + // Note: this mesh will be modified as we add new faces. + hmesh_t* parent_face_hmesh_ptr = (parent_face_from_source_hmesh ? &source_hmesh : &cut_hmesh); + + source_hmesh_modified = source_hmesh_modified || parent_face_from_source_hmesh; + cut_hmesh_modified = cut_hmesh_modified || !parent_face_from_source_hmesh; + + // This data structure maps the new faces in the modified input mesh, to the original partitioned/parent face in the [user-provided] input mesh. + std::unordered_map& child_to_client_birth_face = (parent_face_from_source_hmesh ? source_hmesh_child_to_usermesh_birth_face : cut_hmesh_child_to_usermesh_birth_face); + // This data structure stores the vertices added into the input mesh partition one or more face . + // We store the coordinates here too because they are sometimes needed to performed perturbation. + // This perturbation can happen when an input mesh face is partitioned with e.g. edge where that + // is sufficient to resolve all floating polygons detected on that input mesh face. + std::unordered_map& new_poly_partition_vertices = (parent_face_from_source_hmesh ? source_hmesh_new_poly_partition_vertices : cut_hmesh_new_poly_partition_vertices); + + // Now compute the actual input mesh face index (accounting for offset) + // i.e. index/descriptor into the mesh referenced by "parent_face_hmesh_ptr" + const fd_t parent_face = parent_face_from_source_hmesh ? parent_face_raw : fd_t((uint32_t)parent_face_raw - (uint32_t)source_hmesh_face_count_prev); // accounting for offset (NOTE: must updated "source_hmesh" state) + + MCUT_ASSERT(static_cast(parent_face) < (uint32_t)parent_face_hmesh_ptr->number_of_faces()); + + // for each floating polygon detected on current ps-face + for (std::vector::const_iterator floating_poly_info_iter = detected_floating_polygons_iter->second.cbegin(); + floating_poly_info_iter != detected_floating_polygons_iter->second.cend(); + ++floating_poly_info_iter) { + + const floating_polygon_info_t& fpi = *floating_poly_info_iter; + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + // Here we now need to partition "origin_face" in "parent_face_hmesh_ptr" + // by adding a new edge which is guarranteed to pass through the area + // spanned by the floating polygon. + + // gather vertices of floating polygon (just a list of 3d coords provided by the kernel) + + const size_t floating_poly_vertex_count = fpi.polygon_vertices.size(); + MCUT_ASSERT(floating_poly_vertex_count >= 3); + const size_t floating_poly_edge_count = floating_poly_vertex_count; // num edges is same as num verts + + // project the floating polygon to 2D + + std::vector floating_poly_vertices_2d; + + project_to_2d(floating_poly_vertices_2d, fpi.polygon_vertices, fpi.polygon_normal, fpi.polygon_normal_largest_component); + + // face to be (potentially) partitioned + // NOTE: This "origin_face" variable refer's to face that [may] actually be a child face that was created (in a previous iteration) + // as a result of another partitioning. + fd_t origin_face = parent_face; + + // we use this map to check is "origin_face" was actually created by polygon partitioning i.e. it did not exist in client/user-provided input mesh + std::unordered_map::const_iterator origin_to_birth_face_iter = child_to_client_birth_face.find(origin_face); + + // This boolean var will be true if "parent_face_raw" has more than one floating polygon associated with it, in which case + // "parent_face_raw" may be unnecessarilly be partitioned once. In such a case we want to minimise the number of edges + // that are used to partition "parent_face_raw" to as low as possible (minimum is one edge). + // Thus, we need this boolean var to handle the cases where another partition of "parent_face_raw" (due to one of its other + // floating polys) leads to the addition of another new edge (i.e. the one partitioning "parent_face_raw") that passes + // through the current floating poly. + // When this boolean variable is true, we will need to: + // 1) find all faces in "child_to_client_birth_face" that are mapped to same birth-face (in user provided mesh) as that + // of "parent_face_raw" (i.e. search by value) + // 2) for each such face check to see if any one of its edges intersect the current floating polygon + // This is necessary to ensure a minimal set of partitions. See below for details. + bool client_hmesh_birth_face_is_partitioned_atleast_once = (origin_to_birth_face_iter != child_to_client_birth_face.cend()); + fd_t client_hmesh_birth_face = hmesh_t::null_face(); + + bool do_partition_current_face = true; + + // check if we still need to partition origin_face. + // If a partition has already been made that added an edge into the mesh (i.e. the one referenced by "parent_face_hmesh_ptr") + // which passes through the current floating poly, then we will not need to partition "parent_face_raw". + // NOTE TO SELF: there is no guarrantee that the previously added edge that partitions "parent_face_raw" will not violate + // general-position w.r.t the current floating poly. + // Thus, general position might potentially be violated such that we would have to resort to numerical perturbation in the next + // dispatch(...) call. + if (client_hmesh_birth_face_is_partitioned_atleast_once) { + + client_hmesh_birth_face = origin_to_birth_face_iter->second; + + MCUT_ASSERT(origin_face == origin_to_birth_face_iter->first); + + // the child faces that we create by partitioning "client_hmesh_birth_face" (possibly over multiple dispatch calls + // in the case that GP is violated by an added edge) + std::vector faces_from_partitioned_birth_face; + + // for all other faces that share "client_hmesh_birth_face" + for (std::unordered_map::const_iterator it = child_to_client_birth_face.cbegin(); + it != child_to_client_birth_face.cend(); + ++it) { + if (it->second == client_hmesh_birth_face) { // matching client birth face ? + faces_from_partitioned_birth_face.push_back(it->first); + } + } + + bool have_face_intersecting_fp = false; + + // Should it be the case that we must proceed to make [another] partition of the + // birth-face, then "face_containing_floating_poly" represents the existing face (a child of the birth face) + // in which the current floating polygon lies. + fd_t face_containing_floating_poly = hmesh_t::null_face(); + + // for each face sharing a client birth face with origin_face + for (std::vector::const_iterator it = faces_from_partitioned_birth_face.cbegin(); + it != faces_from_partitioned_birth_face.cend(); + ++it) { + + fd_t face = *it; + + // :::::::::::::::::::::: + // get face vertex coords + const std::vector face_vertex_descriptors = parent_face_hmesh_ptr->get_vertices_around_face(face); + std::vector face_vertex_coords_3d(face_vertex_descriptors.size()); + + for (std::vector::const_iterator i = face_vertex_descriptors.cbegin(); i != face_vertex_descriptors.cend(); ++i) { + const size_t idx = std::distance(face_vertex_descriptors.cbegin(), i); + const vec3& coords = parent_face_hmesh_ptr->vertex(*i); + face_vertex_coords_3d[idx] = coords; + } + + // ::::::::::::::::::::::::: + // project face coords to 2D + std::vector face_vertex_coords_2d; + + project_to_2d(face_vertex_coords_2d, face_vertex_coords_3d, fpi.polygon_normal, fpi.polygon_normal_largest_component); + + const int face_edge_count = (int)face_vertex_descriptors.size(); // num edges == num verts + const int face_vertex_count = face_edge_count; + + // for each edge of face + for (int edge_iter = 0; edge_iter < face_edge_count; ++edge_iter) { + + const vec2& face_edge_v0 = SAFE_ACCESS(face_vertex_coords_2d, ((size_t)edge_iter) + 0); + const vec2& face_edge_v1 = SAFE_ACCESS(face_vertex_coords_2d, (((size_t)edge_iter) + 1) % face_vertex_count); + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + // Does the current edge of "face" intersect/pass through the area of + // the current floating polygon? + + bool have_face_edge_intersecting_fp = false; + + // for each edge of current floating poly + for (int fp_face_edge_iter = 0; fp_face_edge_iter < (int)floating_poly_edge_count; ++fp_face_edge_iter) { + + const vec2& fp_edge_v0 = floating_poly_vertices_2d[((size_t)fp_face_edge_iter) + 0]; + const vec2& fp_edge_v1 = floating_poly_vertices_2d[(((size_t)fp_face_edge_iter) + 1) % floating_poly_vertex_count]; + + // placeholders + double _1; // unused + double _2; // unused + vec2 _3; // unused + + const char res = compute_segment_intersection(face_edge_v0, face_edge_v1, fp_edge_v0, fp_edge_v1, _3, _1, _2); + + if (res == '1') { // implies a propery segment-segment intersection + have_face_edge_intersecting_fp = true; + break; + } + } + + if (have_face_edge_intersecting_fp == false && face_containing_floating_poly == hmesh_t::null_face()) { + // here we also do a test to find if the current face actually contains + // the floating polygon in its area. We will need this information in order to + // know the correct birth-face child-face that will be further partitioned + // so as to prevent the current floating polygon from coming up again in the + // next dispatch call. + + // for each floating polygon vertex ... + for (int fpVertIter = 0; fpVertIter < (int)floating_poly_vertices_2d.size(); ++fpVertIter) { + const char ret = compute_point_in_polygon_test(SAFE_ACCESS(floating_poly_vertices_2d, fpVertIter), face_vertex_coords_2d); + if (ret == 'i') { // check if strictly interior + face_containing_floating_poly = *it; + break; + } + } + } + + if (have_face_edge_intersecting_fp) { + have_face_intersecting_fp = true; + break; + } + } // for (std::vector::const_iterator hIt = halfedges.cbegin(); ... + + if (have_face_intersecting_fp) { + break; // done + } + + } // for (std::vector::const_iterator it = faces_from_partitioned_birth_face.cbegin(); ... + + // i.e. there exists no partitioning-edge which passes through the current floating polygon + do_partition_current_face = (have_face_intersecting_fp == false); + + if (do_partition_current_face) { + // update which face we treat as "origin_face" i.e. the one that we will partition + MCUT_ASSERT(face_containing_floating_poly != hmesh_t::null_face()); + origin_face = face_containing_floating_poly; + } + + } // if (client_hmesh_birth_face_is_partitioned_atleast_once) { + else { + client_hmesh_birth_face = origin_face; + } + + if (!do_partition_current_face) { + // skip current floating polygon no need to partition "origin_face" this time + // because an already-added edge into "parent_face_hmesh_ptr" will prevent the current + // floating polygon from arising + continue; // got to next floating polygon + } + + // gather vertices of "origin_face" (descriptors and 3d coords) + + // std::vector originFaceVertexDescriptors = parent_face_hmesh_ptr->get_vertices_around_face(origin_face); + std::vector origin_face_vertices_3d; + // get information about each edge (used by "origin_face") that needs to be split along the respective intersection point + const std::vector& origin_face_halfedges = parent_face_hmesh_ptr->get_halfedges_around_face(origin_face); + + for (std::vector::const_iterator i = origin_face_halfedges.cbegin(); i != origin_face_halfedges.cend(); ++i) { + const vd_t src = parent_face_hmesh_ptr->source(*i); // NOTE: we use source so that edge iterators/indices match with internal mesh storage + origin_face_vertices_3d.push_back(parent_face_hmesh_ptr->vertex(src)); + } + + // MCUT_ASSERT(fpi.projection_component != -1); // should be defined when we identify the floating polygon in the kernel + + // project the "origin_face" to 2D + // Since the geometry operations we are concerned about are inherently in 2d, here we project + // our coords from 3D to 2D. We project by eliminating the component corresponding + // to the "origin_face"'s normal vector's largest component. ("origin_face" and our + // floating polygon have the same normal!) + // + + std::vector origin_face_vertices_2d; + project_to_2d(origin_face_vertices_2d, origin_face_vertices_3d, fpi.polygon_normal, fpi.polygon_normal_largest_component); + + // ROUGH STEPS TO COMPUTE THE LINE THAT WILL BE USED TO PARTITION origin_face + // 1. pick two edges in the floating polygon + // 2. compute their mid-points + // 3. construct a [segment] with these two mid-points + // 4. if any vertex of the floating-poly is on the [line] defined by the segment OR + // ... if any vertex of the origin_face on the [line] defined by the segment: + // --> GOTO step 1 and select another pair of edges in the floating poly + // 5. construct a ray with the segment whose origin lies outside origin_face + // 6. intersect the ray with all edges of origin_face, and keep the intersection points [on the boundary] of origin_face + // 7. compute mid-point of our segment (from the two mid-points in step 3) + // 8. Get the two closest intersection points to this mid-point of our segment + // 9. Partition origin_face using the two closest intersection points this mid-point + // 10. Likewise update the connectivity of neighbouring faces of origin_face + // --> Neighbours to update are inferred from the halfedges that are partitioned at the two intersection points + // 11. remove "origin_face" from "parent_face_hmesh_ptr" + // 12. remove neighbours of "origin_face" from "parent_face_hmesh_ptr" that shared the edge on which the two intersection points lie. + // 13. add the child_polygons of "origin_face" and the re-traced neighbours into "parent_face_hmesh_ptr" + // 14. store a mapping from newly traced polygons to the original (user provided) input mesh elements + // --> This will also be used client vertex- and face-data mapping. + + const auto fp_get_edge_vertex_coords = [&](const int fp_edge_idx, vec2& fp_edge_v0, vec2& fp_edge_v1) { + const size_t fp_edge_v0_idx = (((size_t)fp_edge_idx) + 0); + fp_edge_v0 = floating_poly_vertices_2d[fp_edge_v0_idx]; + const size_t fp_edge_v1_idx = (((size_t)fp_edge_idx) + 1) % floating_poly_vertex_count; + fp_edge_v1 = floating_poly_vertices_2d[fp_edge_v1_idx]; + }; + + const auto fp_get_edge_midpoint = [&](int edgeIdx) { + vec2 edgeV0; + vec2 edgeV1; + + fp_get_edge_vertex_coords(edgeIdx, edgeV0, edgeV1); + + const vec2 midPoint( + (edgeV0.x() + edgeV1.x()) / double(2.0), // + (edgeV0.y() + edgeV1.y()) / double(2.0)); + + return midPoint; + }; + + auto fp_get_midpoint_distance = [&](std::pair edgePair) { + const vec2 edge0MidPoint = fp_get_edge_midpoint(edgePair.first); + const vec2 edge1MidPoint = fp_get_edge_midpoint(edgePair.second); + const double dist = squared_length(edge1MidPoint - edge0MidPoint); + return dist; + }; + + // NOTE: using max (i.e. < operator) lead to floating point precision issues on + // test 40. The only solution to which is exact arithmetic. However, since we still + // want MCUT to work even if the user only has fixed precision numbers. + // We pick edges based on this which are closest. No worries about colinear edges + // because they will be detected later and skipped! + auto fp_max_dist_predicate = [&](std::pair edgePairA, std::pair edgePairB) -> bool { + const double aDist = fp_get_midpoint_distance(edgePairA); + const double bDist = fp_get_midpoint_distance(edgePairB); + return aDist < bDist; + }; + + std::priority_queue< + std::pair, // + std::vector>, // + decltype(fp_max_dist_predicate)> + fp_edge_pair_priority_queue(fp_max_dist_predicate); + + // populate queue with [unique] pairs of edges from the floating polygon + // priority is given to those pairs with the farthest distance between then + for (int i = 0; i < (int)floating_poly_edge_count; ++i) { + for (int j = i + 1; j < (int)floating_poly_edge_count; ++j) { + fp_edge_pair_priority_queue.push(std::make_pair(i, j)); + } + } + + MCUT_ASSERT(fp_edge_pair_priority_queue.size() >= 3); // we can have at least 3 pairs for the simplest polygon (triangle) i.e. assuming it is not generate + + // In the next while loop, each iteration will attempt to contruct a line [passing through + // our floating polygon] that will be used partition "origin_face" . + // NOTE: the reason we have a while loop is because it allows us to test several possible lines + // with-which "origin_face" can be partitioned. Some lines may not usable because they pass through + // a vertex of the floating polygon or a vertex the "origin_face" - in which case GP will be + // violated. + // + + bool haveSegmentOnFP = false; // the current pair of floating polygon edges worked! + + // the line segment constructed from midpoints of two edges of the + // floating polygon + std::pair fpSegment; + + while (!fp_edge_pair_priority_queue.empty() && !haveSegmentOnFP) { + + const std::pair fpEdgePairCur = fp_edge_pair_priority_queue.top(); + fp_edge_pair_priority_queue.pop(); + + const vec2 fpEdge0Midpoint = fp_get_edge_midpoint(fpEdgePairCur.first); + const vec2 fpEdge1Midpoint = fp_get_edge_midpoint(fpEdgePairCur.second); + + // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + // if the line intersects/passes through a vertex in "origin_face" or a vertex in + // the floating polygon then try another edge pair. + + auto anyPointIsOnLine = [&]( + const vec2& segStart, + const vec2& segEnd, + const std::vector& polyVerts) -> bool { + double predResult(0xdeadbeef); + for (std::vector::const_iterator it = polyVerts.cbegin(); it != polyVerts.cend(); ++it) { + + bool are_collinear = collinear(segStart, segEnd, (*it), predResult); + // last ditch attempt to prevent the possibility of creating a partitioning + // edge that more-or-less passes through a vertex (of origin-face or the floatig poly itself) + // see: test41 + const double epsilon = 1e-6; + if (are_collinear || (!are_collinear && epsilon > std::fabs(predResult))) { + return true; + } + } + return false; + }; // end lambda + + // do we have general position? i.e. line segment does not pass through a vertex of the + // floating polygon and "origin_face" + bool haveGPOnFP = !anyPointIsOnLine(fpEdge0Midpoint, fpEdge1Midpoint, floating_poly_vertices_2d); + bool haveGPOnOriginFace = !anyPointIsOnLine(fpEdge0Midpoint, fpEdge1Midpoint, origin_face_vertices_2d); + bool haveGP = haveGPOnFP && haveGPOnOriginFace; + + if (haveGP /*|| true*/) { + haveSegmentOnFP = true; + fpSegment.first = fpEdge1Midpoint; + fpSegment.second = fpEdge0Midpoint; + } + + } // while (fp_edge_pair_priority_queue.size() > 0 && successivelyPartitionedOriginFaceWithCurrentEdgePair == false) { + + if (!haveSegmentOnFP) { + // OH OH! + // You have encountered an extremely rare problem case. + // Email the developers (there is a solution but it requires numerical perturbation on "fpSegment"). + throw std::logic_error("Floating-polygon partitioning step could not find a usable fpSegment"); + } + + // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + // At this point we have a valid line segment with which we can proceed to + // partition the "origin_mesh". + + // Now we compute intersection points between every edge in "origin_face" and + // our segment (treating "fpSegment" as an infinitely-long line) + + const int originFaceVertexCount = (int)origin_face_vertices_3d.size(); + MCUT_ASSERT(originFaceVertexCount >= 3); + const int originFaceEdgeCount = originFaceVertexCount; + + // this maps stores the intersection points between our line segment and the + // edges of "origin_face" + std::vector< + // information about "origin_face" edge that is to be split + std::pair< + // index of an edge (i.e. index in the halfedge-list, where this list is defined w.r.t the + // order of halfedges_around_face(origin_face) + int, + std::pair< + vec2, // intersection point coords + double // parameter value (t) of intersection point along our edge (used to recover 3D coords) + >>> + originFaceIntersectedEdgeInfo; + + // ************************************************************************************************************* + // NOTE: "origFaceEdgeIter==0" corresponds to the second halfedge in the list returned by + // "get_halfedges_around_face(origin_face)". + // This is because "get_halfedges_around_face" builds the list of vertices by storing the target (not source) of + // each halfedge of a given face. + // ************************************************************************************************************* + + // for each edge in "origin_face" + for (int origFaceEdgeIter = 0; origFaceEdgeIter < originFaceEdgeCount; ++origFaceEdgeIter) { + + const vec2& origFaceEdgeV0 = SAFE_ACCESS(origin_face_vertices_2d, ((size_t)origFaceEdgeIter) + 0); + const vec2& origFaceEdgeV1 = SAFE_ACCESS(origin_face_vertices_2d, ((origFaceEdgeIter) + 1) % originFaceVertexCount); + + const double garbageVal(0xdeadbeef); + vec2 intersectionPoint(garbageVal); + + double origFaceEdgeParam; + double fpEdgeParam; + + char intersectionResult = compute_segment_intersection( + origFaceEdgeV0, origFaceEdgeV1, fpSegment.first, fpSegment.second, intersectionPoint, origFaceEdgeParam, fpEdgeParam); + + // These assertion must hold since, by construction, "fpSegment" (computed from two edges + // of the floating polygon) partitions the floating polygon which lies inside the area + // of "origin_face". + // Thus "fpSegment" can never intersect any half|edge/segment of "origin_face". It is the + // infinite-line represented by the "fpSegment" that can intersect edges of "origin_face". + MCUT_ASSERT(intersectionResult != '1'); // implies segment-segment intersection + MCUT_ASSERT(intersectionResult != 'v'); // implies that at-least one vertex of one segment touches the other + MCUT_ASSERT(intersectionResult != 'e'); // implies that segments collinearly overlap + + if ( + // intersection point was successively computed i.e. the infinite-line of "fpSegment" intersected the edge of "origin_face" (somewhere including outside of "origin_face") + (intersectionPoint.x() != garbageVal && intersectionPoint.y() != garbageVal) && + // no actual segment-segment intersection exists, which is what we want + intersectionResult == '0') { + originFaceIntersectedEdgeInfo.push_back(std::make_pair(origFaceEdgeIter, std::make_pair(intersectionPoint, origFaceEdgeParam))); + } + } // for (int origFaceEdgeIter = 0; origFaceEdgeIter < originFaceEdgeCount; ++origFaceEdgeIter) { + + // compute mid-point of "fpSegment", which we will used to find closest intersection points + + const vec2 fpSegmentMidPoint( + (fpSegment.first.x() + fpSegment.second.x()) * double(0.5), // + (fpSegment.first.y() + fpSegment.second.y()) * double(0.5)); + + // Get the two closest [valid] intersection points to "fpSegmentMidPoint". + // We do this by sorting elements of "originFaceIntersectedEdgeInfo" by the distance + // of their respective intersection point from "fpSegmentMidPoint". We skip intersection + // points that do not lie on an edge of "origin_face" because they introduce ambiguities + // and that they are technically not usable (i.e. they are outside "origin_face"). + + std::sort(originFaceIntersectedEdgeInfo.begin(), originFaceIntersectedEdgeInfo.end(), + [&](const std::pair>& a, // + const std::pair>& b) { + double aDist(std::numeric_limits::max()); // bias toward points inside polygon + // char aOnEdge = compute_point_in_polygon_test( + // a.second.first, + // origin_face_vertices_2d.data(), + // (int)origin_face_vertices_2d.size()); + + bool aOnEdge = (double(.0) <= a.second.second && double(1.) >= a.second.second); + // for (int i = 0; i < (int)origin_face_vertices_2d.size(); ++i) { + // int i0 = i; + // int i1 = (i0 + 1) % (int)origin_face_vertices_2d.size(); + // if (collinear(origin_face_vertices_2d[i0], origin_face_vertices_2d[i1], a.second.first)) { + // aOnEdge = true; + // break; + // } + // } + + if (aOnEdge) { + const vec2 aVec = a.second.first - fpSegmentMidPoint; + aDist = squared_length(aVec); + } + + double bDist(std::numeric_limits::max()); + // char bOnEdge = compute_point_in_polygon_test( + // b.second.first, + // origin_face_vertices_2d.data(), + // (int)origin_face_vertices_2d.size()); + bool bOnEdge = (double(.0) <= b.second.second && double(1.) >= b.second.second); + + // for (int i = 0; i < (int)origin_face_vertices_2d.size(); ++i) { + // int i0 = i; + // int i1 = (i0 + 1) % (int)origin_face_vertices_2d.size(); + // if (collinear(origin_face_vertices_2d[i0], origin_face_vertices_2d[i1], b.second.first)) { + // bOnEdge = true; + // break; + // } + // } + + if (bOnEdge) { + const vec2 bVec = b.second.first - fpSegmentMidPoint; + bDist = squared_length(bVec); + } + + return aDist < bDist; + }); + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + // At this point we have all information necessary to partition "origin_face" using + // the two closest intersection points to "fpSegmentMidPoint". + // + + // this std::vector stores the faces that use an edge that will be partitioned + std::vector replaced_input_mesh_faces = { origin_face }; + + MCUT_ASSERT(originFaceIntersectedEdgeInfo.size() >= 2); // we partition atleast two edges of origin_face [always!] + + // origFaceEdge0: This is the first edge in the list after sorting. + // --------------------------------------------------------------- + + const std::pair>& originFaceIntersectedEdge0Info = originFaceIntersectedEdgeInfo[0]; // first elem + const int origFaceEdge0Idx = originFaceIntersectedEdge0Info.first; + const double& origFaceEdge0IntPointEqnParam = originFaceIntersectedEdge0Info.second.second; + + // NOTE: minus-1 since "get_vertices_around_face(origin_face)" builds a list using halfedge target vertices + // See the starred note above + int halfedgeIdx = origFaceEdge0Idx; // wrap_integer(origFaceEdge0Idx - 1, 0, (int)originFaceEdgeCount - 1); //(origFaceEdge0Idx + 1) % originFaceEdgeCount; + const hd_t origFaceEdge0Halfedge = SAFE_ACCESS(origin_face_halfedges, halfedgeIdx); + MCUT_ASSERT(origin_face == parent_face_hmesh_ptr->face(origFaceEdge0Halfedge)); + const ed_t origFaceEdge0Descr = parent_face_hmesh_ptr->edge(origFaceEdge0Halfedge); + const vd_t origFaceEdge0HalfedgeSrcDescr = parent_face_hmesh_ptr->source(origFaceEdge0Halfedge); + const vd_t origFaceEdge0HalfedgeTgtDescr = parent_face_hmesh_ptr->target(origFaceEdge0Halfedge); + + // query src and tgt coords and build edge vector (i.e. "tgt - src"), which is in 3d + const vec3& origFaceEdge0HalfedgeSrc = parent_face_hmesh_ptr->vertex(origFaceEdge0HalfedgeSrcDescr); + const vec3& origFaceEdge0HalfedgeTgt = parent_face_hmesh_ptr->vertex(origFaceEdge0HalfedgeTgtDescr); + + // infer 3D intersection point along edge using "origFaceEdge0IntPointEqnParam" + const vec3 origFaceEdge0Vec = (origFaceEdge0HalfedgeTgt - origFaceEdge0HalfedgeSrc); + const vec3 origFaceEdge0IntPoint3d = origFaceEdge0HalfedgeSrc + (origFaceEdge0Vec * origFaceEdge0IntPointEqnParam); + // TODO: ensure that "origFaceEdge0IntPoint3d" lies on the plane of "origFace", this is a source of many problems""" + const hd_t origFaceEdge0HalfedgeOpp = parent_face_hmesh_ptr->opposite(origFaceEdge0Halfedge); + const fd_t origFaceEdge0HalfedgeOppFace = parent_face_hmesh_ptr->face(origFaceEdge0HalfedgeOpp); + + if (origFaceEdge0HalfedgeOppFace != hmesh_t::null_face()) { // exists + // this check is needed in the case that both partitioned edges in "origin_face" + // are incident to the same two faces + const bool contained = std::find(replaced_input_mesh_faces.cbegin(), replaced_input_mesh_faces.cend(), origFaceEdge0HalfedgeOppFace) != replaced_input_mesh_faces.cend(); + if (!contained) { + replaced_input_mesh_faces.push_back(origFaceEdge0HalfedgeOppFace); + } + } + + // origFaceEdge1: This is the second edge in the list after sorting. + // --------------------------------------------------------------- + + const std::pair>& originFaceIntersectedEdge1Info = originFaceIntersectedEdgeInfo[1]; // second elem + const int origFaceEdge1Idx = originFaceIntersectedEdge1Info.first; + const double& origFaceEdge1IntPointEqnParam = originFaceIntersectedEdge1Info.second.second; + + halfedgeIdx = origFaceEdge1Idx; /// wrap_integer(origFaceEdge1Idx - 1, 0, (int)originFaceEdgeCount - 1); // (origFaceEdge1Idx + 1) % originFaceEdgeCount; + const hd_t origFaceEdge1Halfedge = SAFE_ACCESS(origin_face_halfedges, halfedgeIdx); + MCUT_ASSERT(origin_face == parent_face_hmesh_ptr->face(origFaceEdge1Halfedge)); + const ed_t origFaceEdge1Descr = parent_face_hmesh_ptr->edge(origFaceEdge1Halfedge); + const vd_t origFaceEdge1HalfedgeSrcDescr = parent_face_hmesh_ptr->source(origFaceEdge1Halfedge); + const vd_t origFaceEdge1HalfedgeTgtDescr = parent_face_hmesh_ptr->target(origFaceEdge1Halfedge); + + // query src and tgt positions and build vector tgt - src + const vec3& origFaceEdge1HalfedgeSrc = parent_face_hmesh_ptr->vertex(origFaceEdge1HalfedgeSrcDescr); + const vec3& origFaceEdge1HalfedgeTgt = parent_face_hmesh_ptr->vertex(origFaceEdge1HalfedgeTgtDescr); + + // infer intersection point in 3d using "origFaceEdge0IntPointEqnParam" + const vec3 origFaceEdge1Vec = (origFaceEdge1HalfedgeTgt - origFaceEdge1HalfedgeSrc); + const vec3 origFaceEdge1IntPoint3d = origFaceEdge1HalfedgeSrc + (origFaceEdge1Vec * origFaceEdge1IntPointEqnParam); + + const hd_t origFaceEdge1HalfedgeOpp = parent_face_hmesh_ptr->opposite(origFaceEdge1Halfedge); + const fd_t origFaceEdge1HalfedgeOppFace = parent_face_hmesh_ptr->face(origFaceEdge1HalfedgeOpp); + + if (origFaceEdge1HalfedgeOppFace != hmesh_t::null_face()) { // exists + const bool contained = std::find(replaced_input_mesh_faces.cbegin(), replaced_input_mesh_faces.cend(), origFaceEdge1HalfedgeOppFace) != replaced_input_mesh_faces.cend(); + if (!contained) { + replaced_input_mesh_faces.push_back(origFaceEdge1HalfedgeOppFace); + } + } + + // gather halfedges of each neighbouring face of "origin_face" that is to be replaced + std::unordered_map> replacedOrigFaceNeighbourToOldHalfedges; + + for (std::vector::const_iterator it = replaced_input_mesh_faces.cbegin(); it != replaced_input_mesh_faces.cend(); ++it) { + if (*it == origin_face) { + continue; + } + replacedOrigFaceNeighbourToOldHalfedges[*it] = parent_face_hmesh_ptr->get_halfedges_around_face(*it); + } + + // ::::::::::::::::::::::::::::::::::::::::::::::::::::: + //** add new intersection points into parent_face_hmesh_ptr + + const vd_t origFaceEdge0IntPoint3dDescr = parent_face_hmesh_ptr->add_vertex(origFaceEdge0IntPoint3d); + MCUT_ASSERT(new_poly_partition_vertices.count(origFaceEdge0IntPoint3dDescr) == 0); + new_poly_partition_vertices[origFaceEdge0IntPoint3dDescr] = origFaceEdge0IntPoint3d; + + const vd_t origFaceEdge1IntPoint3dDescr = parent_face_hmesh_ptr->add_vertex(origFaceEdge1IntPoint3d); + MCUT_ASSERT(new_poly_partition_vertices.count(origFaceEdge1IntPoint3dDescr) == 0); + new_poly_partition_vertices[origFaceEdge1IntPoint3dDescr] = origFaceEdge1IntPoint3d; + + // ::::::::::: + //** add edges + + // halfedge between the intersection points + const hd_t intPointHalfedgeDescr = parent_face_hmesh_ptr->add_edge(origFaceEdge0IntPoint3dDescr, origFaceEdge1IntPoint3dDescr); + + // partitioning edges for origFaceEdge0 + const hd_t origFaceEdge0FirstNewHalfedgeDescr = parent_face_hmesh_ptr->add_edge(origFaceEdge0HalfedgeSrcDescr, origFaceEdge0IntPoint3dDescr); // o --> x + const hd_t origFaceEdge0SecondNewHalfedgeDescr = parent_face_hmesh_ptr->add_edge(origFaceEdge0IntPoint3dDescr, origFaceEdge0HalfedgeTgtDescr); // x --> o + + // partitioning edges for origFaceEdge1 + const hd_t origFaceEdge1FirstNewHalfedgeDescr = parent_face_hmesh_ptr->add_edge(origFaceEdge1HalfedgeSrcDescr, origFaceEdge1IntPoint3dDescr); // o--> x + const hd_t origFaceEdge1SecondNewHalfedgeDescr = parent_face_hmesh_ptr->add_edge(origFaceEdge1IntPoint3dDescr, origFaceEdge1HalfedgeTgtDescr); // x --> o + + // We will now re-trace the face that are incident to the partitioned edges to create + // new faces. + std::unordered_map> replacedOrigFaceNeighbourToNewHalfedges; + + // NOTE: first we retrace the neighbouring polygons that shared a partitioned edge with "origin_face". + // These are somewhat easier to deal with first because a fixed set of steps can be followed with a simple for-loop. + + // for each neighbouring face (w.r.t. "origin_face") to be replaced + for (std::unordered_map>::const_iterator i = replacedOrigFaceNeighbourToOldHalfedges.cbegin(); + i != replacedOrigFaceNeighbourToOldHalfedges.cend(); + ++i) { + + fd_t face = i->first; + MCUT_ASSERT(face != origin_face); // avoid complex case here, where we need to partition the polygon in two. We'll handle that later. + + const std::vector& oldHalfedges = i->second; + + // for each halfedge of face + for (std::vector::const_iterator j = oldHalfedges.cbegin(); j != oldHalfedges.cend(); ++j) { + + const hd_t oldHalfedge = *j; + hd_t newHalfedge = hmesh_t::null_halfedge(); + const ed_t oldHalfedgeEdge = parent_face_hmesh_ptr->edge(oldHalfedge); + + // is the halfedge part of an edge that is to be partitioned...? + + if (oldHalfedgeEdge == origFaceEdge0Descr) { + hd_t firstNewHalfedge = parent_face_hmesh_ptr->opposite(origFaceEdge0SecondNewHalfedgeDescr); + replacedOrigFaceNeighbourToNewHalfedges[face].push_back(firstNewHalfedge); + hd_t secondNewHalfedge = parent_face_hmesh_ptr->opposite(origFaceEdge0FirstNewHalfedgeDescr); + replacedOrigFaceNeighbourToNewHalfedges[face].push_back(secondNewHalfedge); + } else if (oldHalfedgeEdge == origFaceEdge1Descr) { + hd_t firstNewHalfedge = parent_face_hmesh_ptr->opposite(origFaceEdge1SecondNewHalfedgeDescr); + replacedOrigFaceNeighbourToNewHalfedges[face].push_back(firstNewHalfedge); + hd_t secondNewHalfedge = parent_face_hmesh_ptr->opposite(origFaceEdge1FirstNewHalfedgeDescr); + replacedOrigFaceNeighbourToNewHalfedges[face].push_back(secondNewHalfedge); + } else { + replacedOrigFaceNeighbourToNewHalfedges[face].push_back(oldHalfedge); // maintain unpartitioned halfedge + } + } // for (std::vector::const_iterator j = oldHalfedges.cbegin(); j != oldHalfedges.cend(); ++j) { + + // remove neighbour face + parent_face_hmesh_ptr->remove_face(i->first); + + // immediately add the updated tracing of the neighbour face so that it maintains the same desciptor! + std::vector faceVertices; + for (std::vector::const_iterator it = replacedOrigFaceNeighbourToNewHalfedges[face].cbegin(); + it != replacedOrigFaceNeighbourToNewHalfedges[face].cend(); ++it) { + const vd_t tgt = parent_face_hmesh_ptr->target(*it); + faceVertices.push_back(tgt); + } + + const fd_t fdescr = parent_face_hmesh_ptr->add_face(faceVertices); + MCUT_ASSERT(fdescr == i->first); + +#if 0 + std::unordered_map::const_iterator fiter = child_to_client_birth_face.find(fdescr); + + bool descrIsMapped = (fiter != child_to_client_birth_face.cend()); + + if (!descrIsMapped) { + child_to_client_birth_face[fdescr] = client_hmesh_birth_face; + } +#endif + } // for (std::unordered_map>::const_iterator i = replacedOrigFaceNeighbourToOldHalfedges.cbegin(); i != replacedOrigFaceNeighbourToOldHalfedges.cend(); ++i) { + + // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + // Here we now handle the complex case where we need to partition + // "origin_face" in two new faces. + + parent_face_hmesh_ptr->remove_face(origin_face); // one free slot + + // This queue contains the halfegdes that we'll start to trace our new faces from + // (those connected to our new intersection points) + std::queue origFaceiHalfedges; + origFaceiHalfedges.push(intPointHalfedgeDescr); + origFaceiHalfedges.push(parent_face_hmesh_ptr->opposite(intPointHalfedgeDescr)); + + // this list containing all halfedges along the boundary of "origin_face" + std::vector origFaceBoundaryHalfdges = { // first add the new boundary-edge partitioning halfedges, since we already know them + origFaceEdge0FirstNewHalfedgeDescr, + origFaceEdge0SecondNewHalfedgeDescr, + origFaceEdge1FirstNewHalfedgeDescr, + origFaceEdge1SecondNewHalfedgeDescr + }; + + // .... now we add the remaining boundary halfedges of "origin_face" i.e. those not partitioneds + for (std::vector::const_iterator it = origin_face_halfedges.cbegin(); it != origin_face_halfedges.cend(); ++it) { + if (*it != origFaceEdge0Halfedge && *it != origFaceEdge1Halfedge) { // if its not one of the replaced/partitioned halfedges + origFaceBoundaryHalfdges.push_back(*it); + } + } + + // here we will store the tracing of the two child polygons that result from partitioning "origin_face" + std::vector> origFaceChildPolygons; + + do { // each iteration will trace a child polygon + hd_t childPolyHE_cur = hmesh_t::null_halfedge(); + hd_t childPolyHE_next = origFaceiHalfedges.front(); // start + origFaceiHalfedges.pop(); + + origFaceChildPolygons.push_back(std::vector()); + std::vector& origFaceChildPoly = origFaceChildPolygons.back(); + + const hd_t firstHalfedge = childPolyHE_next; + const vd_t firstHalfedgeSrc = parent_face_hmesh_ptr->source(firstHalfedge); + + do { + childPolyHE_cur = childPolyHE_next; + origFaceChildPoly.push_back(childPolyHE_cur); + const vd_t childPolyHE_curTgt = parent_face_hmesh_ptr->target(childPolyHE_cur); + childPolyHE_cur = hmesh_t::null_halfedge(); + childPolyHE_next = hmesh_t::null_halfedge(); + + if (childPolyHE_curTgt != firstHalfedgeSrc) { + // find next halfedge to continue building the current child polygon + std::vector::const_iterator fiter = std::find_if(origFaceBoundaryHalfdges.cbegin(), origFaceBoundaryHalfdges.cend(), + [&](const hd_t h) { // find a boundary halfedge that can be connected to the current halfedge + const vd_t src = parent_face_hmesh_ptr->source(h); + return src == childPolyHE_curTgt; + }); + + MCUT_ASSERT(fiter != origFaceBoundaryHalfdges.cend()); + + childPolyHE_next = *fiter; + } + + } while (childPolyHE_next != hmesh_t::null_halfedge()); + + MCUT_ASSERT(origFaceChildPoly.size() >= 3); // minimum size of valid polygon (triangle) + + // Add child face into mesh + std::vector origFaceChildPolyVertices; + + for (std::vector::const_iterator hIt = origFaceChildPoly.cbegin(); hIt != origFaceChildPoly.cend(); ++hIt) { + const vd_t tgt = parent_face_hmesh_ptr->target(*hIt); + origFaceChildPolyVertices.push_back(tgt); + } + + const fd_t fdescr = parent_face_hmesh_ptr->add_face(origFaceChildPolyVertices); + MCUT_ASSERT(fdescr != hmesh_t::null_face()); + + if (origFaceChildPolygons.size() == 1) { + // the first child face will re-use the descriptor of "origin_face". + MCUT_ASSERT(fdescr == origin_face); + } + + child_to_client_birth_face[fdescr] = client_hmesh_birth_face; + + } while (origFaceiHalfedges.empty() == false); + + MCUT_ASSERT(origFaceChildPolygons.size() == 2); // "origin_face" shall only ever be partition into two child polygons + + // remove the partitioned/'splitted' edges + parent_face_hmesh_ptr->remove_edge(origFaceEdge0Descr); + parent_face_hmesh_ptr->remove_edge(origFaceEdge1Descr); + + } // for (std::vector::const_iterator floating_poly_info_iter = detected_floating_polygons_iter->second.cbegin(); ... + } // for (std::vector::const_iterator detected_floating_polygons_iter = kernel_output.detected_floating_polygons.cbegin(); ... +} + +extern "C" void preproc( + std::shared_ptr context_ptr, + McFlags dispatchFlags, + const void* pSrcMeshVertices, + const uint32_t* pSrcMeshFaceIndices, + const uint32_t* pSrcMeshFaceSizes, + uint32_t numSrcMeshVertices, + uint32_t numSrcMeshFaces, + const void* pCutMeshVertices, + const uint32_t* pCutMeshFaceIndices, + const uint32_t* pCutMeshFaceSizes, + uint32_t numCutMeshVertices, + uint32_t numCutMeshFaces) noexcept(false) +{ + std::shared_ptr source_hmesh = std::shared_ptr(new hmesh_t); + double source_hmesh_aabb_diag(0.0); + + if (false == client_input_arrays_to_hmesh(context_ptr, dispatchFlags, *source_hmesh.get(), source_hmesh_aabb_diag, pSrcMeshVertices, pSrcMeshFaceIndices, pSrcMeshFaceSizes, numSrcMeshVertices, numSrcMeshFaces)) { + throw std::invalid_argument("invalid source-mesh arrays"); + } + + if (false == check_input_mesh(context_ptr, *source_hmesh.get())) { + throw std::invalid_argument("invalid source-mesh connectivity"); + } + + input_t kernel_input; // kernel/backend inpout + +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + kernel_input.scheduler = &context_ptr->get_shared_compute_threadpool() ; +#endif + + kernel_input.src_mesh = source_hmesh; + + kernel_input.verbose = false; + kernel_input.require_looped_cutpaths = false; + + kernel_input.verbose = static_cast((context_ptr->get_flags() & MC_DEBUG) && (context_ptr->dbgCallbackBitfieldType & MC_DEBUG_SOURCE_KERNEL)); + kernel_input.require_looped_cutpaths = static_cast(dispatchFlags & MC_DISPATCH_REQUIRE_THROUGH_CUTS); + kernel_input.populate_vertex_maps = static_cast(dispatchFlags & MC_DISPATCH_INCLUDE_VERTEX_MAP); + kernel_input.populate_face_maps = static_cast(dispatchFlags & MC_DISPATCH_INCLUDE_FACE_MAP); + + uint32_t dispatch_filter_flag_bitset_all = ( // + MC_DISPATCH_FILTER_FRAGMENT_LOCATION_ABOVE | // + MC_DISPATCH_FILTER_FRAGMENT_LOCATION_BELOW | // + MC_DISPATCH_FILTER_FRAGMENT_LOCATION_UNDEFINED | // + MC_DISPATCH_FILTER_FRAGMENT_SEALING_INSIDE | // + MC_DISPATCH_FILTER_FRAGMENT_SEALING_OUTSIDE | // + MC_DISPATCH_FILTER_FRAGMENT_SEALING_NONE | // + MC_DISPATCH_FILTER_PATCH_INSIDE | // + MC_DISPATCH_FILTER_PATCH_OUTSIDE | // + MC_DISPATCH_FILTER_SEAM_SRCMESH | // + MC_DISPATCH_FILTER_SEAM_CUTMESH); + + const bool dispatchFilteringEnabled = static_cast(dispatchFlags & dispatch_filter_flag_bitset_all); // any + + if (dispatchFilteringEnabled) { // user only wants [some] output connected components + kernel_input.keep_fragments_below_cutmesh = static_cast(dispatchFlags & MC_DISPATCH_FILTER_FRAGMENT_LOCATION_BELOW); + kernel_input.keep_fragments_above_cutmesh = static_cast(dispatchFlags & MC_DISPATCH_FILTER_FRAGMENT_LOCATION_ABOVE); + kernel_input.keep_fragments_sealed_outside = static_cast(dispatchFlags & MC_DISPATCH_FILTER_FRAGMENT_SEALING_OUTSIDE); + kernel_input.keep_fragments_sealed_inside = static_cast(dispatchFlags & MC_DISPATCH_FILTER_FRAGMENT_SEALING_INSIDE); + kernel_input.keep_unsealed_fragments = static_cast(dispatchFlags & MC_DISPATCH_FILTER_FRAGMENT_SEALING_NONE); + kernel_input.keep_fragments_partially_cut = static_cast(dispatchFlags & MC_DISPATCH_FILTER_FRAGMENT_LOCATION_UNDEFINED); + kernel_input.keep_inside_patches = static_cast(dispatchFlags & MC_DISPATCH_FILTER_PATCH_INSIDE); + kernel_input.keep_outside_patches = static_cast(dispatchFlags & MC_DISPATCH_FILTER_PATCH_OUTSIDE); + kernel_input.keep_srcmesh_seam = static_cast(dispatchFlags & MC_DISPATCH_FILTER_SEAM_SRCMESH); + kernel_input.keep_cutmesh_seam = static_cast(dispatchFlags & MC_DISPATCH_FILTER_SEAM_CUTMESH); + } else { // compute all possible types of connected components + kernel_input.keep_fragments_below_cutmesh = true; + kernel_input.keep_fragments_above_cutmesh = true; + kernel_input.keep_fragments_partially_cut = true; + kernel_input.keep_unsealed_fragments = true; + kernel_input.keep_fragments_sealed_outside = true; // mutually exclusive with exhaustive case + kernel_input.keep_fragments_sealed_inside = true; + kernel_input.keep_fragments_sealed_outside_exhaustive = false; + kernel_input.keep_fragments_sealed_inside_exhaustive = false; + kernel_input.keep_inside_patches = true; + kernel_input.keep_outside_patches = true; + kernel_input.keep_srcmesh_seam = true; + kernel_input.keep_cutmesh_seam = true; + } + + kernel_input.enforce_general_position = (0 != (dispatchFlags & MC_DISPATCH_ENFORCE_GENERAL_POSITION)); + + // Construct BVHs + // :::::::::::::: + + context_ptr->dbg_cb(MC_DEBUG_SOURCE_API, MC_DEBUG_TYPE_OTHER, 0, MC_DEBUG_SEVERITY_NOTIFICATION, "Build source-mesh BVH"); + +#if defined(USE_OIBVH) + std::vector> source_hmesh_BVH_aabb_array; + std::vector source_hmesh_BVH_leafdata_array; + std::vector> source_hmesh_face_aabb_array; + build_oibvh( +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + context_ptr->get_shared_compute_threadpool() , +#endif + *source_hmesh.get(), source_hmesh_BVH_aabb_array, source_hmesh_BVH_leafdata_array, source_hmesh_face_aabb_array); +#else + BoundingVolumeHierarchy source_hmesh_BVH; + source_hmesh_BVH.buildTree(source_hmesh); +#endif + context_ptr->dbg_cb(MC_DEBUG_SOURCE_API, MC_DEBUG_TYPE_OTHER, 0, MC_DEBUG_SEVERITY_NOTIFICATION, "Build cut-mesh BVH"); + + /* + NOTE: All variables declared as shared pointers here represent variables that live (on the heap) + until all connected components (that are created during the current mcDispatch call) are destroyed. + Thus, each such connected component will maintain its own (reference counted) pointer. + + These variables are used when populating client output arrays during �mcGetConnectedComponentData�. + One example of this usage is when the client requests a connected component�s face map. In the cases + where polygon partitioning occurs during the respective mcDispatch call then some of these shared_ptrs like + �source_hmesh_child_to_usermesh_birth_face� will be used to generate the correct mapping from + the faces of the connected component to the + client mesh (which will be internally modified due to polygon partitioning). Here the client mesh + corresponds to the input source mesh if the connected component is a fragment, and the cut mesh otherwise. + */ + + // mapping variables from a child face to the parent face in the corresponding input-hmesh face. + // This child face is produced as a result of polygon partition. + std::shared_ptr< // + std::unordered_map< // + fd_t /*child face*/, + fd_t /*parent face in the [user-provided] source mesh*/ + > // + > + source_hmesh_child_to_usermesh_birth_face = std::shared_ptr>(new std::unordered_map); + + std::unordered_map< // + fd_t /*child face*/, + fd_t /*parent face in the [user-provided] cut mesh*/ + > + cut_hmesh_child_to_usermesh_birth_face; + // descriptors and coordinates of new vertices that are added into an input mesh (source mesh or cut mesh) + // in order to carry out partitioning + std::shared_ptr> source_hmesh_new_poly_partition_vertices = std::shared_ptr>(new std::unordered_map); + std::unordered_map cut_hmesh_new_poly_partition_vertices; + + // the number of faces in the source mesh from the last/previous dispatch call + const uint32_t source_hmesh_face_count = numSrcMeshFaces; + uint32_t source_hmesh_face_count_prev = source_hmesh_face_count; + + output_t kernel_output; + + std::shared_ptr cut_hmesh = std::shared_ptr(new hmesh_t); // halfedge representation of the cut-mesh + double cut_hmesh_aabb_diag(0.0); + +#if defined(USE_OIBVH) + std::vector> cut_hmesh_BVH_aabb_array; + std::vector cut_hmesh_BVH_leafdata_array; + std::vector> cut_hmesh_face_face_aabb_array; +#else + BoundingVolumeHierarchy cut_hmesh_BVH; // built later (see below) +#endif + + bool source_or_cut_hmesh_BVH_rebuilt = true; // i.e. used to determine whether we should retraverse BVHs + + std::map> ps_face_to_potentially_intersecting_others; // result of BVH traversal + +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + kernel_output.status.store(status_t::SUCCESS); +#else + kernel_output.status = status_t::SUCCESS; +#endif + + int cut_mesh_perturbation_count = 0; // number of times we have perturbed the cut mesh + int kernel_invocation_counter = -1; // number of times we have called the internal dispatch/intersect function + double numerical_perturbation_constant = 0.0; // = cut_hmesh_aabb_diag * GENERAL_POSITION_ENFORCMENT_CONSTANT; + + // RESOLVE mesh intersections + // :::::::::::::::::::::::::: + + // The following loop-body contains code to do the cutting. The logic resides in a loop + // for 2 reasons: + // 1) the input meshes may not be in "general position" + // 2) the resulting intersection between the input meshes may + // produce "floating polygons" (an input mesh intersects a face of the the other in such a way that none of the edges of this face are severed). + // + // For each reason, we need to modify the input(s) in order to have a valid (proper intersection-permitting) configuration. + // If general position is violated, then we apply numerical perturbation of the cut-mesh. + // And if floating polygons arise, then we partition the suspected face into two new faces with an edge that is guaranteed to be + // severed during the cut. + do { + kernel_invocation_counter++; + + // here we check the reason (if any) for entering the loop body. + // NOTE: the aforementioned 2 reasons could both be false, which will + // be the case during the first iteration (i.e. when "kernel_invocation_counter == 0") +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + bool general_position_assumption_was_violated = ((kernel_output.status.load() == status_t::GENERAL_POSITION_VIOLATION)); + bool floating_polygon_was_detected = kernel_output.status.load() == status_t::DETECTED_FLOATING_POLYGON; +#else + bool general_position_assumption_was_violated = (kernel_output.status == status_t::GENERAL_POSITION_VIOLATION); + bool floating_polygon_was_detected = kernel_output.status == status_t::DETECTED_FLOATING_POLYGON; +#endif + + // Here we reset the kernel execution status +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + kernel_output.status.store(status_t::SUCCESS); +#else + kernel_output.status = status_t::SUCCESS; +#endif + + // the (translation) vector to hold the values with which we will + // carry out numerical perturbation of the cutting surface + vec3 perturbation(0.0, 0.0, 0.0); + + if (general_position_assumption_was_violated) { // i.e. do we need to perturb the cut-mesh? + + MCUT_ASSERT(floating_polygon_was_detected == false); // cannot occur at same time! (see kernel) + + if (cut_mesh_perturbation_count == MAX_PERTUBATION_ATTEMPTS) { + + context_ptr->dbg_cb(MC_DEBUG_SOURCE_KERNEL, MC_DEBUG_TYPE_OTHER, 0, MC_DEBUG_SEVERITY_MEDIUM, kernel_output.logger.get_reason_for_failure()); + + throw std::runtime_error("max perturbation iteratons reached"); + } + + // used by the kernel track if the most-recent perturbation causes the cut-mesh and src-mesh to + // not intersect at all, which means we need to perturb again. + kernel_input.general_position_enforcement_count = cut_mesh_perturbation_count; + + MCUT_ASSERT(numerical_perturbation_constant != double(0.0)); + + static thread_local std::default_random_engine random_engine(1); + static thread_local std::mt19937 mersenne_twister_generator(random_engine()); + static thread_local std::uniform_real_distribution uniform_distribution(-1.0, 1.0); + + for (int i = 0; i < 3; ++i) { + perturbation[i] = uniform_distribution(mersenne_twister_generator) * numerical_perturbation_constant; + } + + cut_mesh_perturbation_count++; + } // if (general_position_assumption_was_violated) { + + if ((cut_mesh_perturbation_count == 0 /*no perturbs required*/ || general_position_assumption_was_violated) && floating_polygon_was_detected == false) { + + // TODO: assume that re-adding elements (vertices and faces) is going to change the order + // from the user-provided order. So we still need to fix the mapping, which may no longer + // be one-to-one as in the case when things do not change. + cut_hmesh->reset(); + + // TODO: the number of cut-mesh faces and vertices may increase due to polygon partitioning + // Therefore: we need to perturb [the updated cut-mesh] i.e. the one containing partitioned polygons + // "pCutMeshFaces" are simply the user provided faces + // We must also use the newly added vertices (coords) due to polygon partitioning as "unperturbed" values + // This will require some intricate mapping + if (false == client_input_arrays_to_hmesh(context_ptr, dispatchFlags, *cut_hmesh.get(), cut_hmesh_aabb_diag, pCutMeshVertices, pCutMeshFaceIndices, pCutMeshFaceSizes, numCutMeshVertices, numCutMeshFaces, ((cut_mesh_perturbation_count == 0) ? NULL : &perturbation))) { + throw std::invalid_argument("invalid cut-mesh arrays"); + } + + numerical_perturbation_constant = cut_hmesh_aabb_diag * GENERAL_POSITION_ENFORCMENT_CONSTANT; + + kernel_input.cut_mesh = cut_hmesh; + + if (cut_mesh_perturbation_count == 0) { // i.e. first time we are invoking kernel intersect function +#if defined(USE_OIBVH) + cut_hmesh_BVH_aabb_array.clear(); + cut_hmesh_BVH_leafdata_array.clear(); + build_oibvh( +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + context_ptr->get_shared_compute_threadpool() , +#endif + *cut_hmesh.get(), cut_hmesh_BVH_aabb_array, cut_hmesh_BVH_leafdata_array, cut_hmesh_face_face_aabb_array, numerical_perturbation_constant); +#else + cut_hmesh_BVH.buildTree(cut_hmesh, numerical_perturbation_constant); +#endif + source_or_cut_hmesh_BVH_rebuilt = true; + } + } + + TIMESTACK_PUSH("partition floating polygons"); + if (floating_polygon_was_detected) { + + MCUT_ASSERT(general_position_assumption_was_violated == false); // cannot occur at same time (GP violation is detected before FPs)! + + // indicates whether a polygon was partitioned on the source mesh + bool source_hmesh_modified = false; + // indicates whether a polygon was partitioned on the cut mesh + bool cut_hmesh_modified = false; + + resolve_floating_polygons( + source_hmesh_modified, + cut_hmesh_modified, + kernel_output.detected_floating_polygons, + source_hmesh_face_count_prev, + *source_hmesh.get(), + *cut_hmesh.get(), + source_hmesh_child_to_usermesh_birth_face.get()[0], + cut_hmesh_child_to_usermesh_birth_face, + source_hmesh_new_poly_partition_vertices.get()[0], + cut_hmesh_new_poly_partition_vertices); + + // :::::::::::::::::::::::::::::::::::::::::::: + // rebuild the BVH of "parent_face_hmesh_ptr" again + + if (source_hmesh_modified) { +#if defined(USE_OIBVH) + source_hmesh_BVH_aabb_array.clear(); + source_hmesh_BVH_leafdata_array.clear(); + build_oibvh( +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + context_ptr->get_shared_compute_threadpool() , +#endif + *source_hmesh.get(), + source_hmesh_BVH_aabb_array, + source_hmesh_BVH_leafdata_array, + source_hmesh_face_aabb_array); +#else + source_hmesh_BVH.buildTree(source_hmesh); +#endif + } + + if (cut_hmesh_modified) { +#if defined(USE_OIBVH) + cut_hmesh_BVH_aabb_array.clear(); + cut_hmesh_BVH_leafdata_array.clear(); + build_oibvh( +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + context_ptr->get_shared_compute_threadpool() , +#endif + *cut_hmesh.get(), + cut_hmesh_BVH_aabb_array, + cut_hmesh_BVH_leafdata_array, + cut_hmesh_face_face_aabb_array, + numerical_perturbation_constant); +#else + cut_hmesh_BVH.buildTree(cut_hmesh, numerical_perturbation_constant); +#endif + } + + source_or_cut_hmesh_BVH_rebuilt = source_hmesh_modified || cut_hmesh_modified; + + MCUT_ASSERT(source_or_cut_hmesh_BVH_rebuilt == true); + + kernel_output.detected_floating_polygons.clear(); + } // if (floating_polygon_was_detected) { + TIMESTACK_POP(); + + // Check for mesh defects + // :::::::::::::::::::::: + + // NOTE: we check for defects here since both input meshes may be modified by the polygon partitioning process above. + // Partitiining is involked after atleast one dispatch call. + context_ptr->dbg_cb(MC_DEBUG_SOURCE_API, MC_DEBUG_TYPE_OTHER, 0, MC_DEBUG_SEVERITY_NOTIFICATION, "Check source-mesh for defects"); + + if (false == check_input_mesh(context_ptr, *source_hmesh.get())) { + throw std::invalid_argument("invalid source-mesh connectivity"); + } + + context_ptr->dbg_cb(MC_DEBUG_SOURCE_API, MC_DEBUG_TYPE_OTHER, 0, MC_DEBUG_SEVERITY_NOTIFICATION, "Check cut-mesh for defects"); + + if (false == check_input_mesh(context_ptr, *cut_hmesh.get())) { + throw std::invalid_argument("invalid cut-mesh connectivity"); + } + + if (source_or_cut_hmesh_BVH_rebuilt) { + // Evaluate BVHs to find polygon pairs that will be tested for intersection + // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + source_or_cut_hmesh_BVH_rebuilt = false; + context_ptr->dbg_cb(MC_DEBUG_SOURCE_API, MC_DEBUG_TYPE_OTHER, 0, MC_DEBUG_SEVERITY_NOTIFICATION, "Find potentially-intersecting polygons"); + + ps_face_to_potentially_intersecting_others.clear(); +#if defined(USE_OIBVH) + intersectOIBVHs(ps_face_to_potentially_intersecting_others, source_hmesh_BVH_aabb_array, source_hmesh_BVH_leafdata_array, cut_hmesh_BVH_aabb_array, cut_hmesh_BVH_leafdata_array); +#else + BoundingVolumeHierarchy::intersectBVHTrees( +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + context_ptr->get_shared_compute_threadpool() , +#endif + ps_face_to_potentially_intersecting_others, + source_hmesh_BVH, + cut_hmesh_BVH, + 0, + source_hmesh.number_of_faces()); + +#endif + + context_ptr->dbg_cb( + MC_DEBUG_SOURCE_API, + MC_DEBUG_TYPE_OTHER, + 0, + MC_DEBUG_SEVERITY_NOTIFICATION, + "Polygon-pairs found = " + std::to_string(ps_face_to_potentially_intersecting_others.size())); + + if (ps_face_to_potentially_intersecting_others.empty()) { + if (general_position_assumption_was_violated && cut_mesh_perturbation_count > 0) { + // perturbation lead to an intersection-free state at the BVH level (and of-course the polygon level). + // We need to perturb again. (The whole cut mesh) +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + kernel_output.status.store(status_t::GENERAL_POSITION_VIOLATION); +#else + kernel_output.status = status_t::GENERAL_POSITION_VIOLATION; +#endif + continue; + } else { + context_ptr->dbg_cb(MC_DEBUG_SOURCE_API, MC_DEBUG_TYPE_OTHER, 0, MC_DEBUG_SEVERITY_NOTIFICATION, "Mesh BVHs do not overlap."); + return; // we are done + } + } + } + + kernel_input.ps_face_to_potentially_intersecting_others = &ps_face_to_potentially_intersecting_others; + +#if defined(USE_OIBVH) + kernel_input.source_hmesh_face_aabb_array_ptr = &source_hmesh_face_aabb_array; + kernel_input.cut_hmesh_face_aabb_array_ptr = &cut_hmesh_face_face_aabb_array; +#else + kernel_input.source_hmesh_BVH = &source_hmesh_BVH; + kernel_input.cut_hmesh_BVH = &cut_hmesh_BVH; +#endif + // Invokee the kernel by calling the internal dispatch function + // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + + source_hmesh_face_count_prev = source_hmesh->number_of_faces(); + + try { + context_ptr->dbg_cb(MC_DEBUG_SOURCE_KERNEL, MC_DEBUG_TYPE_OTHER, 0, MC_DEBUG_SEVERITY_NOTIFICATION, "dispatch kernel"); + dispatch(kernel_output, kernel_input); + } catch (const std::exception& e) { + fprintf(stderr, "fatal kernel exception caught : %s\n", e.what()); + throw e; + } + } while ( +#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) + (kernel_output.status.load() == status_t::GENERAL_POSITION_VIOLATION && kernel_input.enforce_general_position) || // + kernel_output.status.load() == status_t::DETECTED_FLOATING_POLYGON +#else + // general position voliation + (kernel_output.status == status_t::GENERAL_POSITION_VIOLATION && kernel_input.enforce_general_position) || // + // kernel detected a floating polygon and we now need to re-partition the origin polygon (in src mesh or cut-mesh) to then recall dispatch + kernel_output.status == status_t::DETECTED_FLOATING_POLYGON +#endif + ); + + if (convert(kernel_output.status) != McResult::MC_NO_ERROR) { + + context_ptr->dbg_cb( + MC_DEBUG_SOURCE_KERNEL, + MC_DEBUG_TYPE_ERROR, + 0, + MC_DEBUG_SEVERITY_HIGH, + to_string(kernel_output.status) + " : " + kernel_output.logger.get_reason_for_failure()); + + throw std::runtime_error("incomplete kernel execution"); + } + + TIMESTACK_PUSH("create face partition maps"); + // NOTE: face descriptors in "cut_hmesh_child_to_usermesh_birth_face", need to be offsetted + // by the number of [internal] source-mesh faces/vertices. This is to ensure consistency with + // the kernel's data-mapping and make it easier for us to map vertex and face descriptors in + // connected components to the correct instance in the client input meshes. + // This offsetting follows a design choice used in the kernel that ("ps-faces" belonging to + // cut-mesh start [after] the source-mesh faces). + std::shared_ptr> cut_hmesh_child_to_usermesh_birth_face_OFFSETTED = std::shared_ptr>(new std::unordered_map); // cut_hmesh_child_to_usermesh_birth_face; + cut_hmesh_child_to_usermesh_birth_face_OFFSETTED->reserve(cut_hmesh_child_to_usermesh_birth_face.size()); + + for (std::unordered_map::iterator i = cut_hmesh_child_to_usermesh_birth_face.begin(); + i != cut_hmesh_child_to_usermesh_birth_face.end(); ++i) { + fd_t offsettedDescr = fd_t(i->first + source_hmesh->number_of_faces()); + (cut_hmesh_child_to_usermesh_birth_face_OFFSETTED.get()[0])[offsettedDescr] = fd_t(i->second + source_hmesh_face_count_prev); // apply offset + // i->second = fd_t(i->second + source_hmesh_face_count_prev); // apply offset + } + + std::shared_ptr> cut_hmesh_new_poly_partition_vertices_OFFSETTED = std::shared_ptr>(new std::unordered_map); + cut_hmesh_new_poly_partition_vertices_OFFSETTED->reserve(cut_hmesh_new_poly_partition_vertices.size()); + + for (std::unordered_map::const_iterator i = cut_hmesh_new_poly_partition_vertices.begin(); + i != cut_hmesh_new_poly_partition_vertices.end(); ++i) { + vd_t offsettedDescr = vd_t(i->first + source_hmesh->number_of_vertices()); + (cut_hmesh_new_poly_partition_vertices_OFFSETTED.get()[0])[offsettedDescr] = i->second; // apply offset + } + + TIMESTACK_POP(); + + // + // sealed-fragment connected components + // + TIMESTACK_PUSH("store sealed-fragment connected components"); + for (std::map>>>::const_iterator i = kernel_output.connected_components.cbegin(); + i != kernel_output.connected_components.cend(); + ++i) { + + for (std::map>>::const_iterator j = i->second.cbegin(); + j != i->second.cend(); + ++j) { + + // const std::string cs_patch_loc_str = to_string(j->first); + + for (std::vector>::const_iterator k = j->second.cbegin(); k != j->second.cend(); ++k) { + + std::shared_ptr cc_ptr = std::shared_ptr(new fragment_cc_t, fn_delete_cc); + + MCUT_ASSERT(cc_ptr != nullptr); + + std::shared_ptr asFragPtr = std::dynamic_pointer_cast(cc_ptr); + + MCUT_ASSERT(asFragPtr != nullptr); + + asFragPtr->m_user_handle = reinterpret_cast(g_objects_counter++); + asFragPtr->type = MC_CONNECTED_COMPONENT_TYPE_FRAGMENT; + asFragPtr->fragmentLocation = convert(i->first); + asFragPtr->patchLocation = convert(j->first); + + MCUT_ASSERT(asFragPtr->patchLocation != MC_PATCH_LOCATION_UNDEFINED); + + asFragPtr->srcMeshSealType = McFragmentSealType::MC_FRAGMENT_SEAL_TYPE_COMPLETE; + + asFragPtr->kernel_hmesh_data = *k; + + asFragPtr->source_hmesh_child_to_usermesh_birth_face = source_hmesh_child_to_usermesh_birth_face; + asFragPtr->cut_hmesh_child_to_usermesh_birth_face = cut_hmesh_child_to_usermesh_birth_face_OFFSETTED; + asFragPtr->source_hmesh_new_poly_partition_vertices = source_hmesh_new_poly_partition_vertices; + asFragPtr->cut_hmesh_new_poly_partition_vertices = cut_hmesh_new_poly_partition_vertices_OFFSETTED; + + asFragPtr->internal_sourcemesh_vertex_count = source_hmesh->number_of_vertices(); + asFragPtr->client_sourcemesh_vertex_count = numSrcMeshVertices; + asFragPtr->internal_sourcemesh_face_count = source_hmesh->number_of_faces(); + asFragPtr->client_sourcemesh_face_count = numSrcMeshFaces; // or source_hmesh_face_count + + context_ptr->connected_components.push_front(cc_ptr); // copy the connected component ptr into the context object + } + } + } + TIMESTACK_POP(); + + // + // unsealed connected components (fragements) + // + TIMESTACK_PUSH("store unsealed connected components"); + for (std::map>>::const_iterator i = kernel_output.unsealed_cc.cbegin(); + i != kernel_output.unsealed_cc.cend(); + ++i) { // for each cc location flag (above/below/undefined) + + for (std::vector>::const_iterator j = i->second.cbegin(); j != i->second.cend(); ++j) { // for each mesh + + std::shared_ptr cc_ptr = std::shared_ptr(new fragment_cc_t, fn_delete_cc); + + MCUT_ASSERT(cc_ptr != nullptr); + + std::shared_ptr asFragPtr = std::dynamic_pointer_cast(cc_ptr); + + MCUT_ASSERT(asFragPtr != nullptr); + + asFragPtr->m_user_handle = reinterpret_cast(g_objects_counter++); + + asFragPtr->type = MC_CONNECTED_COMPONENT_TYPE_FRAGMENT; + asFragPtr->fragmentLocation = convert(i->first); + asFragPtr->patchLocation = McPatchLocation::MC_PATCH_LOCATION_UNDEFINED; + asFragPtr->srcMeshSealType = McFragmentSealType::MC_FRAGMENT_SEAL_TYPE_NONE; + + asFragPtr->kernel_hmesh_data = (*j); + + asFragPtr->source_hmesh_child_to_usermesh_birth_face = source_hmesh_child_to_usermesh_birth_face; + asFragPtr->cut_hmesh_child_to_usermesh_birth_face = cut_hmesh_child_to_usermesh_birth_face_OFFSETTED; + asFragPtr->source_hmesh_new_poly_partition_vertices = source_hmesh_new_poly_partition_vertices; + asFragPtr->cut_hmesh_new_poly_partition_vertices = cut_hmesh_new_poly_partition_vertices_OFFSETTED; + + asFragPtr->internal_sourcemesh_vertex_count = source_hmesh->number_of_vertices(); + asFragPtr->client_sourcemesh_vertex_count = numSrcMeshVertices; + asFragPtr->internal_sourcemesh_face_count = source_hmesh->number_of_faces(); + asFragPtr->client_sourcemesh_face_count = numSrcMeshFaces; // or source_hmesh_face_count + + context_ptr->connected_components.push_front(cc_ptr); // copy the connected component ptr into the context object + } + } + TIMESTACK_POP(); + + // inside patches + TIMESTACK_PUSH("store interior patches"); + const std::vector>& insidePatches = kernel_output.inside_patches[cm_patch_winding_order_t::DEFAULT]; + + for (std::vector>::const_iterator it = insidePatches.cbegin(); + it != insidePatches.cend(); + ++it) { + + std::shared_ptr cc_ptr = std::shared_ptr(new patch_cc_t, fn_delete_cc); + + MCUT_ASSERT(cc_ptr != nullptr); + + std::shared_ptr asPatchPtr = std::dynamic_pointer_cast(cc_ptr); + + MCUT_ASSERT(asPatchPtr != nullptr); + + asPatchPtr->m_user_handle = reinterpret_cast(g_objects_counter++); +#if 0 + // std::shared_ptr patchConnComp = std::unique_ptr(new patch_cc_t, fn_delete_cc); + // McConnectedComponent clientHandle = reinterpret_cast(patchConnComp.get()); + // context_ptr->connected_components.emplace(clientHandle, std::move(patchConnComp)); + const McConnectedComponent handle = reinterpret_cast(g_objects_counter++); + + // allocate internal context object (including associated threadpool etc.) + context_ptr->connected_components.add_or_update_mapping(handle, std::shared_ptr(new patch_cc_t, fn_delete_cc)); + + std::shared_ptr cc_ptr = context_ptr->connected_components.value_for(handle); + + MCUT_ASSERT(cc_ptr != nullptr); + patch_cc_t* asPatchPtr = dynamic_cast(cc_ptr.get()); +#endif + asPatchPtr->type = MC_CONNECTED_COMPONENT_TYPE_PATCH; + asPatchPtr->patchLocation = MC_PATCH_LOCATION_INSIDE; + + asPatchPtr->kernel_hmesh_data = (*it); + + asPatchPtr->source_hmesh_child_to_usermesh_birth_face = source_hmesh_child_to_usermesh_birth_face; + asPatchPtr->cut_hmesh_child_to_usermesh_birth_face = cut_hmesh_child_to_usermesh_birth_face_OFFSETTED; + asPatchPtr->source_hmesh_new_poly_partition_vertices = source_hmesh_new_poly_partition_vertices; + asPatchPtr->cut_hmesh_new_poly_partition_vertices = cut_hmesh_new_poly_partition_vertices_OFFSETTED; + + asPatchPtr->internal_sourcemesh_vertex_count = source_hmesh->number_of_vertices(); + asPatchPtr->client_sourcemesh_vertex_count = numSrcMeshVertices; + asPatchPtr->internal_sourcemesh_face_count = source_hmesh->number_of_faces(); + asPatchPtr->client_sourcemesh_face_count = numSrcMeshFaces; // or source_hmesh_face_count + + context_ptr->connected_components.push_front(cc_ptr); // copy the connected component ptr into the context object + } + TIMESTACK_POP(); + + // outside patches + TIMESTACK_PUSH("store exterior patches"); + const std::vector>& outsidePatches = kernel_output.outside_patches[cm_patch_winding_order_t::DEFAULT]; + + for (std::vector>::const_iterator it = outsidePatches.cbegin(); it != outsidePatches.cend(); ++it) { + std::shared_ptr cc_ptr = std::shared_ptr(new patch_cc_t, fn_delete_cc); + + MCUT_ASSERT(cc_ptr != nullptr); + + std::shared_ptr asPatchPtr = std::dynamic_pointer_cast(cc_ptr); + + MCUT_ASSERT(asPatchPtr != nullptr); + + asPatchPtr->m_user_handle = reinterpret_cast(g_objects_counter++); +#if 0 + /// std::shared_ptr patchConnComp = std::unique_ptr(new patch_cc_t, fn_delete_cc); + // McConnectedComponent clientHandle = reinterpret_cast(patchConnComp.get()); + // context_ptr->connected_components.emplace(clientHandle, std::move(patchConnComp)); + const McConnectedComponent handle = reinterpret_cast(g_objects_counter++); + + // allocate internal context object (including associated threadpool etc.) + context_ptr->connected_components.add_or_update_mapping(handle, std::shared_ptr(new patch_cc_t, fn_delete_cc)); + + std::shared_ptr cc_ptr = context_ptr->connected_components.value_for(handle); + + MCUT_ASSERT(cc_ptr != nullptr); + patch_cc_t* asPatchPtr = dynamic_cast(cc_ptr.get()); +#endif + asPatchPtr->type = MC_CONNECTED_COMPONENT_TYPE_PATCH; + asPatchPtr->patchLocation = MC_PATCH_LOCATION_OUTSIDE; + asPatchPtr->kernel_hmesh_data = (*it); + + asPatchPtr->source_hmesh_child_to_usermesh_birth_face = source_hmesh_child_to_usermesh_birth_face; + asPatchPtr->cut_hmesh_child_to_usermesh_birth_face = cut_hmesh_child_to_usermesh_birth_face_OFFSETTED; + asPatchPtr->source_hmesh_new_poly_partition_vertices = source_hmesh_new_poly_partition_vertices; + asPatchPtr->cut_hmesh_new_poly_partition_vertices = cut_hmesh_new_poly_partition_vertices_OFFSETTED; + + asPatchPtr->internal_sourcemesh_vertex_count = source_hmesh->number_of_vertices(); + asPatchPtr->client_sourcemesh_vertex_count = numSrcMeshVertices; + asPatchPtr->internal_sourcemesh_face_count = source_hmesh->number_of_faces(); + asPatchPtr->client_sourcemesh_face_count = numSrcMeshFaces; // or source_hmesh_face_count + + context_ptr->connected_components.push_front(cc_ptr); // copy the connected component ptr into the context object + } + TIMESTACK_POP(); + + // seam connected components + // ------------------------- + + // NOTE: seam meshes are not available if there was a partial cut intersection (due to constraints imposed by halfedge construction rules). + + // src mesh + + if (kernel_output.seamed_src_mesh != nullptr && kernel_output.seamed_src_mesh->mesh->number_of_faces() > 0) { + TIMESTACK_PUSH("store source-mesh seam"); + + std::shared_ptr cc_ptr = std::shared_ptr(new seam_cc_t, fn_delete_cc); + + MCUT_ASSERT(cc_ptr != nullptr); + + std::shared_ptr asSrcMeshSeamPtr = std::dynamic_pointer_cast(cc_ptr); + + MCUT_ASSERT(asSrcMeshSeamPtr != nullptr); + + asSrcMeshSeamPtr->m_user_handle = reinterpret_cast(g_objects_counter++); +#if 0 + // std::shared_ptr srcMeshSeam = std::unique_ptr(new seam_cc_t, fn_delete_cc); + // McConnectedComponent clientHandle = reinterpret_cast(srcMeshSeam.get()); + // context_ptr->connected_components.emplace(clientHandle, std::move(srcMeshSeam)); + // allocate internal context object (including associated threadpool etc.) + const McConnectedComponent handle = reinterpret_cast(g_objects_counter++); + + context_ptr->connected_components.add_or_update_mapping(handle, std::shared_ptr(new seam_cc_t, fn_delete_cc)); + + std::shared_ptr cc_ptr = context_ptr->connected_components.value_for(handle); + + MCUT_ASSERT(cc_ptr != nullptr); + seam_cc_t* asSrcMeshSeamPtr = dynamic_cast(cc_ptr.get()); +#endif + + asSrcMeshSeamPtr->type = MC_CONNECTED_COMPONENT_TYPE_SEAM; + asSrcMeshSeamPtr->origin = MC_SEAM_ORIGIN_SRCMESH; + + asSrcMeshSeamPtr->kernel_hmesh_data = (kernel_output.seamed_src_mesh); + + asSrcMeshSeamPtr->source_hmesh_child_to_usermesh_birth_face = source_hmesh_child_to_usermesh_birth_face; + asSrcMeshSeamPtr->cut_hmesh_child_to_usermesh_birth_face = cut_hmesh_child_to_usermesh_birth_face_OFFSETTED; + asSrcMeshSeamPtr->source_hmesh_new_poly_partition_vertices = source_hmesh_new_poly_partition_vertices; + asSrcMeshSeamPtr->cut_hmesh_new_poly_partition_vertices = cut_hmesh_new_poly_partition_vertices_OFFSETTED; + + asSrcMeshSeamPtr->internal_sourcemesh_vertex_count = source_hmesh->number_of_vertices(); + asSrcMeshSeamPtr->client_sourcemesh_vertex_count = numSrcMeshVertices; + asSrcMeshSeamPtr->internal_sourcemesh_face_count = source_hmesh->number_of_faces(); + asSrcMeshSeamPtr->client_sourcemesh_face_count = numSrcMeshFaces; // or source_hmesh_face_count + + context_ptr->connected_components.push_front(cc_ptr); // copy the connected component ptr into the context object + + TIMESTACK_POP(); + } + + // cut mesh + + if (kernel_output.seamed_cut_mesh != nullptr && kernel_output.seamed_cut_mesh->mesh->number_of_faces() > 0) { + TIMESTACK_PUSH("store cut-mesh seam"); + + std::shared_ptr cc_ptr = std::shared_ptr(new seam_cc_t, fn_delete_cc); + + MCUT_ASSERT(cc_ptr != nullptr); + + std::shared_ptr asCutMeshSeamPtr = std::dynamic_pointer_cast(cc_ptr); + + MCUT_ASSERT(asCutMeshSeamPtr != nullptr); + + asCutMeshSeamPtr->m_user_handle = reinterpret_cast(g_objects_counter++); +#if 0 + // std::shared_ptr cutMeshSeam = std::unique_ptr(new seam_cc_t, fn_delete_cc); + // McConnectedComponent clientHandle = reinterpret_cast(cutMeshSeam.get()); + // context_ptr->connected_components.emplace(clientHandle, std::move(cutMeshSeam)); + const McConnectedComponent handle = reinterpret_cast(g_objects_counter++); + + context_ptr->connected_components.add_or_update_mapping(handle, std::shared_ptr(new seam_cc_t, fn_delete_cc)); + + std::shared_ptr cc_ptr = context_ptr->connected_components.value_for(handle); + + MCUT_ASSERT(cc_ptr != nullptr); + seam_cc_t* asCutMeshSeamPtr = dynamic_cast(cc_ptr.get()); +#endif + asCutMeshSeamPtr->type = MC_CONNECTED_COMPONENT_TYPE_SEAM; + asCutMeshSeamPtr->origin = MC_SEAM_ORIGIN_CUTMESH; + + asCutMeshSeamPtr->kernel_hmesh_data = (kernel_output.seamed_cut_mesh); + + asCutMeshSeamPtr->source_hmesh_child_to_usermesh_birth_face = source_hmesh_child_to_usermesh_birth_face; + asCutMeshSeamPtr->cut_hmesh_child_to_usermesh_birth_face = cut_hmesh_child_to_usermesh_birth_face_OFFSETTED; + asCutMeshSeamPtr->source_hmesh_new_poly_partition_vertices = source_hmesh_new_poly_partition_vertices; + asCutMeshSeamPtr->cut_hmesh_new_poly_partition_vertices = cut_hmesh_new_poly_partition_vertices_OFFSETTED; + + asCutMeshSeamPtr->internal_sourcemesh_vertex_count = source_hmesh->number_of_vertices(); + asCutMeshSeamPtr->client_sourcemesh_vertex_count = numSrcMeshVertices; + asCutMeshSeamPtr->internal_sourcemesh_face_count = source_hmesh->number_of_faces(); + asCutMeshSeamPtr->client_sourcemesh_face_count = numSrcMeshFaces; // or source_hmesh_face_count + + context_ptr->connected_components.push_front(cc_ptr); // copy the connected component ptr into the context object + + TIMESTACK_POP(); + } + + // input connected components + // -------------------------- + + // internal cut-mesh (possibly with new faces and vertices) + { + TIMESTACK_PUSH("store original cut-mesh"); + + std::shared_ptr cc_ptr = std::shared_ptr(new input_cc_t, fn_delete_cc); + + MCUT_ASSERT(cc_ptr != nullptr); + + std::shared_ptr asCutMeshInputPtr = std::dynamic_pointer_cast(cc_ptr); + + MCUT_ASSERT(asCutMeshInputPtr != nullptr); + + asCutMeshInputPtr->m_user_handle = reinterpret_cast(g_objects_counter++); +#if 0 + // std::shared_ptr internalCutMesh = std::unique_ptr(new input_cc_t, fn_delete_cc); + // McConnectedComponent clientHandle = reinterpret_cast(internalCutMesh.get()); + // context_ptr->connected_components.emplace(clientHandle, std::move(internalCutMesh)); + const McConnectedComponent handle = reinterpret_cast(g_objects_counter++); + + context_ptr->connected_components.add_or_update_mapping(handle, std::shared_ptr(new input_cc_t, fn_delete_cc)); + + std::shared_ptr cc_ptr = context_ptr->connected_components.value_for(handle); + + MCUT_ASSERT(cc_ptr != nullptr); + input_cc_t* asCutMeshInputPtr = dynamic_cast(cc_ptr.get()); +#endif + asCutMeshInputPtr->type = MC_CONNECTED_COMPONENT_TYPE_INPUT; + asCutMeshInputPtr->origin = MC_INPUT_ORIGIN_CUTMESH; + + std::shared_ptr omi = std::shared_ptr(new output_mesh_info_t); + omi->mesh = cut_hmesh; // naive copy (could use std::move) + + // TODO: assume that re-adding elements (vertices and faces) e.g. prior to perturbation or partitioning is going to change the order + // from the user-provided order. So we still need to fix the mapping, which may no longer + // be one-to-one (even if with an sm offset ) as in the case when things do not change. + + if (kernel_input.populate_vertex_maps) { + omi->data_maps.vertex_map.resize(cut_hmesh->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 i = block_start_; i != block_end_; ++i) { + omi->data_maps.vertex_map[*i] = vd_t((*i) + source_hmesh->number_of_vertices()); // apply offset like kernel does + } + }; + + parallel_for( + context_ptr->get_shared_compute_threadpool() , + cut_hmesh->vertices_begin(), + cut_hmesh->vertices_end(), + fn_fill_vertex_map); +#else + for (vertex_array_iterator_t i = cut_hmesh->vertices_begin(); i != cut_hmesh->vertices_end(); ++i) { + omi->data_maps.vertex_map[*i] = vd_t((*i) + source_hmesh->number_of_vertices()); // apply offset like kernel does + } +#endif + + } + + if (kernel_input.populate_face_maps) { + omi->data_maps.face_map.resize(cut_hmesh->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 i = block_start_; i != block_end_; ++i) { + omi->data_maps.face_map[*i] = fd_t((*i) + source_hmesh->number_of_faces()); // apply offset like kernel does + } + }; + + parallel_for( + context_ptr->get_shared_compute_threadpool() , + cut_hmesh->faces_begin(), + cut_hmesh->faces_end(), + fn_fill_face_map); +#else + for (face_array_iterator_t i = cut_hmesh->faces_begin(); i != cut_hmesh->faces_end(); ++i) { + omi->data_maps.face_map[*i] = fd_t((*i) + source_hmesh->number_of_faces()); // apply offset like kernel does + } +#endif + } + + omi->seam_vertices = {}; // empty. an input connected component has no polygon intersection points + + asCutMeshInputPtr->kernel_hmesh_data = (omi); + + asCutMeshInputPtr->source_hmesh_child_to_usermesh_birth_face = source_hmesh_child_to_usermesh_birth_face; + asCutMeshInputPtr->cut_hmesh_child_to_usermesh_birth_face = cut_hmesh_child_to_usermesh_birth_face_OFFSETTED; + asCutMeshInputPtr->source_hmesh_new_poly_partition_vertices = source_hmesh_new_poly_partition_vertices; + asCutMeshInputPtr->cut_hmesh_new_poly_partition_vertices = cut_hmesh_new_poly_partition_vertices_OFFSETTED; + + asCutMeshInputPtr->internal_sourcemesh_vertex_count = source_hmesh->number_of_vertices(); + asCutMeshInputPtr->client_sourcemesh_vertex_count = numSrcMeshVertices; + asCutMeshInputPtr->internal_sourcemesh_face_count = source_hmesh->number_of_faces(); + asCutMeshInputPtr->client_sourcemesh_face_count = numSrcMeshFaces; // or source_hmesh_face_count + + context_ptr->connected_components.push_front(cc_ptr); // copy the connected component ptr into the context object + + TIMESTACK_POP(); + } + + // internal source-mesh (possibly with new faces and vertices) + { + TIMESTACK_PUSH("store original src-mesh"); + + std::shared_ptr cc_ptr = std::shared_ptr(new input_cc_t, fn_delete_cc); + + MCUT_ASSERT(cc_ptr != nullptr); + + std::shared_ptr asSrcMeshInputPtr = std::dynamic_pointer_cast(cc_ptr); + + MCUT_ASSERT(asSrcMeshInputPtr != nullptr); + + asSrcMeshInputPtr->m_user_handle = reinterpret_cast(g_objects_counter++); +#if 0 + // std::shared_ptr internalSrcMesh = std::unique_ptr(new input_cc_t, fn_delete_cc); + // McConnectedComponent clientHandle = reinterpret_cast(internalSrcMesh.get()); + // context_ptr->connected_components.emplace(clientHandle, std::move(internalSrcMesh)); + const McConnectedComponent handle = reinterpret_cast(g_objects_counter++); + + context_ptr->connected_components.add_or_update_mapping(handle, std::shared_ptr(new input_cc_t, fn_delete_cc)); + + std::shared_ptr cc_ptr = context_ptr->connected_components.value_for(handle); + + MCUT_ASSERT(cc_ptr != nullptr); + input_cc_t* asSrcMeshInputPtr = dynamic_cast(cc_ptr.get()); +#endif + asSrcMeshInputPtr->type = MC_CONNECTED_COMPONENT_TYPE_INPUT; + asSrcMeshInputPtr->origin = MC_INPUT_ORIGIN_SRCMESH; + + std::shared_ptr omi = std::shared_ptr(new output_mesh_info_t); + omi->mesh = source_hmesh; // naive copy + + if (kernel_input.populate_vertex_maps) { + omi->data_maps.vertex_map.resize(source_hmesh->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 i = block_start_; i != block_end_; ++i) { + omi->data_maps.vertex_map[*i] = *i; // one to one mapping + } + }; + + parallel_for( + context_ptr->get_shared_compute_threadpool() , + source_hmesh->vertices_begin(), + source_hmesh->vertices_end(), + fn_fill_vertex_map); +#else + for (vertex_array_iterator_t i = source_hmesh->vertices_begin(); i != source_hmesh->vertices_end(); ++i) { + omi->data_maps.vertex_map[*i] = *i; // one to one mapping + } +#endif + } + + if (kernel_input.populate_face_maps) { + omi->data_maps.face_map.resize(source_hmesh->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 i = block_start_; i != block_end_; ++i) { + omi->data_maps.face_map[*i] = *i; // one to one mapping + } + }; + + parallel_for( + context_ptr->get_shared_compute_threadpool() , + source_hmesh->faces_begin(), + source_hmesh->faces_end(), + fn_fill_face_map); +#else + for (face_array_iterator_t i = source_hmesh->faces_begin(); i != source_hmesh->faces_end(); ++i) { + omi->data_maps.face_map[*i] = *i; // one to one mapping + } +#endif + } + + omi->seam_vertices = {}; // empty. an input connected component has no polygon intersection points + + asSrcMeshInputPtr->kernel_hmesh_data = (omi); + + asSrcMeshInputPtr->source_hmesh_child_to_usermesh_birth_face = source_hmesh_child_to_usermesh_birth_face; + asSrcMeshInputPtr->cut_hmesh_child_to_usermesh_birth_face = cut_hmesh_child_to_usermesh_birth_face_OFFSETTED; + asSrcMeshInputPtr->source_hmesh_new_poly_partition_vertices = source_hmesh_new_poly_partition_vertices; + asSrcMeshInputPtr->cut_hmesh_new_poly_partition_vertices = cut_hmesh_new_poly_partition_vertices_OFFSETTED; + + asSrcMeshInputPtr->internal_sourcemesh_vertex_count = source_hmesh->number_of_vertices(); + asSrcMeshInputPtr->client_sourcemesh_vertex_count = numSrcMeshVertices; + asSrcMeshInputPtr->internal_sourcemesh_face_count = source_hmesh->number_of_faces(); + asSrcMeshInputPtr->client_sourcemesh_face_count = numSrcMeshFaces; // or source_hmesh_face_count + + context_ptr->connected_components.push_front(cc_ptr); // copy the connected component ptr into the context object + + TIMESTACK_POP(); + } +} \ No newline at end of file diff --git a/src/mcut/source/shewchuk.c b/src/mcut/source/shewchuk.c new file mode 100644 index 0000000000..3143e40234 --- /dev/null +++ b/src/mcut/source/shewchuk.c @@ -0,0 +1,8682 @@ +#if 1 +/* WARNING: these are NOT the original Shewchuk's predicates, + * but rather a modified version used in Cinolib (https://github.com/mlivesu/cinolib) + * + * Edits: + * line 130: included float.h to import machine epsilon directly from the standard C library + * lines 374-443: initialize machine epsilon and coefficients for roundoff errors at compile time. + * With this edit it is no longer necessary to call exactinit() prior using the exact + * predicates. They should go out of the box without any explicit initialization! + * lines 571-758: commented unused random number generation functions and exactinit() +*/ + + +/*****************************************************************************/ +/* */ +/* Routines for Arbitrary Precision Floating-point Arithmetic */ +/* and Fast Robust Geometric Predicates */ +/* (predicates.c) */ +/* */ +/* May 18, 1996 */ +/* */ +/* Placed in the public domain by */ +/* Jonathan Richard Shewchuk */ +/* School of Computer Science */ +/* Carnegie Mellon University */ +/* 5000 Forbes Avenue */ +/* Pittsburgh, Pennsylvania 15213-3891 */ +/* jrs@cs.cmu.edu */ +/* */ +/* This file contains C implementation of algorithms for exact addition */ +/* and multiplication of floating-point numbers, and predicates for */ +/* robustly performing the orientation and incircle tests used in */ +/* computational geometry. The algorithms and underlying theory are */ +/* described in Jonathan Richard Shewchuk. "Adaptive Precision Floating- */ +/* Point Arithmetic and Fast Robust Geometric Predicates." Technical */ +/* Report CMU-CS-96-140, School of Computer Science, Carnegie Mellon */ +/* University, Pittsburgh, Pennsylvania, May 1996. (Submitted to */ +/* Discrete & Computational Geometry.) */ +/* */ +/* This file, the paper listed above, and other information are available */ +/* from the Web page http://www.cs.cmu.edu/~quake/robust.html . */ +/* */ +/*****************************************************************************/ + +/*****************************************************************************/ +/* */ +/* Using this code: */ +/* */ +/* First, read the short or long version of the paper (from the Web page */ +/* above). */ +/* */ +/* Be sure to call exactinit() once, before calling any of the arithmetic */ +/* functions or geometric predicates. Also be sure to turn on the */ +/* optimizer when compiling this file. */ +/* */ +/* */ +/* Several geometric predicates are defined. Their parameters are all */ +/* points. Each point is an array of two or three floating-point */ +/* numbers. The geometric predicates, described in the papers, are */ +/* */ +/* orient2d(pa, pb, pc) */ +/* orient2dfast(pa, pb, pc) */ +/* orient3d(pa, pb, pc, pd) */ +/* orient3dfast(pa, pb, pc, pd) */ +/* incircle(pa, pb, pc, pd) */ +/* incirclefast(pa, pb, pc, pd) */ +/* insphere(pa, pb, pc, pd, pe) */ +/* inspherefast(pa, pb, pc, pd, pe) */ +/* */ +/* Those with suffix "fast" are approximate, non-robust versions. Those */ +/* without the suffix are adaptive precision, robust versions. There */ +/* are also versions with the suffices "exact" and "slow", which are */ +/* non-adaptive, exact arithmetic versions, which I use only for timings */ +/* in my arithmetic papers. */ +/* */ +/* */ +/* An expansion is represented by an array of floating-point numbers, */ +/* sorted from smallest to largest magnitude (possibly with interspersed */ +/* zeros). The length of each expansion is stored as a separate integer, */ +/* and each arithmetic function returns an integer which is the length */ +/* of the expansion it created. */ +/* */ +/* Several arithmetic functions are defined. Their parameters are */ +/* */ +/* e, f Input expansions */ +/* elen, flen Lengths of input expansions (must be >= 1) */ +/* h Output expansion */ +/* b Input scalar */ +/* */ +/* The arithmetic functions are */ +/* */ +/* grow_expansion(elen, e, b, h) */ +/* grow_expansion_zeroelim(elen, e, b, h) */ +/* expansion_sum(elen, e, flen, f, h) */ +/* expansion_sum_zeroelim1(elen, e, flen, f, h) */ +/* expansion_sum_zeroelim2(elen, e, flen, f, h) */ +/* fast_expansion_sum(elen, e, flen, f, h) */ +/* fast_expansion_sum_zeroelim(elen, e, flen, f, h) */ +/* linear_expansion_sum(elen, e, flen, f, h) */ +/* linear_expansion_sum_zeroelim(elen, e, flen, f, h) */ +/* scale_expansion(elen, e, b, h) */ +/* scale_expansion_zeroelim(elen, e, b, h) */ +/* compress(elen, e, h) */ +/* */ +/* All of these are described in the long version of the paper; some are */ +/* described in the short version. All return an integer that is the */ +/* length of h. Those with suffix _zeroelim perform zero elimination, */ +/* and are recommended over their counterparts. The procedure */ +/* fast_expansion_sum_zeroelim() (or linear_expansion_sum_zeroelim() on */ +/* processors that do not use the round-to-even tiebreaking rule) is */ +/* recommended over expansion_sum_zeroelim(). Each procedure has a */ +/* little note next to it (in the code below) that tells you whether or */ +/* not the output expansion may be the same array as one of the input */ +/* expansions. */ +/* */ +/* */ +/* If you look around below, you'll also find macros for a bunch of */ +/* simple unrolled arithmetic operations, and procedures for printing */ +/* expansions (commented out because they don't work with all C */ +/* compilers) and for generating random floating-point numbers whose */ +/* significand bits are all random. Most of the macros have undocumented */ +/* requirements that certain of their parameters should not be the same */ +/* variable; for safety, better to make sure all the parameters are */ +/* distinct variables. Feel free to send email to jrs@cs.cmu.edu if you */ +/* have questions. */ +/* */ +/*****************************************************************************/ + +#include +#include +#include +#include +//#include + +/* On some machines, the exact arithmetic routines might be defeated by the */ +/* use of internal extended precision floating-point registers. Sometimes */ +/* this problem can be fixed by defining certain values to be volatile, */ +/* thus forcing them to be stored to memory and rounded off. This isn't */ +/* a great solution, though, as it slows the arithmetic down. */ +/* */ +/* To try this out, write "#define INEXACT volatile" below. Normally, */ +/* however, INEXACT should be defined to be nothing. ("#define INEXACT".) */ + +#define INEXACT /* Nothing */ +/* #define INEXACT volatile */ + +#define REAL double // Cino edit: if you change to float remember to update epsilon ad splitter!!! +#define REALPRINT doubleprint +#define REALRAND doublerand +#define NARROWRAND narrowdoublerand +#define UNIFORMRAND uniformdoublerand + +/* Which of the following two methods of finding the absolute values is */ +/* fastest is compiler-dependent. A few compilers can inline and optimize */ +/* the fabs() call; but most will incur the overhead of a function call, */ +/* which is disastrously slow. A faster way on IEEE machines might be to */ +/* mask the appropriate bit, but that's difficult to do in C. */ + +#define Absolute(a) ((a) >= 0.0 ? (a) : -(a)) +/* #define Absolute(a) fabs(a) */ + +/* Many of the operations are broken up into two pieces, a main part that */ +/* performs an approximate operation, and a "tail" that computes the */ +/* roundoff error of that operation. */ +/* */ +/* The operations Fast_Two_Sum(), Fast_Two_Diff(), Two_Sum(), Two_Diff(), */ +/* Split(), and Two_Product() are all implemented as described in the */ +/* reference. Each of these macros requires certain variables to be */ +/* defined in the calling routine. The variables `bvirt', `c', `abig', */ +/* `_i', `_j', `_k', `_l', `_m', and `_n' are declared `INEXACT' because */ +/* they store the result of an operation that may incur roundoff error. */ +/* The input parameter `x' (or the highest numbered `x_' parameter) must */ +/* also be declared `INEXACT'. */ + +#define Fast_Two_Sum_Tail(a, b, x, y) \ + bvirt = x - a; \ + y = b - bvirt + +#define Fast_Two_Sum(a, b, x, y) \ + x = (REAL) (a + b); \ + Fast_Two_Sum_Tail(a, b, x, y) + +#define Fast_Two_Diff_Tail(a, b, x, y) \ + bvirt = a - x; \ + y = bvirt - b + +#define Fast_Two_Diff(a, b, x, y) \ + x = (REAL) (a - b); \ + Fast_Two_Diff_Tail(a, b, x, y) + +#define Two_Sum_Tail(a, b, x, y) \ + bvirt = (REAL) (x - a); \ + avirt = x - bvirt; \ + bround = b - bvirt; \ + around = a - avirt; \ + y = around + bround + +#define Two_Sum(a, b, x, y) \ + x = (REAL) (a + b); \ + Two_Sum_Tail(a, b, x, y) + +#define Two_Diff_Tail(a, b, x, y) \ + bvirt = (REAL) (a - x); \ + avirt = x + bvirt; \ + bround = bvirt - b; \ + around = a - avirt; \ + y = around + bround + +#define Two_Diff(a, b, x, y) \ + x = (REAL) (a - b); \ + Two_Diff_Tail(a, b, x, y) + +#define Split(a, ahi, alo) \ + c = (REAL) (splitter * a); \ + abig = (REAL) (c - a); \ + ahi = c - abig; \ + alo = a - ahi + +#define Two_Product_Tail(a, b, x, y) \ + Split(a, ahi, alo); \ + Split(b, bhi, blo); \ + err1 = x - (ahi * bhi); \ + err2 = err1 - (alo * bhi); \ + err3 = err2 - (ahi * blo); \ + y = (alo * blo) - err3 + +#define Two_Product(a, b, x, y) \ + x = (REAL) (a * b); \ + Two_Product_Tail(a, b, x, y) + +/* Two_Product_Presplit() is Two_Product() where one of the inputs has */ +/* already been split. Avoids redundant splitting. */ + +#define Two_Product_Presplit(a, b, bhi, blo, x, y) \ + x = (REAL) (a * b); \ + Split(a, ahi, alo); \ + err1 = x - (ahi * bhi); \ + err2 = err1 - (alo * bhi); \ + err3 = err2 - (ahi * blo); \ + y = (alo * blo) - err3 + +/* Two_Product_2Presplit() is Two_Product() where both of the inputs have */ +/* already been split. Avoids redundant splitting. */ + +#define Two_Product_2Presplit(a, ahi, alo, b, bhi, blo, x, y) \ + x = (REAL) (a * b); \ + err1 = x - (ahi * bhi); \ + err2 = err1 - (alo * bhi); \ + err3 = err2 - (ahi * blo); \ + y = (alo * blo) - err3 + +/* Square() can be done more quickly than Two_Product(). */ + +#define Square_Tail(a, x, y) \ + Split(a, ahi, alo); \ + err1 = x - (ahi * ahi); \ + err3 = err1 - ((ahi + ahi) * alo); \ + y = (alo * alo) - err3 + +#define Square(a, x, y) \ + x = (REAL) (a * a); \ + Square_Tail(a, x, y) + +/* Macros for summing expansions of various fixed lengths. These are all */ +/* unrolled versions of Expansion_Sum(). */ + +#define Two_One_Sum(a1, a0, b, x2, x1, x0) \ + Two_Sum(a0, b , _i, x0); \ + Two_Sum(a1, _i, x2, x1) + +#define Two_One_Diff(a1, a0, b, x2, x1, x0) \ + Two_Diff(a0, b , _i, x0); \ + Two_Sum( a1, _i, x2, x1) + +#define Two_Two_Sum(a1, a0, b1, b0, x3, x2, x1, x0) \ + Two_One_Sum(a1, a0, b0, _j, _0, x0); \ + Two_One_Sum(_j, _0, b1, x3, x2, x1) + +#define Two_Two_Diff(a1, a0, b1, b0, x3, x2, x1, x0) \ + Two_One_Diff(a1, a0, b0, _j, _0, x0); \ + Two_One_Diff(_j, _0, b1, x3, x2, x1) + +#define Four_One_Sum(a3, a2, a1, a0, b, x4, x3, x2, x1, x0) \ + Two_One_Sum(a1, a0, b , _j, x1, x0); \ + Two_One_Sum(a3, a2, _j, x4, x3, x2) + +#define Four_Two_Sum(a3, a2, a1, a0, b1, b0, x5, x4, x3, x2, x1, x0) \ + Four_One_Sum(a3, a2, a1, a0, b0, _k, _2, _1, _0, x0); \ + Four_One_Sum(_k, _2, _1, _0, b1, x5, x4, x3, x2, x1) + +#define Four_Four_Sum(a3, a2, a1, a0, b4, b3, b1, b0, x7, x6, x5, x4, x3, x2, \ + x1, x0) \ + Four_Two_Sum(a3, a2, a1, a0, b1, b0, _l, _2, _1, _0, x1, x0); \ + Four_Two_Sum(_l, _2, _1, _0, b4, b3, x7, x6, x5, x4, x3, x2) + +#define Eight_One_Sum(a7, a6, a5, a4, a3, a2, a1, a0, b, x8, x7, x6, x5, x4, \ + x3, x2, x1, x0) \ + Four_One_Sum(a3, a2, a1, a0, b , _j, x3, x2, x1, x0); \ + Four_One_Sum(a7, a6, a5, a4, _j, x8, x7, x6, x5, x4) + +#define Eight_Two_Sum(a7, a6, a5, a4, a3, a2, a1, a0, b1, b0, x9, x8, x7, \ + x6, x5, x4, x3, x2, x1, x0) \ + Eight_One_Sum(a7, a6, a5, a4, a3, a2, a1, a0, b0, _k, _6, _5, _4, _3, _2, \ + _1, _0, x0); \ + Eight_One_Sum(_k, _6, _5, _4, _3, _2, _1, _0, b1, x9, x8, x7, x6, x5, x4, \ + x3, x2, x1) + +#define Eight_Four_Sum(a7, a6, a5, a4, a3, a2, a1, a0, b4, b3, b1, b0, x11, \ + x10, x9, x8, x7, x6, x5, x4, x3, x2, x1, x0) \ + Eight_Two_Sum(a7, a6, a5, a4, a3, a2, a1, a0, b1, b0, _l, _6, _5, _4, _3, \ + _2, _1, _0, x1, x0); \ + Eight_Two_Sum(_l, _6, _5, _4, _3, _2, _1, _0, b4, b3, x11, x10, x9, x8, \ + x7, x6, x5, x4, x3, x2) + +/* Macros for multiplying expansions of various fixed lengths. */ + +#define Two_One_Product(a1, a0, b, x3, x2, x1, x0) \ + Split(b, bhi, blo); \ + Two_Product_Presplit(a0, b, bhi, blo, _i, x0); \ + Two_Product_Presplit(a1, b, bhi, blo, _j, _0); \ + Two_Sum(_i, _0, _k, x1); \ + Fast_Two_Sum(_j, _k, x3, x2) + +#define Four_One_Product(a3, a2, a1, a0, b, x7, x6, x5, x4, x3, x2, x1, x0) \ + Split(b, bhi, blo); \ + Two_Product_Presplit(a0, b, bhi, blo, _i, x0); \ + Two_Product_Presplit(a1, b, bhi, blo, _j, _0); \ + Two_Sum(_i, _0, _k, x1); \ + Fast_Two_Sum(_j, _k, _i, x2); \ + Two_Product_Presplit(a2, b, bhi, blo, _j, _0); \ + Two_Sum(_i, _0, _k, x3); \ + Fast_Two_Sum(_j, _k, _i, x4); \ + Two_Product_Presplit(a3, b, bhi, blo, _j, _0); \ + Two_Sum(_i, _0, _k, x5); \ + Fast_Two_Sum(_j, _k, x7, x6) + +#define Two_Two_Product(a1, a0, b1, b0, x7, x6, x5, x4, x3, x2, x1, x0) \ + Split(a0, a0hi, a0lo); \ + Split(b0, bhi, blo); \ + Two_Product_2Presplit(a0, a0hi, a0lo, b0, bhi, blo, _i, x0); \ + Split(a1, a1hi, a1lo); \ + Two_Product_2Presplit(a1, a1hi, a1lo, b0, bhi, blo, _j, _0); \ + Two_Sum(_i, _0, _k, _1); \ + Fast_Two_Sum(_j, _k, _l, _2); \ + Split(b1, bhi, blo); \ + Two_Product_2Presplit(a0, a0hi, a0lo, b1, bhi, blo, _i, _0); \ + Two_Sum(_1, _0, _k, x1); \ + Two_Sum(_2, _k, _j, _1); \ + Two_Sum(_l, _j, _m, _2); \ + Two_Product_2Presplit(a1, a1hi, a1lo, b1, bhi, blo, _j, _0); \ + Two_Sum(_i, _0, _n, _0); \ + Two_Sum(_1, _0, _i, x2); \ + Two_Sum(_2, _i, _k, _1); \ + Two_Sum(_m, _k, _l, _2); \ + Two_Sum(_j, _n, _k, _0); \ + Two_Sum(_1, _0, _j, x3); \ + Two_Sum(_2, _j, _i, _1); \ + Two_Sum(_l, _i, _m, _2); \ + Two_Sum(_1, _k, _i, x4); \ + Two_Sum(_2, _i, _k, x5); \ + Two_Sum(_m, _k, x7, x6) + +/* An expansion of length two can be squared more quickly than finding the */ +/* product of two different expansions of length two, and the result is */ +/* guaranteed to have no more than six (rather than eight) components. */ + +#define Two_Square(a1, a0, x5, x4, x3, x2, x1, x0) \ + Square(a0, _j, x0); \ + _0 = a0 + a0; \ + Two_Product(a1, _0, _k, _1); \ + Two_One_Sum(_k, _1, _j, _l, _2, x1); \ + Square(a1, _j, _1); \ + Two_Two_Sum(_j, _1, _l, _2, x5, x4, x3, x2) + +/* Cino edit: + * this piece of code substitutes the exactinit() routine defined in the + * original Shewchuk's predicates, and serves to define machine epsilon + * and the error bounds for the orient and incircle predicates. + * The header float.h defines DBL_EPSILON such that 1.0 + DBL_EPSILON > 1.0 in + * double-precision arithmetic, but 1.0 + z = 1.0 for any `z < DBL_EPSILON`. + * Shewchuk defines the machine epsilon as the largest floating-point number + * such that 1.0 + epsilon = 1.0. The two definitions are related by + * SHEWCHUK_EPSILON = DBL_EPSILON / 2 + * + * In this modified version of the predicates SHEWCHUK_EPSILON is initialized + * with #define, and not as a global variable like in the original code. This + * is because GCC complains if I make it const REAL and I use it to inizialize + * the error bounds below... + */ + +/* +#define SHEWCHUK_EPSILON DBL_EPSILON/2 + +const REAL resulterrbound = ( 3.0 + 8.0 * SHEWCHUK_EPSILON) * SHEWCHUK_EPSILON; +const REAL ccwerrboundA = ( 3.0 + 16.0 * SHEWCHUK_EPSILON) * SHEWCHUK_EPSILON; +const REAL ccwerrboundB = ( 2.0 + 12.0 * SHEWCHUK_EPSILON) * SHEWCHUK_EPSILON; +const REAL ccwerrboundC = ( 9.0 + 64.0 * SHEWCHUK_EPSILON) * SHEWCHUK_EPSILON * SHEWCHUK_EPSILON; +const REAL o3derrboundA = ( 7.0 + 56.0 * SHEWCHUK_EPSILON) * SHEWCHUK_EPSILON; +const REAL o3derrboundB = ( 3.0 + 28.0 * SHEWCHUK_EPSILON) * SHEWCHUK_EPSILON; +const REAL o3derrboundC = (26.0 + 288.0 * SHEWCHUK_EPSILON) * SHEWCHUK_EPSILON * SHEWCHUK_EPSILON; +const REAL iccerrboundA = (10.0 + 96.0 * SHEWCHUK_EPSILON) * SHEWCHUK_EPSILON; +const REAL iccerrboundB = ( 4.0 + 48.0 * SHEWCHUK_EPSILON) * SHEWCHUK_EPSILON; +const REAL iccerrboundC = (44.0 + 576.0 * SHEWCHUK_EPSILON) * SHEWCHUK_EPSILON * SHEWCHUK_EPSILON; +const REAL isperrboundA = (16.0 + 224.0 * SHEWCHUK_EPSILON) * SHEWCHUK_EPSILON; +const REAL isperrboundB = ( 5.0 + 72.0 * SHEWCHUK_EPSILON) * SHEWCHUK_EPSILON; +const REAL isperrboundC = (71.0 + 1408.0 * SHEWCHUK_EPSILON) * SHEWCHUK_EPSILON * SHEWCHUK_EPSILON; + +// this matches the value produced by exactinit(). +// However, 1 << (DBL_MANT_DIG/2)) + 1.0 is more correct IMHO +const REAL splitter = (1 << (DBL_MANT_DIG/2+1)) + 1.0; +*/ + +/* Marco edit: + * the previous (now commented) code is semantically correct only in C++, and some compilers + * issue an error if one attempts to consider it as C code. This happens e.g. in MSVC + * (https://docs.microsoft.com/it-it/cpp/error-messages/compiler-errors-1/compiler-error-c2099). + * As a workaround, I precalculated all the values as constants here below. + * These values should be fine for any IEEE 754 compliant system. +*/ +const double resulterrbound = 3.3306690738754706e-16; +const double ccwerrboundA = 3.3306690738754716e-16; +const double ccwerrboundB = 2.2204460492503146e-16; +const double ccwerrboundC = 1.1093356479670487e-31; +const double o3derrboundA = 7.7715611723761027e-16; +const double o3derrboundB = 3.3306690738754731e-16; +const double o3derrboundC = 3.2047474274603644e-31; +const double iccerrboundA = 1.1102230246251577e-15; +const double iccerrboundB = 4.4408920985006321e-16; +const double iccerrboundC = 5.4234187233944640e-31; +const double isperrboundA = 1.7763568394002532e-15; +const double isperrboundB = 5.5511151231257916e-16; +const double isperrboundC = 8.7514256672956190e-31; +const double splitter = 1.34217729e+08; + +// Original code from Shewchuk: +// +//REAL splitter; /* = 2^ceiling(p / 2) + 1. Used to split floats in half. */ +//REAL epsilon; /* = 2^(-p). Used to estimate roundoff errors. */ +///* A set of coefficients used to calculate maximum roundoff errors. */ +//REAL resulterrbound; +//REAL ccwerrboundA, ccwerrboundB, ccwerrboundC; +//REAL o3derrboundA, o3derrboundB, o3derrboundC; +//REAL iccerrboundA, iccerrboundB, iccerrboundC; +//REAL isperrboundA, isperrboundB, isperrboundC; + +/*****************************************************************************/ +/* */ +/* doubleprint() Print the bit representation of a double. */ +/* */ +/* Useful for debugging exact arithmetic routines. */ +/* */ +/*****************************************************************************/ + +/* +void doubleprint(number) +double number; +{ + unsigned long long no; + unsigned long long sign, expo; + int exponent; + int i, bottomi; + + no = *(unsigned long long *) &number; + sign = no & 0x8000000000000000ll; + expo = (no >> 52) & 0x7ffll; + exponent = (int) expo; + exponent = exponent - 1023; + if (sign) { + printf("-"); + } else { + printf(" "); + } + if (exponent == -1023) { + printf( + "0.0000000000000000000000000000000000000000000000000000_ ( )"); + } else { + printf("1."); + bottomi = -1; + for (i = 0; i < 52; i++) { + if (no & 0x0008000000000000ll) { + printf("1"); + bottomi = i; + } else { + printf("0"); + } + no <<= 1; + } + printf("_%d (%d)", exponent, exponent - 1 - bottomi); + } +} +*/ + +/*****************************************************************************/ +/* */ +/* floatprint() Print the bit representation of a float. */ +/* */ +/* Useful for debugging exact arithmetic routines. */ +/* */ +/*****************************************************************************/ + +/* +void floatprint(number) +float number; +{ + unsigned no; + unsigned sign, expo; + int exponent; + int i, bottomi; + + no = *(unsigned *) &number; + sign = no & 0x80000000; + expo = (no >> 23) & 0xff; + exponent = (int) expo; + exponent = exponent - 127; + if (sign) { + printf("-"); + } else { + printf(" "); + } + if (exponent == -127) { + printf("0.00000000000000000000000_ ( )"); + } else { + printf("1."); + bottomi = -1; + for (i = 0; i < 23; i++) { + if (no & 0x00400000) { + printf("1"); + bottomi = i; + } else { + printf("0"); + } + no <<= 1; + } + printf("_%3d (%3d)", exponent, exponent - 1 - bottomi); + } +} +*/ + +/*****************************************************************************/ +/* */ +/* expansion_print() Print the bit representation of an expansion. */ +/* */ +/* Useful for debugging exact arithmetic routines. */ +/* */ +/*****************************************************************************/ + +/* +void expansion_print(elen, e) +int elen; +REAL *e; +{ + int i; + + for (i = elen - 1; i >= 0; i--) { + REALPRINT(e[i]); + if (i > 0) { + printf(" +\n"); + } else { + printf("\n"); + } + } +} +*/ + +/*****************************************************************************/ +/* */ +/* doublerand() Generate a double with random 53-bit significand and a */ +/* random exponent in [0, 511]. */ +/* */ +/*****************************************************************************/ + +//double doublerand() +//{ +// double result; +// double expo; +// long a, b, c; +// long i; +// +// a = random(); +// b = random(); +// c = random(); +// result = (double) (a - 1073741824) * 8388608.0 + (double) (b >> 8); +// for (i = 512, expo = 2; i <= 131072; i *= 2, expo = expo * expo) { +// if (c & i) { +// result *= expo; +// } +// } +// return result; +//} + +/*****************************************************************************/ +/* */ +/* narrowdoublerand() Generate a double with random 53-bit significand */ +/* and a random exponent in [0, 7]. */ +/* */ +/*****************************************************************************/ + +//double narrowdoublerand() +//{ +// double result; +// double expo; +// long a, b, c; +// long i; +// +// a = random(); +// b = random(); +// c = random(); +// result = (double) (a - 1073741824) * 8388608.0 + (double) (b >> 8); +// for (i = 512, expo = 2; i <= 2048; i *= 2, expo = expo * expo) { +// if (c & i) { +// result *= expo; +// } +// } +// return result; +//} + +/*****************************************************************************/ +/* */ +/* uniformdoublerand() Generate a double with random 53-bit significand. */ +/* */ +/*****************************************************************************/ + +//double uniformdoublerand() +//{ +// double result; +// long a, b; +// +// a = random(); +// b = random(); +// result = (double) (a - 1073741824) * 8388608.0 + (double) (b >> 8); +// return result; +//} + +/*****************************************************************************/ +/* */ +/* floatrand() Generate a float with random 24-bit significand and a */ +/* random exponent in [0, 63]. */ +/* */ +/*****************************************************************************/ + +//float floatrand() +//{ +// float result; +// float expo; +// long a, c; +// long i; +// +// a = random(); +// c = random(); +// result = (float) ((a - 1073741824) >> 6); +// for (i = 512, expo = 2; i <= 16384; i *= 2, expo = expo * expo) { +// if (c & i) { +// result *= expo; +// } +// } +// return result; +//} + +/*****************************************************************************/ +/* */ +/* narrowfloatrand() Generate a float with random 24-bit significand and */ +/* a random exponent in [0, 7]. */ +/* */ +/*****************************************************************************/ +// +//float narrowfloatrand() +//{ +// float result; +// float expo; +// long a, c; +// long i; +// +// a = random(); +// c = random(); +// result = (float) ((a - 1073741824) >> 6); +// for (i = 512, expo = 2; i <= 2048; i *= 2, expo = expo * expo) { +// if (c & i) { +// result *= expo; +// } +// } +// return result; +//} + +/*****************************************************************************/ +/* */ +/* uniformfloatrand() Generate a float with random 24-bit significand. */ +/* */ +/*****************************************************************************/ + +//float uniformfloatrand() +//{ +// float result; +// long a; +// +// a = random(); +// result = (float) ((a - 1073741824) >> 6); +// return result; +//} + +/*****************************************************************************/ +/* */ +/* exactinit() Initialize the variables used for exact arithmetic. */ +/* */ +/* `epsilon' is the largest power of two such that 1.0 + epsilon = 1.0 in */ +/* floating-point arithmetic. `epsilon' bounds the relative roundoff */ +/* error. It is used for floating-point error analysis. */ +/* */ +/* `splitter' is used to split floating-point numbers into two half- */ +/* length significands for exact multiplication. */ +/* */ +/* I imagine that a highly optimizing compiler might be too smart for its */ +/* own good, and somehow cause this routine to fail, if it pretends that */ +/* floating-point arithmetic is too much like real arithmetic. */ +/* */ +/* Don't change this routine unless you fully understand it. */ +/* */ +/*****************************************************************************/ + +//void exactinit() +//{ +// REAL half; +// REAL check, lastcheck; +// int every_other; + +// every_other = 1; +// half = 0.5; +// epsilon = 1.0; +// splitter = 1.0; +// check = 1.0; +// /* Repeatedly divide `epsilon' by two until it is too small to add to */ +// /* one without causing roundoff. (Also check if the sum is equal to */ +// /* the previous sum, for machines that round up instead of using exact */ +// /* rounding. Not that this library will work on such machines anyway. */ +// do { +// lastcheck = check; +// epsilon *= half; +// if (every_other) { +// splitter *= 2.0; +// } +// every_other = !every_other; +// check = 1.0 + epsilon; +// } while ((check != 1.0) && (check != lastcheck)); +// splitter += 1.0; + +// /* Error bounds for orientation and incircle tests. */ +// resulterrbound = (3.0 + 8.0 * epsilon) * epsilon; +// ccwerrboundA = (3.0 + 16.0 * epsilon) * epsilon; +// ccwerrboundB = (2.0 + 12.0 * epsilon) * epsilon; +// ccwerrboundC = (9.0 + 64.0 * epsilon) * epsilon * epsilon; +// o3derrboundA = (7.0 + 56.0 * epsilon) * epsilon; +// o3derrboundB = (3.0 + 28.0 * epsilon) * epsilon; +// o3derrboundC = (26.0 + 288.0 * epsilon) * epsilon * epsilon; +// iccerrboundA = (10.0 + 96.0 * epsilon) * epsilon; +// iccerrboundB = (4.0 + 48.0 * epsilon) * epsilon; +// iccerrboundC = (44.0 + 576.0 * epsilon) * epsilon * epsilon; +// isperrboundA = (16.0 + 224.0 * epsilon) * epsilon; +// isperrboundB = (5.0 + 72.0 * epsilon) * epsilon; +// isperrboundC = (71.0 + 1408.0 * epsilon) * epsilon * epsilon; +//} + +/*****************************************************************************/ +/* */ +/* grow_expansion() Add a scalar to an expansion. */ +/* */ +/* Sets h = e + b. See the long version of my paper for details. */ +/* */ +/* Maintains the nonoverlapping property. If round-to-even is used (as */ +/* with IEEE 754), maintains the strongly nonoverlapping and nonadjacent */ +/* properties as well. (That is, if e has one of these properties, so */ +/* will h.) */ +/* */ +/*****************************************************************************/ + +int grow_expansion(elen, e, b, h) /* e and h can be the same. */ +int elen; +REAL *e; +REAL b; +REAL *h; +{ + REAL Q; + INEXACT REAL Qnew; + int eindex; + REAL enow; + INEXACT REAL bvirt; + REAL avirt, bround, around; + + Q = b; + for (eindex = 0; eindex < elen; eindex++) { + enow = e[eindex]; + Two_Sum(Q, enow, Qnew, h[eindex]); + Q = Qnew; + } + h[eindex] = Q; + return eindex + 1; +} + +/*****************************************************************************/ +/* */ +/* grow_expansion_zeroelim() Add a scalar to an expansion, eliminating */ +/* zero components from the output expansion. */ +/* */ +/* Sets h = e + b. See the long version of my paper for details. */ +/* */ +/* Maintains the nonoverlapping property. If round-to-even is used (as */ +/* with IEEE 754), maintains the strongly nonoverlapping and nonadjacent */ +/* properties as well. (That is, if e has one of these properties, so */ +/* will h.) */ +/* */ +/*****************************************************************************/ + +int grow_expansion_zeroelim(elen, e, b, h) /* e and h can be the same. */ +int elen; +REAL *e; +REAL b; +REAL *h; +{ + REAL Q, hh; + INEXACT REAL Qnew; + int eindex, hindex; + REAL enow; + INEXACT REAL bvirt; + REAL avirt, bround, around; + + hindex = 0; + Q = b; + for (eindex = 0; eindex < elen; eindex++) { + enow = e[eindex]; + Two_Sum(Q, enow, Qnew, hh); + Q = Qnew; + if (hh != 0.0) { + h[hindex++] = hh; + } + } + if ((Q != 0.0) || (hindex == 0)) { + h[hindex++] = Q; + } + return hindex; +} + +/*****************************************************************************/ +/* */ +/* expansion_sum() Sum two expansions. */ +/* */ +/* Sets h = e + f. See the long version of my paper for details. */ +/* */ +/* Maintains the nonoverlapping property. If round-to-even is used (as */ +/* with IEEE 754), maintains the nonadjacent property as well. (That is, */ +/* if e has one of these properties, so will h.) Does NOT maintain the */ +/* strongly nonoverlapping property. */ +/* */ +/*****************************************************************************/ + +int expansion_sum(elen, e, flen, f, h) +/* e and h can be the same, but f and h cannot. */ +int elen; +REAL *e; +int flen; +REAL *f; +REAL *h; +{ + REAL Q; + INEXACT REAL Qnew; + int findex, hindex, hlast; + REAL hnow; + INEXACT REAL bvirt; + REAL avirt, bround, around; + + Q = f[0]; + for (hindex = 0; hindex < elen; hindex++) { + hnow = e[hindex]; + Two_Sum(Q, hnow, Qnew, h[hindex]); + Q = Qnew; + } + h[hindex] = Q; + hlast = hindex; + for (findex = 1; findex < flen; findex++) { + Q = f[findex]; + for (hindex = findex; hindex <= hlast; hindex++) { + hnow = h[hindex]; + Two_Sum(Q, hnow, Qnew, h[hindex]); + Q = Qnew; + } + h[++hlast] = Q; + } + return hlast + 1; +} + +/*****************************************************************************/ +/* */ +/* expansion_sum_zeroelim1() Sum two expansions, eliminating zero */ +/* components from the output expansion. */ +/* */ +/* Sets h = e + f. See the long version of my paper for details. */ +/* */ +/* Maintains the nonoverlapping property. If round-to-even is used (as */ +/* with IEEE 754), maintains the nonadjacent property as well. (That is, */ +/* if e has one of these properties, so will h.) Does NOT maintain the */ +/* strongly nonoverlapping property. */ +/* */ +/*****************************************************************************/ + +int expansion_sum_zeroelim1(elen, e, flen, f, h) +/* e and h can be the same, but f and h cannot. */ +int elen; +REAL *e; +int flen; +REAL *f; +REAL *h; +{ + REAL Q; + INEXACT REAL Qnew; + int index, findex, hindex, hlast; + REAL hnow; + INEXACT REAL bvirt; + REAL avirt, bround, around; + + Q = f[0]; + for (hindex = 0; hindex < elen; hindex++) { + hnow = e[hindex]; + Two_Sum(Q, hnow, Qnew, h[hindex]); + Q = Qnew; + } + h[hindex] = Q; + hlast = hindex; + for (findex = 1; findex < flen; findex++) { + Q = f[findex]; + for (hindex = findex; hindex <= hlast; hindex++) { + hnow = h[hindex]; + Two_Sum(Q, hnow, Qnew, h[hindex]); + Q = Qnew; + } + h[++hlast] = Q; + } + hindex = -1; + for (index = 0; index <= hlast; index++) { + hnow = h[index]; + if (hnow != 0.0) { + h[++hindex] = hnow; + } + } + if (hindex == -1) { + return 1; + } else { + return hindex + 1; + } +} + +/*****************************************************************************/ +/* */ +/* expansion_sum_zeroelim2() Sum two expansions, eliminating zero */ +/* components from the output expansion. */ +/* */ +/* Sets h = e + f. See the long version of my paper for details. */ +/* */ +/* Maintains the nonoverlapping property. If round-to-even is used (as */ +/* with IEEE 754), maintains the nonadjacent property as well. (That is, */ +/* if e has one of these properties, so will h.) Does NOT maintain the */ +/* strongly nonoverlapping property. */ +/* */ +/*****************************************************************************/ + +int expansion_sum_zeroelim2(elen, e, flen, f, h) +/* e and h can be the same, but f and h cannot. */ +int elen; +REAL *e; +int flen; +REAL *f; +REAL *h; +{ + REAL Q, hh; + INEXACT REAL Qnew; + int eindex, findex, hindex, hlast; + REAL enow; + INEXACT REAL bvirt; + REAL avirt, bround, around; + + hindex = 0; + Q = f[0]; + for (eindex = 0; eindex < elen; eindex++) { + enow = e[eindex]; + Two_Sum(Q, enow, Qnew, hh); + Q = Qnew; + if (hh != 0.0) { + h[hindex++] = hh; + } + } + h[hindex] = Q; + hlast = hindex; + for (findex = 1; findex < flen; findex++) { + hindex = 0; + Q = f[findex]; + for (eindex = 0; eindex <= hlast; eindex++) { + enow = h[eindex]; + Two_Sum(Q, enow, Qnew, hh); + Q = Qnew; + if (hh != 0) { + h[hindex++] = hh; + } + } + h[hindex] = Q; + hlast = hindex; + } + return hlast + 1; +} + +/*****************************************************************************/ +/* */ +/* fast_expansion_sum() Sum two expansions. */ +/* */ +/* Sets h = e + f. See the long version of my paper for details. */ +/* */ +/* If round-to-even is used (as with IEEE 754), maintains the strongly */ +/* nonoverlapping property. (That is, if e is strongly nonoverlapping, h */ +/* will be also.) Does NOT maintain the nonoverlapping or nonadjacent */ +/* properties. */ +/* */ +/*****************************************************************************/ + +int fast_expansion_sum(elen, e, flen, f, h) /* h cannot be e or f. */ +int elen; +REAL *e; +int flen; +REAL *f; +REAL *h; +{ + REAL Q; + INEXACT REAL Qnew; + INEXACT REAL bvirt; + REAL avirt, bround, around; + int eindex, findex, hindex; + REAL enow, fnow; + + enow = e[0]; + fnow = f[0]; + eindex = findex = 0; + if ((fnow > enow) == (fnow > -enow)) { + Q = enow; + enow = e[++eindex]; + } else { + Q = fnow; + fnow = f[++findex]; + } + hindex = 0; + if ((eindex < elen) && (findex < flen)) { + if ((fnow > enow) == (fnow > -enow)) { + Fast_Two_Sum(enow, Q, Qnew, h[0]); + enow = e[++eindex]; + } else { + Fast_Two_Sum(fnow, Q, Qnew, h[0]); + fnow = f[++findex]; + } + Q = Qnew; + hindex = 1; + while ((eindex < elen) && (findex < flen)) { + if ((fnow > enow) == (fnow > -enow)) { + Two_Sum(Q, enow, Qnew, h[hindex]); + enow = e[++eindex]; + } else { + Two_Sum(Q, fnow, Qnew, h[hindex]); + fnow = f[++findex]; + } + Q = Qnew; + hindex++; + } + } + while (eindex < elen) { + Two_Sum(Q, enow, Qnew, h[hindex]); + enow = e[++eindex]; + Q = Qnew; + hindex++; + } + while (findex < flen) { + Two_Sum(Q, fnow, Qnew, h[hindex]); + fnow = f[++findex]; + Q = Qnew; + hindex++; + } + h[hindex] = Q; + return hindex + 1; +} + +/*****************************************************************************/ +/* */ +/* fast_expansion_sum_zeroelim() Sum two expansions, eliminating zero */ +/* components from the output expansion. */ +/* */ +/* Sets h = e + f. See the long version of my paper for details. */ +/* */ +/* If round-to-even is used (as with IEEE 754), maintains the strongly */ +/* nonoverlapping property. (That is, if e is strongly nonoverlapping, h */ +/* will be also.) Does NOT maintain the nonoverlapping or nonadjacent */ +/* properties. */ +/* */ +/*****************************************************************************/ + +int fast_expansion_sum_zeroelim(elen, e, flen, f, h) /* h cannot be e or f. */ +int elen; +REAL *e; +int flen; +REAL *f; +REAL *h; +{ + REAL Q; + INEXACT REAL Qnew; + INEXACT REAL hh; + INEXACT REAL bvirt; + REAL avirt, bround, around; + int eindex, findex, hindex; + REAL enow, fnow; + + enow = e[0]; + fnow = f[0]; + eindex = findex = 0; + if ((fnow > enow) == (fnow > -enow)) { + Q = enow; + enow = e[++eindex]; + } else { + Q = fnow; + fnow = f[++findex]; + } + hindex = 0; + if ((eindex < elen) && (findex < flen)) { + if ((fnow > enow) == (fnow > -enow)) { + Fast_Two_Sum(enow, Q, Qnew, hh); + enow = e[++eindex]; + } else { + Fast_Two_Sum(fnow, Q, Qnew, hh); + fnow = f[++findex]; + } + Q = Qnew; + if (hh != 0.0) { + h[hindex++] = hh; + } + while ((eindex < elen) && (findex < flen)) { + if ((fnow > enow) == (fnow > -enow)) { + Two_Sum(Q, enow, Qnew, hh); + enow = e[++eindex]; + } else { + Two_Sum(Q, fnow, Qnew, hh); + fnow = f[++findex]; + } + Q = Qnew; + if (hh != 0.0) { + h[hindex++] = hh; + } + } + } + while (eindex < elen) { + Two_Sum(Q, enow, Qnew, hh); + enow = e[++eindex]; + Q = Qnew; + if (hh != 0.0) { + h[hindex++] = hh; + } + } + while (findex < flen) { + Two_Sum(Q, fnow, Qnew, hh); + fnow = f[++findex]; + Q = Qnew; + if (hh != 0.0) { + h[hindex++] = hh; + } + } + if ((Q != 0.0) || (hindex == 0)) { + h[hindex++] = Q; + } + return hindex; +} + +/*****************************************************************************/ +/* */ +/* linear_expansion_sum() Sum two expansions. */ +/* */ +/* Sets h = e + f. See either version of my paper for details. */ +/* */ +/* Maintains the nonoverlapping property. (That is, if e is */ +/* nonoverlapping, h will be also.) */ +/* */ +/*****************************************************************************/ + +int linear_expansion_sum(elen, e, flen, f, h) /* h cannot be e or f. */ +int elen; +REAL *e; +int flen; +REAL *f; +REAL *h; +{ + REAL Q, q; + INEXACT REAL Qnew; + INEXACT REAL R; + INEXACT REAL bvirt; + REAL avirt, bround, around; + int eindex, findex, hindex; + REAL enow, fnow; + REAL g0; + + enow = e[0]; + fnow = f[0]; + eindex = findex = 0; + if ((fnow > enow) == (fnow > -enow)) { + g0 = enow; + enow = e[++eindex]; + } else { + g0 = fnow; + fnow = f[++findex]; + } + if ((eindex < elen) && ((findex >= flen) + || ((fnow > enow) == (fnow > -enow)))) { + Fast_Two_Sum(enow, g0, Qnew, q); + enow = e[++eindex]; + } else { + Fast_Two_Sum(fnow, g0, Qnew, q); + fnow = f[++findex]; + } + Q = Qnew; + for (hindex = 0; hindex < elen + flen - 2; hindex++) { + if ((eindex < elen) && ((findex >= flen) + || ((fnow > enow) == (fnow > -enow)))) { + Fast_Two_Sum(enow, q, R, h[hindex]); + enow = e[++eindex]; + } else { + Fast_Two_Sum(fnow, q, R, h[hindex]); + fnow = f[++findex]; + } + Two_Sum(Q, R, Qnew, q); + Q = Qnew; + } + h[hindex] = q; + h[hindex + 1] = Q; + return hindex + 2; +} + +/*****************************************************************************/ +/* */ +/* linear_expansion_sum_zeroelim() Sum two expansions, eliminating zero */ +/* components from the output expansion. */ +/* */ +/* Sets h = e + f. See either version of my paper for details. */ +/* */ +/* Maintains the nonoverlapping property. (That is, if e is */ +/* nonoverlapping, h will be also.) */ +/* */ +/*****************************************************************************/ + +int linear_expansion_sum_zeroelim(elen, e, flen, f, h)/* h cannot be e or f. */ +int elen; +REAL *e; +int flen; +REAL *f; +REAL *h; +{ + REAL Q, q, hh; + INEXACT REAL Qnew; + INEXACT REAL R; + INEXACT REAL bvirt; + REAL avirt, bround, around; + int eindex, findex, hindex; + int count; + REAL enow, fnow; + REAL g0; + + enow = e[0]; + fnow = f[0]; + eindex = findex = 0; + hindex = 0; + if ((fnow > enow) == (fnow > -enow)) { + g0 = enow; + enow = e[++eindex]; + } else { + g0 = fnow; + fnow = f[++findex]; + } + if ((eindex < elen) && ((findex >= flen) + || ((fnow > enow) == (fnow > -enow)))) { + Fast_Two_Sum(enow, g0, Qnew, q); + enow = e[++eindex]; + } else { + Fast_Two_Sum(fnow, g0, Qnew, q); + fnow = f[++findex]; + } + Q = Qnew; + for (count = 2; count < elen + flen; count++) { + if ((eindex < elen) && ((findex >= flen) + || ((fnow > enow) == (fnow > -enow)))) { + Fast_Two_Sum(enow, q, R, hh); + enow = e[++eindex]; + } else { + Fast_Two_Sum(fnow, q, R, hh); + fnow = f[++findex]; + } + Two_Sum(Q, R, Qnew, q); + Q = Qnew; + if (hh != 0) { + h[hindex++] = hh; + } + } + if (q != 0) { + h[hindex++] = q; + } + if ((Q != 0.0) || (hindex == 0)) { + h[hindex++] = Q; + } + return hindex; +} + +/*****************************************************************************/ +/* */ +/* scale_expansion() Multiply an expansion by a scalar. */ +/* */ +/* Sets h = be. See either version of my paper for details. */ +/* */ +/* Maintains the nonoverlapping property. If round-to-even is used (as */ +/* with IEEE 754), maintains the strongly nonoverlapping and nonadjacent */ +/* properties as well. (That is, if e has one of these properties, so */ +/* will h.) */ +/* */ +/*****************************************************************************/ + +int scale_expansion(elen, e, b, h) /* e and h cannot be the same. */ +int elen; +REAL *e; +REAL b; +REAL *h; +{ + INEXACT REAL Q; + INEXACT REAL sum; + INEXACT REAL product1; + REAL product0; + int eindex, hindex; + REAL enow; + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + + Split(b, bhi, blo); + Two_Product_Presplit(e[0], b, bhi, blo, Q, h[0]); + hindex = 1; + for (eindex = 1; eindex < elen; eindex++) { + enow = e[eindex]; + Two_Product_Presplit(enow, b, bhi, blo, product1, product0); + Two_Sum(Q, product0, sum, h[hindex]); + hindex++; + Two_Sum(product1, sum, Q, h[hindex]); + hindex++; + } + h[hindex] = Q; + return elen + elen; +} + +/*****************************************************************************/ +/* */ +/* scale_expansion_zeroelim() Multiply an expansion by a scalar, */ +/* eliminating zero components from the */ +/* output expansion. */ +/* */ +/* Sets h = be. See either version of my paper for details. */ +/* */ +/* Maintains the nonoverlapping property. If round-to-even is used (as */ +/* with IEEE 754), maintains the strongly nonoverlapping and nonadjacent */ +/* properties as well. (That is, if e has one of these properties, so */ +/* will h.) */ +/* */ +/*****************************************************************************/ + +int scale_expansion_zeroelim(elen, e, b, h) /* e and h cannot be the same. */ +int elen; +REAL *e; +REAL b; +REAL *h; +{ + INEXACT REAL Q, sum; + REAL hh; + INEXACT REAL product1; + REAL product0; + int eindex, hindex; + REAL enow; + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + + Split(b, bhi, blo); + Two_Product_Presplit(e[0], b, bhi, blo, Q, hh); + hindex = 0; + if (hh != 0) { + h[hindex++] = hh; + } + for (eindex = 1; eindex < elen; eindex++) { + enow = e[eindex]; + Two_Product_Presplit(enow, b, bhi, blo, product1, product0); + Two_Sum(Q, product0, sum, hh); + if (hh != 0) { + h[hindex++] = hh; + } + Fast_Two_Sum(product1, sum, Q, hh); + if (hh != 0) { + h[hindex++] = hh; + } + } + if ((Q != 0.0) || (hindex == 0)) { + h[hindex++] = Q; + } + return hindex; +} + +/*****************************************************************************/ +/* */ +/* compress() Compress an expansion. */ +/* */ +/* See the long version of my paper for details. */ +/* */ +/* Maintains the nonoverlapping property. If round-to-even is used (as */ +/* with IEEE 754), then any nonoverlapping expansion is converted to a */ +/* nonadjacent expansion. */ +/* */ +/*****************************************************************************/ + +//int compress(elen, e, h) /* e and h may be the same. */ +//int elen; +//REAL *e; +//REAL *h; +//{ +// REAL Q, q; +// INEXACT REAL Qnew; +// int eindex, hindex; +// INEXACT REAL bvirt; +// REAL enow, hnow; +// int top, bottom; +// +// bottom = elen - 1; +// Q = e[bottom]; +// for (eindex = elen - 2; eindex >= 0; eindex--) { +// enow = e[eindex]; +// Fast_Two_Sum(Q, enow, Qnew, q); +// if (q != 0) { +// h[bottom--] = Qnew; +// Q = q; +// } else { +// Q = Qnew; +// } +// } +// top = 0; +// for (hindex = bottom + 1; hindex < elen; hindex++) { +// hnow = h[hindex]; +// Fast_Two_Sum(hnow, Q, Qnew, q); +// if (q != 0) { +// h[top++] = q; +// } +// Q = Qnew; +// } +// h[top] = Q; +// return top + 1; +//} + +/*****************************************************************************/ +/* */ +/* estimate() Produce a one-word estimate of an expansion's value. */ +/* */ +/* See either version of my paper for details. */ +/* */ +/*****************************************************************************/ + +REAL estimate(elen, e) +int elen; +REAL *e; +{ + REAL Q; + int eindex; + + Q = e[0]; + for (eindex = 1; eindex < elen; eindex++) { + Q += e[eindex]; + } + return Q; +} + +/*****************************************************************************/ +/* */ +/* orient2dfast() Approximate 2D orientation test. Nonrobust. */ +/* orient2dexact() Exact 2D orientation test. Robust. */ +/* orient2dslow() Another exact 2D orientation test. Robust. */ +/* orient2d() Adaptive exact 2D orientation test. Robust. */ +/* */ +/* Return a positive value if the points pa, pb, and pc occur */ +/* in counterclockwise order; a negative value if they occur */ +/* in clockwise order; and zero if they are collinear. The */ +/* result is also a rough approximation of twice the signed */ +/* area of the triangle defined by the three points. */ +/* */ +/* Only the first and last routine should be used; the middle two are for */ +/* timings. */ +/* */ +/* The last three use exact arithmetic to ensure a correct answer. The */ +/* result returned is the determinant of a matrix. In orient2d() only, */ +/* this determinant is computed adaptively, in the sense that exact */ +/* arithmetic is used only to the degree it is needed to ensure that the */ +/* returned value has the correct sign. Hence, orient2d() is usually quite */ +/* fast, but will run more slowly when the input points are collinear or */ +/* nearly so. */ +/* */ +/*****************************************************************************/ + +REAL orient2dfast(pa, pb, pc) +REAL *pa; +REAL *pb; +REAL *pc; +{ + REAL acx, bcx, acy, bcy; + + acx = pa[0] - pc[0]; + bcx = pb[0] - pc[0]; + acy = pa[1] - pc[1]; + bcy = pb[1] - pc[1]; + return acx * bcy - acy * bcx; +} + +REAL orient2dexact(pa, pb, pc) +REAL *pa; +REAL *pb; +REAL *pc; +{ + INEXACT REAL axby1, axcy1, bxcy1, bxay1, cxay1, cxby1; + REAL axby0, axcy0, bxcy0, bxay0, cxay0, cxby0; + REAL aterms[4], bterms[4], cterms[4]; + INEXACT REAL aterms3, bterms3, cterms3; + REAL v[8], w[12]; + int vlength, wlength; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j; + REAL _0; + + Two_Product(pa[0], pb[1], axby1, axby0); + Two_Product(pa[0], pc[1], axcy1, axcy0); + Two_Two_Diff(axby1, axby0, axcy1, axcy0, + aterms3, aterms[2], aterms[1], aterms[0]); + aterms[3] = aterms3; + + Two_Product(pb[0], pc[1], bxcy1, bxcy0); + Two_Product(pb[0], pa[1], bxay1, bxay0); + Two_Two_Diff(bxcy1, bxcy0, bxay1, bxay0, + bterms3, bterms[2], bterms[1], bterms[0]); + bterms[3] = bterms3; + + Two_Product(pc[0], pa[1], cxay1, cxay0); + Two_Product(pc[0], pb[1], cxby1, cxby0); + Two_Two_Diff(cxay1, cxay0, cxby1, cxby0, + cterms3, cterms[2], cterms[1], cterms[0]); + cterms[3] = cterms3; + + vlength = fast_expansion_sum_zeroelim(4, aterms, 4, bterms, v); + wlength = fast_expansion_sum_zeroelim(vlength, v, 4, cterms, w); + + return w[wlength - 1]; +} + +REAL orient2dslow(pa, pb, pc) +REAL *pa; +REAL *pb; +REAL *pc; +{ + INEXACT REAL acx, acy, bcx, bcy; + REAL acxtail, acytail; + REAL bcxtail, bcytail; + REAL negate, negatetail; + REAL axby[8], bxay[8]; + INEXACT REAL axby7, bxay7; + REAL deter[16]; + int deterlen; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL a0hi, a0lo, a1hi, a1lo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j, _k, _l, _m, _n; + REAL _0, _1, _2; + + Two_Diff(pa[0], pc[0], acx, acxtail); + Two_Diff(pa[1], pc[1], acy, acytail); + Two_Diff(pb[0], pc[0], bcx, bcxtail); + Two_Diff(pb[1], pc[1], bcy, bcytail); + + Two_Two_Product(acx, acxtail, bcy, bcytail, + axby7, axby[6], axby[5], axby[4], + axby[3], axby[2], axby[1], axby[0]); + axby[7] = axby7; + negate = -acy; + negatetail = -acytail; + Two_Two_Product(bcx, bcxtail, negate, negatetail, + bxay7, bxay[6], bxay[5], bxay[4], + bxay[3], bxay[2], bxay[1], bxay[0]); + bxay[7] = bxay7; + + deterlen = fast_expansion_sum_zeroelim(8, axby, 8, bxay, deter); + + return deter[deterlen - 1]; +} + +REAL orient2dadapt(pa, pb, pc, detsum) +REAL *pa; +REAL *pb; +REAL *pc; +REAL detsum; +{ + INEXACT REAL acx, acy, bcx, bcy; + REAL acxtail, acytail, bcxtail, bcytail; + INEXACT REAL detleft, detright; + REAL detlefttail, detrighttail; + REAL det, errbound; + REAL B[4], C1[8], C2[12], D[16]; + INEXACT REAL B3; + int C1length, C2length, Dlength; + REAL u[4]; + INEXACT REAL u3; + INEXACT REAL s1, t1; + REAL s0, t0; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j; + REAL _0; + + acx = (REAL) (pa[0] - pc[0]); + bcx = (REAL) (pb[0] - pc[0]); + acy = (REAL) (pa[1] - pc[1]); + bcy = (REAL) (pb[1] - pc[1]); + + Two_Product(acx, bcy, detleft, detlefttail); + Two_Product(acy, bcx, detright, detrighttail); + + Two_Two_Diff(detleft, detlefttail, detright, detrighttail, + B3, B[2], B[1], B[0]); + B[3] = B3; + + det = estimate(4, B); + errbound = ccwerrboundB * detsum; + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + Two_Diff_Tail(pa[0], pc[0], acx, acxtail); + Two_Diff_Tail(pb[0], pc[0], bcx, bcxtail); + Two_Diff_Tail(pa[1], pc[1], acy, acytail); + Two_Diff_Tail(pb[1], pc[1], bcy, bcytail); + + if ((acxtail == 0.0) && (acytail == 0.0) + && (bcxtail == 0.0) && (bcytail == 0.0)) { + return det; + } + + errbound = ccwerrboundC * detsum + resulterrbound * Absolute(det); + det += (acx * bcytail + bcy * acxtail) + - (acy * bcxtail + bcx * acytail); + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + Two_Product(acxtail, bcy, s1, s0); + Two_Product(acytail, bcx, t1, t0); + Two_Two_Diff(s1, s0, t1, t0, u3, u[2], u[1], u[0]); + u[3] = u3; + C1length = fast_expansion_sum_zeroelim(4, B, 4, u, C1); + + Two_Product(acx, bcytail, s1, s0); + Two_Product(acy, bcxtail, t1, t0); + Two_Two_Diff(s1, s0, t1, t0, u3, u[2], u[1], u[0]); + u[3] = u3; + C2length = fast_expansion_sum_zeroelim(C1length, C1, 4, u, C2); + + Two_Product(acxtail, bcytail, s1, s0); + Two_Product(acytail, bcxtail, t1, t0); + Two_Two_Diff(s1, s0, t1, t0, u3, u[2], u[1], u[0]); + u[3] = u3; + Dlength = fast_expansion_sum_zeroelim(C2length, C2, 4, u, D); + + return(D[Dlength - 1]); +} + +REAL orient2d(pa, pb, pc) +REAL *pa; +REAL *pb; +REAL *pc; +{ + REAL detleft, detright, det; + REAL detsum, errbound; + + detleft = (pa[0] - pc[0]) * (pb[1] - pc[1]); + detright = (pa[1] - pc[1]) * (pb[0] - pc[0]); + det = detleft - detright; + + if (detleft > 0.0) { + if (detright <= 0.0) { + return det; + } else { + detsum = detleft + detright; + } + } else if (detleft < 0.0) { + if (detright >= 0.0) { + return det; + } else { + detsum = -detleft - detright; + } + } else { + return det; + } + + errbound = ccwerrboundA * detsum; + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + return orient2dadapt(pa, pb, pc, detsum); +} + +/*****************************************************************************/ +/* */ +/* orient3dfast() Approximate 3D orientation test. Nonrobust. */ +/* orient3dexact() Exact 3D orientation test. Robust. */ +/* orient3dslow() Another exact 3D orientation test. Robust. */ +/* orient3d() Adaptive exact 3D orientation test. Robust. */ +/* */ +/* Return a positive value if the point pd lies below the */ +/* plane passing through pa, pb, and pc; "below" is defined so */ +/* that pa, pb, and pc appear in counterclockwise order when */ +/* viewed from above the plane. Returns a negative value if */ +/* pd lies above the plane. Returns zero if the points are */ +/* coplanar. The result is also a rough approximation of six */ +/* times the signed volume of the tetrahedron defined by the */ +/* four points. */ +/* */ +/* Only the first and last routine should be used; the middle two are for */ +/* timings. */ +/* */ +/* The last three use exact arithmetic to ensure a correct answer. The */ +/* result returned is the determinant of a matrix. In orient3d() only, */ +/* this determinant is computed adaptively, in the sense that exact */ +/* arithmetic is used only to the degree it is needed to ensure that the */ +/* returned value has the correct sign. Hence, orient3d() is usually quite */ +/* fast, but will run more slowly when the input points are coplanar or */ +/* nearly so. */ +/* */ +/*****************************************************************************/ + +REAL orient3dfast(pa, pb, pc, pd) +REAL *pa; +REAL *pb; +REAL *pc; +REAL *pd; +{ + REAL adx, bdx, cdx; + REAL ady, bdy, cdy; + REAL adz, bdz, cdz; + + adx = pa[0] - pd[0]; + bdx = pb[0] - pd[0]; + cdx = pc[0] - pd[0]; + ady = pa[1] - pd[1]; + bdy = pb[1] - pd[1]; + cdy = pc[1] - pd[1]; + adz = pa[2] - pd[2]; + bdz = pb[2] - pd[2]; + cdz = pc[2] - pd[2]; + + return adx * (bdy * cdz - bdz * cdy) + + bdx * (cdy * adz - cdz * ady) + + cdx * (ady * bdz - adz * bdy); +} + +REAL orient3dexact(pa, pb, pc, pd) +REAL *pa; +REAL *pb; +REAL *pc; +REAL *pd; +{ + INEXACT REAL axby1, bxcy1, cxdy1, dxay1, axcy1, bxdy1; + INEXACT REAL bxay1, cxby1, dxcy1, axdy1, cxay1, dxby1; + REAL axby0, bxcy0, cxdy0, dxay0, axcy0, bxdy0; + REAL bxay0, cxby0, dxcy0, axdy0, cxay0, dxby0; + REAL ab[4], bc[4], cd[4], da[4], ac[4], bd[4]; + REAL temp8[8]; + int templen; + REAL abc[12], bcd[12], cda[12], dab[12]; + int abclen, bcdlen, cdalen, dablen; + REAL adet[24], bdet[24], cdet[24], ddet[24]; + int alen, blen, clen, dlen; + REAL abdet[48], cddet[48]; + int ablen, cdlen; + REAL deter[96]; + int deterlen; + int i; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j; + REAL _0; + + Two_Product(pa[0], pb[1], axby1, axby0); + Two_Product(pb[0], pa[1], bxay1, bxay0); + Two_Two_Diff(axby1, axby0, bxay1, bxay0, ab[3], ab[2], ab[1], ab[0]); + + Two_Product(pb[0], pc[1], bxcy1, bxcy0); + Two_Product(pc[0], pb[1], cxby1, cxby0); + Two_Two_Diff(bxcy1, bxcy0, cxby1, cxby0, bc[3], bc[2], bc[1], bc[0]); + + Two_Product(pc[0], pd[1], cxdy1, cxdy0); + Two_Product(pd[0], pc[1], dxcy1, dxcy0); + Two_Two_Diff(cxdy1, cxdy0, dxcy1, dxcy0, cd[3], cd[2], cd[1], cd[0]); + + Two_Product(pd[0], pa[1], dxay1, dxay0); + Two_Product(pa[0], pd[1], axdy1, axdy0); + Two_Two_Diff(dxay1, dxay0, axdy1, axdy0, da[3], da[2], da[1], da[0]); + + Two_Product(pa[0], pc[1], axcy1, axcy0); + Two_Product(pc[0], pa[1], cxay1, cxay0); + Two_Two_Diff(axcy1, axcy0, cxay1, cxay0, ac[3], ac[2], ac[1], ac[0]); + + Two_Product(pb[0], pd[1], bxdy1, bxdy0); + Two_Product(pd[0], pb[1], dxby1, dxby0); + Two_Two_Diff(bxdy1, bxdy0, dxby1, dxby0, bd[3], bd[2], bd[1], bd[0]); + + templen = fast_expansion_sum_zeroelim(4, cd, 4, da, temp8); + cdalen = fast_expansion_sum_zeroelim(templen, temp8, 4, ac, cda); + templen = fast_expansion_sum_zeroelim(4, da, 4, ab, temp8); + dablen = fast_expansion_sum_zeroelim(templen, temp8, 4, bd, dab); + for (i = 0; i < 4; i++) { + bd[i] = -bd[i]; + ac[i] = -ac[i]; + } + templen = fast_expansion_sum_zeroelim(4, ab, 4, bc, temp8); + abclen = fast_expansion_sum_zeroelim(templen, temp8, 4, ac, abc); + templen = fast_expansion_sum_zeroelim(4, bc, 4, cd, temp8); + bcdlen = fast_expansion_sum_zeroelim(templen, temp8, 4, bd, bcd); + + alen = scale_expansion_zeroelim(bcdlen, bcd, pa[2], adet); + blen = scale_expansion_zeroelim(cdalen, cda, -pb[2], bdet); + clen = scale_expansion_zeroelim(dablen, dab, pc[2], cdet); + dlen = scale_expansion_zeroelim(abclen, abc, -pd[2], ddet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + cdlen = fast_expansion_sum_zeroelim(clen, cdet, dlen, ddet, cddet); + deterlen = fast_expansion_sum_zeroelim(ablen, abdet, cdlen, cddet, deter); + + return deter[deterlen - 1]; +} + +REAL orient3dslow(pa, pb, pc, pd) +REAL *pa; +REAL *pb; +REAL *pc; +REAL *pd; +{ + INEXACT REAL adx, ady, adz, bdx, bdy, bdz, cdx, cdy, cdz; + REAL adxtail, adytail, adztail; + REAL bdxtail, bdytail, bdztail; + REAL cdxtail, cdytail, cdztail; + REAL negate, negatetail; + INEXACT REAL axby7, bxcy7, axcy7, bxay7, cxby7, cxay7; + REAL axby[8], bxcy[8], axcy[8], bxay[8], cxby[8], cxay[8]; + REAL temp16[16], temp32[32], temp32t[32]; + int temp16len, temp32len, temp32tlen; + REAL adet[64], bdet[64], cdet[64]; + int alen, blen, clen; + REAL abdet[128]; + int ablen; + REAL deter[192]; + int deterlen; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL a0hi, a0lo, a1hi, a1lo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j, _k, _l, _m, _n; + REAL _0, _1, _2; + + Two_Diff(pa[0], pd[0], adx, adxtail); + Two_Diff(pa[1], pd[1], ady, adytail); + Two_Diff(pa[2], pd[2], adz, adztail); + Two_Diff(pb[0], pd[0], bdx, bdxtail); + Two_Diff(pb[1], pd[1], bdy, bdytail); + Two_Diff(pb[2], pd[2], bdz, bdztail); + Two_Diff(pc[0], pd[0], cdx, cdxtail); + Two_Diff(pc[1], pd[1], cdy, cdytail); + Two_Diff(pc[2], pd[2], cdz, cdztail); + + Two_Two_Product(adx, adxtail, bdy, bdytail, + axby7, axby[6], axby[5], axby[4], + axby[3], axby[2], axby[1], axby[0]); + axby[7] = axby7; + negate = -ady; + negatetail = -adytail; + Two_Two_Product(bdx, bdxtail, negate, negatetail, + bxay7, bxay[6], bxay[5], bxay[4], + bxay[3], bxay[2], bxay[1], bxay[0]); + bxay[7] = bxay7; + Two_Two_Product(bdx, bdxtail, cdy, cdytail, + bxcy7, bxcy[6], bxcy[5], bxcy[4], + bxcy[3], bxcy[2], bxcy[1], bxcy[0]); + bxcy[7] = bxcy7; + negate = -bdy; + negatetail = -bdytail; + Two_Two_Product(cdx, cdxtail, negate, negatetail, + cxby7, cxby[6], cxby[5], cxby[4], + cxby[3], cxby[2], cxby[1], cxby[0]); + cxby[7] = cxby7; + Two_Two_Product(cdx, cdxtail, ady, adytail, + cxay7, cxay[6], cxay[5], cxay[4], + cxay[3], cxay[2], cxay[1], cxay[0]); + cxay[7] = cxay7; + negate = -cdy; + negatetail = -cdytail; + Two_Two_Product(adx, adxtail, negate, negatetail, + axcy7, axcy[6], axcy[5], axcy[4], + axcy[3], axcy[2], axcy[1], axcy[0]); + axcy[7] = axcy7; + + temp16len = fast_expansion_sum_zeroelim(8, bxcy, 8, cxby, temp16); + temp32len = scale_expansion_zeroelim(temp16len, temp16, adz, temp32); + temp32tlen = scale_expansion_zeroelim(temp16len, temp16, adztail, temp32t); + alen = fast_expansion_sum_zeroelim(temp32len, temp32, temp32tlen, temp32t, + adet); + + temp16len = fast_expansion_sum_zeroelim(8, cxay, 8, axcy, temp16); + temp32len = scale_expansion_zeroelim(temp16len, temp16, bdz, temp32); + temp32tlen = scale_expansion_zeroelim(temp16len, temp16, bdztail, temp32t); + blen = fast_expansion_sum_zeroelim(temp32len, temp32, temp32tlen, temp32t, + bdet); + + temp16len = fast_expansion_sum_zeroelim(8, axby, 8, bxay, temp16); + temp32len = scale_expansion_zeroelim(temp16len, temp16, cdz, temp32); + temp32tlen = scale_expansion_zeroelim(temp16len, temp16, cdztail, temp32t); + clen = fast_expansion_sum_zeroelim(temp32len, temp32, temp32tlen, temp32t, + cdet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + deterlen = fast_expansion_sum_zeroelim(ablen, abdet, clen, cdet, deter); + + return deter[deterlen - 1]; +} + +REAL orient3dadapt(pa, pb, pc, pd, permanent) +REAL *pa; +REAL *pb; +REAL *pc; +REAL *pd; +REAL permanent; +{ + INEXACT REAL adx, bdx, cdx, ady, bdy, cdy, adz, bdz, cdz; + REAL det, errbound; + + INEXACT REAL bdxcdy1, cdxbdy1, cdxady1, adxcdy1, adxbdy1, bdxady1; + REAL bdxcdy0, cdxbdy0, cdxady0, adxcdy0, adxbdy0, bdxady0; + REAL bc[4], ca[4], ab[4]; + INEXACT REAL bc3, ca3, ab3; + REAL adet[8], bdet[8], cdet[8]; + int alen, blen, clen; + REAL abdet[16]; + int ablen; + REAL *finnow, *finother, *finswap; + REAL fin1[192], fin2[192]; + int finlength; + + REAL adxtail, bdxtail, cdxtail; + REAL adytail, bdytail, cdytail; + REAL adztail, bdztail, cdztail; + INEXACT REAL at_blarge, at_clarge; + INEXACT REAL bt_clarge, bt_alarge; + INEXACT REAL ct_alarge, ct_blarge; + REAL at_b[4], at_c[4], bt_c[4], bt_a[4], ct_a[4], ct_b[4]; + int at_blen, at_clen, bt_clen, bt_alen, ct_alen, ct_blen; + INEXACT REAL bdxt_cdy1, cdxt_bdy1, cdxt_ady1; + INEXACT REAL adxt_cdy1, adxt_bdy1, bdxt_ady1; + REAL bdxt_cdy0, cdxt_bdy0, cdxt_ady0; + REAL adxt_cdy0, adxt_bdy0, bdxt_ady0; + INEXACT REAL bdyt_cdx1, cdyt_bdx1, cdyt_adx1; + INEXACT REAL adyt_cdx1, adyt_bdx1, bdyt_adx1; + REAL bdyt_cdx0, cdyt_bdx0, cdyt_adx0; + REAL adyt_cdx0, adyt_bdx0, bdyt_adx0; + REAL bct[8], cat[8], abt[8]; + int bctlen, catlen, abtlen; + INEXACT REAL bdxt_cdyt1, cdxt_bdyt1, cdxt_adyt1; + INEXACT REAL adxt_cdyt1, adxt_bdyt1, bdxt_adyt1; + REAL bdxt_cdyt0, cdxt_bdyt0, cdxt_adyt0; + REAL adxt_cdyt0, adxt_bdyt0, bdxt_adyt0; + REAL u[4], v[12], w[16]; + INEXACT REAL u3; + int vlength, wlength; + REAL negate; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j, _k; + REAL _0; + + adx = (REAL) (pa[0] - pd[0]); + bdx = (REAL) (pb[0] - pd[0]); + cdx = (REAL) (pc[0] - pd[0]); + ady = (REAL) (pa[1] - pd[1]); + bdy = (REAL) (pb[1] - pd[1]); + cdy = (REAL) (pc[1] - pd[1]); + adz = (REAL) (pa[2] - pd[2]); + bdz = (REAL) (pb[2] - pd[2]); + cdz = (REAL) (pc[2] - pd[2]); + + Two_Product(bdx, cdy, bdxcdy1, bdxcdy0); + Two_Product(cdx, bdy, cdxbdy1, cdxbdy0); + Two_Two_Diff(bdxcdy1, bdxcdy0, cdxbdy1, cdxbdy0, bc3, bc[2], bc[1], bc[0]); + bc[3] = bc3; + alen = scale_expansion_zeroelim(4, bc, adz, adet); + + Two_Product(cdx, ady, cdxady1, cdxady0); + Two_Product(adx, cdy, adxcdy1, adxcdy0); + Two_Two_Diff(cdxady1, cdxady0, adxcdy1, adxcdy0, ca3, ca[2], ca[1], ca[0]); + ca[3] = ca3; + blen = scale_expansion_zeroelim(4, ca, bdz, bdet); + + Two_Product(adx, bdy, adxbdy1, adxbdy0); + Two_Product(bdx, ady, bdxady1, bdxady0); + Two_Two_Diff(adxbdy1, adxbdy0, bdxady1, bdxady0, ab3, ab[2], ab[1], ab[0]); + ab[3] = ab3; + clen = scale_expansion_zeroelim(4, ab, cdz, cdet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + finlength = fast_expansion_sum_zeroelim(ablen, abdet, clen, cdet, fin1); + + det = estimate(finlength, fin1); + errbound = o3derrboundB * permanent; + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + Two_Diff_Tail(pa[0], pd[0], adx, adxtail); + Two_Diff_Tail(pb[0], pd[0], bdx, bdxtail); + Two_Diff_Tail(pc[0], pd[0], cdx, cdxtail); + Two_Diff_Tail(pa[1], pd[1], ady, adytail); + Two_Diff_Tail(pb[1], pd[1], bdy, bdytail); + Two_Diff_Tail(pc[1], pd[1], cdy, cdytail); + Two_Diff_Tail(pa[2], pd[2], adz, adztail); + Two_Diff_Tail(pb[2], pd[2], bdz, bdztail); + Two_Diff_Tail(pc[2], pd[2], cdz, cdztail); + + if ((adxtail == 0.0) && (bdxtail == 0.0) && (cdxtail == 0.0) + && (adytail == 0.0) && (bdytail == 0.0) && (cdytail == 0.0) + && (adztail == 0.0) && (bdztail == 0.0) && (cdztail == 0.0)) { + return det; + } + + errbound = o3derrboundC * permanent + resulterrbound * Absolute(det); + det += (adz * ((bdx * cdytail + cdy * bdxtail) + - (bdy * cdxtail + cdx * bdytail)) + + adztail * (bdx * cdy - bdy * cdx)) + + (bdz * ((cdx * adytail + ady * cdxtail) + - (cdy * adxtail + adx * cdytail)) + + bdztail * (cdx * ady - cdy * adx)) + + (cdz * ((adx * bdytail + bdy * adxtail) + - (ady * bdxtail + bdx * adytail)) + + cdztail * (adx * bdy - ady * bdx)); + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + finnow = fin1; + finother = fin2; + + if (adxtail == 0.0) { + if (adytail == 0.0) { + at_b[0] = 0.0; + at_blen = 1; + at_c[0] = 0.0; + at_clen = 1; + } else { + negate = -adytail; + Two_Product(negate, bdx, at_blarge, at_b[0]); + at_b[1] = at_blarge; + at_blen = 2; + Two_Product(adytail, cdx, at_clarge, at_c[0]); + at_c[1] = at_clarge; + at_clen = 2; + } + } else { + if (adytail == 0.0) { + Two_Product(adxtail, bdy, at_blarge, at_b[0]); + at_b[1] = at_blarge; + at_blen = 2; + negate = -adxtail; + Two_Product(negate, cdy, at_clarge, at_c[0]); + at_c[1] = at_clarge; + at_clen = 2; + } else { + Two_Product(adxtail, bdy, adxt_bdy1, adxt_bdy0); + Two_Product(adytail, bdx, adyt_bdx1, adyt_bdx0); + Two_Two_Diff(adxt_bdy1, adxt_bdy0, adyt_bdx1, adyt_bdx0, + at_blarge, at_b[2], at_b[1], at_b[0]); + at_b[3] = at_blarge; + at_blen = 4; + Two_Product(adytail, cdx, adyt_cdx1, adyt_cdx0); + Two_Product(adxtail, cdy, adxt_cdy1, adxt_cdy0); + Two_Two_Diff(adyt_cdx1, adyt_cdx0, adxt_cdy1, adxt_cdy0, + at_clarge, at_c[2], at_c[1], at_c[0]); + at_c[3] = at_clarge; + at_clen = 4; + } + } + if (bdxtail == 0.0) { + if (bdytail == 0.0) { + bt_c[0] = 0.0; + bt_clen = 1; + bt_a[0] = 0.0; + bt_alen = 1; + } else { + negate = -bdytail; + Two_Product(negate, cdx, bt_clarge, bt_c[0]); + bt_c[1] = bt_clarge; + bt_clen = 2; + Two_Product(bdytail, adx, bt_alarge, bt_a[0]); + bt_a[1] = bt_alarge; + bt_alen = 2; + } + } else { + if (bdytail == 0.0) { + Two_Product(bdxtail, cdy, bt_clarge, bt_c[0]); + bt_c[1] = bt_clarge; + bt_clen = 2; + negate = -bdxtail; + Two_Product(negate, ady, bt_alarge, bt_a[0]); + bt_a[1] = bt_alarge; + bt_alen = 2; + } else { + Two_Product(bdxtail, cdy, bdxt_cdy1, bdxt_cdy0); + Two_Product(bdytail, cdx, bdyt_cdx1, bdyt_cdx0); + Two_Two_Diff(bdxt_cdy1, bdxt_cdy0, bdyt_cdx1, bdyt_cdx0, + bt_clarge, bt_c[2], bt_c[1], bt_c[0]); + bt_c[3] = bt_clarge; + bt_clen = 4; + Two_Product(bdytail, adx, bdyt_adx1, bdyt_adx0); + Two_Product(bdxtail, ady, bdxt_ady1, bdxt_ady0); + Two_Two_Diff(bdyt_adx1, bdyt_adx0, bdxt_ady1, bdxt_ady0, + bt_alarge, bt_a[2], bt_a[1], bt_a[0]); + bt_a[3] = bt_alarge; + bt_alen = 4; + } + } + if (cdxtail == 0.0) { + if (cdytail == 0.0) { + ct_a[0] = 0.0; + ct_alen = 1; + ct_b[0] = 0.0; + ct_blen = 1; + } else { + negate = -cdytail; + Two_Product(negate, adx, ct_alarge, ct_a[0]); + ct_a[1] = ct_alarge; + ct_alen = 2; + Two_Product(cdytail, bdx, ct_blarge, ct_b[0]); + ct_b[1] = ct_blarge; + ct_blen = 2; + } + } else { + if (cdytail == 0.0) { + Two_Product(cdxtail, ady, ct_alarge, ct_a[0]); + ct_a[1] = ct_alarge; + ct_alen = 2; + negate = -cdxtail; + Two_Product(negate, bdy, ct_blarge, ct_b[0]); + ct_b[1] = ct_blarge; + ct_blen = 2; + } else { + Two_Product(cdxtail, ady, cdxt_ady1, cdxt_ady0); + Two_Product(cdytail, adx, cdyt_adx1, cdyt_adx0); + Two_Two_Diff(cdxt_ady1, cdxt_ady0, cdyt_adx1, cdyt_adx0, + ct_alarge, ct_a[2], ct_a[1], ct_a[0]); + ct_a[3] = ct_alarge; + ct_alen = 4; + Two_Product(cdytail, bdx, cdyt_bdx1, cdyt_bdx0); + Two_Product(cdxtail, bdy, cdxt_bdy1, cdxt_bdy0); + Two_Two_Diff(cdyt_bdx1, cdyt_bdx0, cdxt_bdy1, cdxt_bdy0, + ct_blarge, ct_b[2], ct_b[1], ct_b[0]); + ct_b[3] = ct_blarge; + ct_blen = 4; + } + } + + bctlen = fast_expansion_sum_zeroelim(bt_clen, bt_c, ct_blen, ct_b, bct); + wlength = scale_expansion_zeroelim(bctlen, bct, adz, w); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, wlength, w, + finother); + finswap = finnow; finnow = finother; finother = finswap; + + catlen = fast_expansion_sum_zeroelim(ct_alen, ct_a, at_clen, at_c, cat); + wlength = scale_expansion_zeroelim(catlen, cat, bdz, w); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, wlength, w, + finother); + finswap = finnow; finnow = finother; finother = finswap; + + abtlen = fast_expansion_sum_zeroelim(at_blen, at_b, bt_alen, bt_a, abt); + wlength = scale_expansion_zeroelim(abtlen, abt, cdz, w); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, wlength, w, + finother); + finswap = finnow; finnow = finother; finother = finswap; + + if (adztail != 0.0) { + vlength = scale_expansion_zeroelim(4, bc, adztail, v); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, vlength, v, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (bdztail != 0.0) { + vlength = scale_expansion_zeroelim(4, ca, bdztail, v); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, vlength, v, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (cdztail != 0.0) { + vlength = scale_expansion_zeroelim(4, ab, cdztail, v); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, vlength, v, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + + if (adxtail != 0.0) { + if (bdytail != 0.0) { + Two_Product(adxtail, bdytail, adxt_bdyt1, adxt_bdyt0); + Two_One_Product(adxt_bdyt1, adxt_bdyt0, cdz, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + if (cdztail != 0.0) { + Two_One_Product(adxt_bdyt1, adxt_bdyt0, cdztail, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + } + if (cdytail != 0.0) { + negate = -adxtail; + Two_Product(negate, cdytail, adxt_cdyt1, adxt_cdyt0); + Two_One_Product(adxt_cdyt1, adxt_cdyt0, bdz, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + if (bdztail != 0.0) { + Two_One_Product(adxt_cdyt1, adxt_cdyt0, bdztail, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + } + } + if (bdxtail != 0.0) { + if (cdytail != 0.0) { + Two_Product(bdxtail, cdytail, bdxt_cdyt1, bdxt_cdyt0); + Two_One_Product(bdxt_cdyt1, bdxt_cdyt0, adz, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + if (adztail != 0.0) { + Two_One_Product(bdxt_cdyt1, bdxt_cdyt0, adztail, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + } + if (adytail != 0.0) { + negate = -bdxtail; + Two_Product(negate, adytail, bdxt_adyt1, bdxt_adyt0); + Two_One_Product(bdxt_adyt1, bdxt_adyt0, cdz, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + if (cdztail != 0.0) { + Two_One_Product(bdxt_adyt1, bdxt_adyt0, cdztail, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + } + } + if (cdxtail != 0.0) { + if (adytail != 0.0) { + Two_Product(cdxtail, adytail, cdxt_adyt1, cdxt_adyt0); + Two_One_Product(cdxt_adyt1, cdxt_adyt0, bdz, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + if (bdztail != 0.0) { + Two_One_Product(cdxt_adyt1, cdxt_adyt0, bdztail, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + } + if (bdytail != 0.0) { + negate = -cdxtail; + Two_Product(negate, bdytail, cdxt_bdyt1, cdxt_bdyt0); + Two_One_Product(cdxt_bdyt1, cdxt_bdyt0, adz, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + if (adztail != 0.0) { + Two_One_Product(cdxt_bdyt1, cdxt_bdyt0, adztail, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + } + } + + if (adztail != 0.0) { + wlength = scale_expansion_zeroelim(bctlen, bct, adztail, w); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, wlength, w, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (bdztail != 0.0) { + wlength = scale_expansion_zeroelim(catlen, cat, bdztail, w); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, wlength, w, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (cdztail != 0.0) { + wlength = scale_expansion_zeroelim(abtlen, abt, cdztail, w); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, wlength, w, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + + return finnow[finlength - 1]; +} + +REAL orient3d(pa, pb, pc, pd) +REAL *pa; +REAL *pb; +REAL *pc; +REAL *pd; +{ + REAL adx, bdx, cdx, ady, bdy, cdy, adz, bdz, cdz; + REAL bdxcdy, cdxbdy, cdxady, adxcdy, adxbdy, bdxady; + REAL det; + REAL permanent, errbound; + + adx = pa[0] - pd[0]; + bdx = pb[0] - pd[0]; + cdx = pc[0] - pd[0]; + ady = pa[1] - pd[1]; + bdy = pb[1] - pd[1]; + cdy = pc[1] - pd[1]; + adz = pa[2] - pd[2]; + bdz = pb[2] - pd[2]; + cdz = pc[2] - pd[2]; + + bdxcdy = bdx * cdy; + cdxbdy = cdx * bdy; + + cdxady = cdx * ady; + adxcdy = adx * cdy; + + adxbdy = adx * bdy; + bdxady = bdx * ady; + + det = adz * (bdxcdy - cdxbdy) + + bdz * (cdxady - adxcdy) + + cdz * (adxbdy - bdxady); + + permanent = (Absolute(bdxcdy) + Absolute(cdxbdy)) * Absolute(adz) + + (Absolute(cdxady) + Absolute(adxcdy)) * Absolute(bdz) + + (Absolute(adxbdy) + Absolute(bdxady)) * Absolute(cdz); + errbound = o3derrboundA * permanent; + if ((det > errbound) || (-det > errbound)) { + return det; + } + + return orient3dadapt(pa, pb, pc, pd, permanent); +} + +/*****************************************************************************/ +/* */ +/* incirclefast() Approximate 2D incircle test. Nonrobust. */ +/* incircleexact() Exact 2D incircle test. Robust. */ +/* incircleslow() Another exact 2D incircle test. Robust. */ +/* incircle() Adaptive exact 2D incircle test. Robust. */ +/* */ +/* Return a positive value if the point pd lies inside the */ +/* circle passing through pa, pb, and pc; a negative value if */ +/* it lies outside; and zero if the four points are cocircular.*/ +/* The points pa, pb, and pc must be in counterclockwise */ +/* order, or the sign of the result will be reversed. */ +/* */ +/* Only the first and last routine should be used; the middle two are for */ +/* timings. */ +/* */ +/* The last three use exact arithmetic to ensure a correct answer. The */ +/* result returned is the determinant of a matrix. In incircle() only, */ +/* this determinant is computed adaptively, in the sense that exact */ +/* arithmetic is used only to the degree it is needed to ensure that the */ +/* returned value has the correct sign. Hence, incircle() is usually quite */ +/* fast, but will run more slowly when the input points are cocircular or */ +/* nearly so. */ +/* */ +/*****************************************************************************/ + +REAL incirclefast(pa, pb, pc, pd) +REAL *pa; +REAL *pb; +REAL *pc; +REAL *pd; +{ + REAL adx, ady, bdx, bdy, cdx, cdy; + REAL abdet, bcdet, cadet; + REAL alift, blift, clift; + + adx = pa[0] - pd[0]; + ady = pa[1] - pd[1]; + bdx = pb[0] - pd[0]; + bdy = pb[1] - pd[1]; + cdx = pc[0] - pd[0]; + cdy = pc[1] - pd[1]; + + abdet = adx * bdy - bdx * ady; + bcdet = bdx * cdy - cdx * bdy; + cadet = cdx * ady - adx * cdy; + alift = adx * adx + ady * ady; + blift = bdx * bdx + bdy * bdy; + clift = cdx * cdx + cdy * cdy; + + return alift * bcdet + blift * cadet + clift * abdet; +} + +REAL incircleexact(pa, pb, pc, pd) +REAL *pa; +REAL *pb; +REAL *pc; +REAL *pd; +{ + INEXACT REAL axby1, bxcy1, cxdy1, dxay1, axcy1, bxdy1; + INEXACT REAL bxay1, cxby1, dxcy1, axdy1, cxay1, dxby1; + REAL axby0, bxcy0, cxdy0, dxay0, axcy0, bxdy0; + REAL bxay0, cxby0, dxcy0, axdy0, cxay0, dxby0; + REAL ab[4], bc[4], cd[4], da[4], ac[4], bd[4]; + REAL temp8[8]; + int templen; + REAL abc[12], bcd[12], cda[12], dab[12]; + int abclen, bcdlen, cdalen, dablen; + REAL det24x[24], det24y[24], det48x[48], det48y[48]; + int xlen, ylen; + REAL adet[96], bdet[96], cdet[96], ddet[96]; + int alen, blen, clen, dlen; + REAL abdet[192], cddet[192]; + int ablen, cdlen; + REAL deter[384]; + int deterlen; + int i; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j; + REAL _0; + + Two_Product(pa[0], pb[1], axby1, axby0); + Two_Product(pb[0], pa[1], bxay1, bxay0); + Two_Two_Diff(axby1, axby0, bxay1, bxay0, ab[3], ab[2], ab[1], ab[0]); + + Two_Product(pb[0], pc[1], bxcy1, bxcy0); + Two_Product(pc[0], pb[1], cxby1, cxby0); + Two_Two_Diff(bxcy1, bxcy0, cxby1, cxby0, bc[3], bc[2], bc[1], bc[0]); + + Two_Product(pc[0], pd[1], cxdy1, cxdy0); + Two_Product(pd[0], pc[1], dxcy1, dxcy0); + Two_Two_Diff(cxdy1, cxdy0, dxcy1, dxcy0, cd[3], cd[2], cd[1], cd[0]); + + Two_Product(pd[0], pa[1], dxay1, dxay0); + Two_Product(pa[0], pd[1], axdy1, axdy0); + Two_Two_Diff(dxay1, dxay0, axdy1, axdy0, da[3], da[2], da[1], da[0]); + + Two_Product(pa[0], pc[1], axcy1, axcy0); + Two_Product(pc[0], pa[1], cxay1, cxay0); + Two_Two_Diff(axcy1, axcy0, cxay1, cxay0, ac[3], ac[2], ac[1], ac[0]); + + Two_Product(pb[0], pd[1], bxdy1, bxdy0); + Two_Product(pd[0], pb[1], dxby1, dxby0); + Two_Two_Diff(bxdy1, bxdy0, dxby1, dxby0, bd[3], bd[2], bd[1], bd[0]); + + templen = fast_expansion_sum_zeroelim(4, cd, 4, da, temp8); + cdalen = fast_expansion_sum_zeroelim(templen, temp8, 4, ac, cda); + templen = fast_expansion_sum_zeroelim(4, da, 4, ab, temp8); + dablen = fast_expansion_sum_zeroelim(templen, temp8, 4, bd, dab); + for (i = 0; i < 4; i++) { + bd[i] = -bd[i]; + ac[i] = -ac[i]; + } + templen = fast_expansion_sum_zeroelim(4, ab, 4, bc, temp8); + abclen = fast_expansion_sum_zeroelim(templen, temp8, 4, ac, abc); + templen = fast_expansion_sum_zeroelim(4, bc, 4, cd, temp8); + bcdlen = fast_expansion_sum_zeroelim(templen, temp8, 4, bd, bcd); + + xlen = scale_expansion_zeroelim(bcdlen, bcd, pa[0], det24x); + xlen = scale_expansion_zeroelim(xlen, det24x, pa[0], det48x); + ylen = scale_expansion_zeroelim(bcdlen, bcd, pa[1], det24y); + ylen = scale_expansion_zeroelim(ylen, det24y, pa[1], det48y); + alen = fast_expansion_sum_zeroelim(xlen, det48x, ylen, det48y, adet); + + xlen = scale_expansion_zeroelim(cdalen, cda, pb[0], det24x); + xlen = scale_expansion_zeroelim(xlen, det24x, -pb[0], det48x); + ylen = scale_expansion_zeroelim(cdalen, cda, pb[1], det24y); + ylen = scale_expansion_zeroelim(ylen, det24y, -pb[1], det48y); + blen = fast_expansion_sum_zeroelim(xlen, det48x, ylen, det48y, bdet); + + xlen = scale_expansion_zeroelim(dablen, dab, pc[0], det24x); + xlen = scale_expansion_zeroelim(xlen, det24x, pc[0], det48x); + ylen = scale_expansion_zeroelim(dablen, dab, pc[1], det24y); + ylen = scale_expansion_zeroelim(ylen, det24y, pc[1], det48y); + clen = fast_expansion_sum_zeroelim(xlen, det48x, ylen, det48y, cdet); + + xlen = scale_expansion_zeroelim(abclen, abc, pd[0], det24x); + xlen = scale_expansion_zeroelim(xlen, det24x, -pd[0], det48x); + ylen = scale_expansion_zeroelim(abclen, abc, pd[1], det24y); + ylen = scale_expansion_zeroelim(ylen, det24y, -pd[1], det48y); + dlen = fast_expansion_sum_zeroelim(xlen, det48x, ylen, det48y, ddet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + cdlen = fast_expansion_sum_zeroelim(clen, cdet, dlen, ddet, cddet); + deterlen = fast_expansion_sum_zeroelim(ablen, abdet, cdlen, cddet, deter); + + return deter[deterlen - 1]; +} + +REAL incircleslow(pa, pb, pc, pd) +REAL *pa; +REAL *pb; +REAL *pc; +REAL *pd; +{ + INEXACT REAL adx, bdx, cdx, ady, bdy, cdy; + REAL adxtail, bdxtail, cdxtail; + REAL adytail, bdytail, cdytail; + REAL negate, negatetail; + INEXACT REAL axby7, bxcy7, axcy7, bxay7, cxby7, cxay7; + REAL axby[8], bxcy[8], axcy[8], bxay[8], cxby[8], cxay[8]; + REAL temp16[16]; + int temp16len; + REAL detx[32], detxx[64], detxt[32], detxxt[64], detxtxt[64]; + int xlen, xxlen, xtlen, xxtlen, xtxtlen; + REAL x1[128], x2[192]; + int x1len, x2len; + REAL dety[32], detyy[64], detyt[32], detyyt[64], detytyt[64]; + int ylen, yylen, ytlen, yytlen, ytytlen; + REAL y1[128], y2[192]; + int y1len, y2len; + REAL adet[384], bdet[384], cdet[384], abdet[768], deter[1152]; + int alen, blen, clen, ablen, deterlen; + int i; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL a0hi, a0lo, a1hi, a1lo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j, _k, _l, _m, _n; + REAL _0, _1, _2; + + Two_Diff(pa[0], pd[0], adx, adxtail); + Two_Diff(pa[1], pd[1], ady, adytail); + Two_Diff(pb[0], pd[0], bdx, bdxtail); + Two_Diff(pb[1], pd[1], bdy, bdytail); + Two_Diff(pc[0], pd[0], cdx, cdxtail); + Two_Diff(pc[1], pd[1], cdy, cdytail); + + Two_Two_Product(adx, adxtail, bdy, bdytail, + axby7, axby[6], axby[5], axby[4], + axby[3], axby[2], axby[1], axby[0]); + axby[7] = axby7; + negate = -ady; + negatetail = -adytail; + Two_Two_Product(bdx, bdxtail, negate, negatetail, + bxay7, bxay[6], bxay[5], bxay[4], + bxay[3], bxay[2], bxay[1], bxay[0]); + bxay[7] = bxay7; + Two_Two_Product(bdx, bdxtail, cdy, cdytail, + bxcy7, bxcy[6], bxcy[5], bxcy[4], + bxcy[3], bxcy[2], bxcy[1], bxcy[0]); + bxcy[7] = bxcy7; + negate = -bdy; + negatetail = -bdytail; + Two_Two_Product(cdx, cdxtail, negate, negatetail, + cxby7, cxby[6], cxby[5], cxby[4], + cxby[3], cxby[2], cxby[1], cxby[0]); + cxby[7] = cxby7; + Two_Two_Product(cdx, cdxtail, ady, adytail, + cxay7, cxay[6], cxay[5], cxay[4], + cxay[3], cxay[2], cxay[1], cxay[0]); + cxay[7] = cxay7; + negate = -cdy; + negatetail = -cdytail; + Two_Two_Product(adx, adxtail, negate, negatetail, + axcy7, axcy[6], axcy[5], axcy[4], + axcy[3], axcy[2], axcy[1], axcy[0]); + axcy[7] = axcy7; + + + temp16len = fast_expansion_sum_zeroelim(8, bxcy, 8, cxby, temp16); + + xlen = scale_expansion_zeroelim(temp16len, temp16, adx, detx); + xxlen = scale_expansion_zeroelim(xlen, detx, adx, detxx); + xtlen = scale_expansion_zeroelim(temp16len, temp16, adxtail, detxt); + xxtlen = scale_expansion_zeroelim(xtlen, detxt, adx, detxxt); + for (i = 0; i < xxtlen; i++) { + detxxt[i] *= 2.0; + } + xtxtlen = scale_expansion_zeroelim(xtlen, detxt, adxtail, detxtxt); + x1len = fast_expansion_sum_zeroelim(xxlen, detxx, xxtlen, detxxt, x1); + x2len = fast_expansion_sum_zeroelim(x1len, x1, xtxtlen, detxtxt, x2); + + ylen = scale_expansion_zeroelim(temp16len, temp16, ady, dety); + yylen = scale_expansion_zeroelim(ylen, dety, ady, detyy); + ytlen = scale_expansion_zeroelim(temp16len, temp16, adytail, detyt); + yytlen = scale_expansion_zeroelim(ytlen, detyt, ady, detyyt); + for (i = 0; i < yytlen; i++) { + detyyt[i] *= 2.0; + } + ytytlen = scale_expansion_zeroelim(ytlen, detyt, adytail, detytyt); + y1len = fast_expansion_sum_zeroelim(yylen, detyy, yytlen, detyyt, y1); + y2len = fast_expansion_sum_zeroelim(y1len, y1, ytytlen, detytyt, y2); + + alen = fast_expansion_sum_zeroelim(x2len, x2, y2len, y2, adet); + + + temp16len = fast_expansion_sum_zeroelim(8, cxay, 8, axcy, temp16); + + xlen = scale_expansion_zeroelim(temp16len, temp16, bdx, detx); + xxlen = scale_expansion_zeroelim(xlen, detx, bdx, detxx); + xtlen = scale_expansion_zeroelim(temp16len, temp16, bdxtail, detxt); + xxtlen = scale_expansion_zeroelim(xtlen, detxt, bdx, detxxt); + for (i = 0; i < xxtlen; i++) { + detxxt[i] *= 2.0; + } + xtxtlen = scale_expansion_zeroelim(xtlen, detxt, bdxtail, detxtxt); + x1len = fast_expansion_sum_zeroelim(xxlen, detxx, xxtlen, detxxt, x1); + x2len = fast_expansion_sum_zeroelim(x1len, x1, xtxtlen, detxtxt, x2); + + ylen = scale_expansion_zeroelim(temp16len, temp16, bdy, dety); + yylen = scale_expansion_zeroelim(ylen, dety, bdy, detyy); + ytlen = scale_expansion_zeroelim(temp16len, temp16, bdytail, detyt); + yytlen = scale_expansion_zeroelim(ytlen, detyt, bdy, detyyt); + for (i = 0; i < yytlen; i++) { + detyyt[i] *= 2.0; + } + ytytlen = scale_expansion_zeroelim(ytlen, detyt, bdytail, detytyt); + y1len = fast_expansion_sum_zeroelim(yylen, detyy, yytlen, detyyt, y1); + y2len = fast_expansion_sum_zeroelim(y1len, y1, ytytlen, detytyt, y2); + + blen = fast_expansion_sum_zeroelim(x2len, x2, y2len, y2, bdet); + + + temp16len = fast_expansion_sum_zeroelim(8, axby, 8, bxay, temp16); + + xlen = scale_expansion_zeroelim(temp16len, temp16, cdx, detx); + xxlen = scale_expansion_zeroelim(xlen, detx, cdx, detxx); + xtlen = scale_expansion_zeroelim(temp16len, temp16, cdxtail, detxt); + xxtlen = scale_expansion_zeroelim(xtlen, detxt, cdx, detxxt); + for (i = 0; i < xxtlen; i++) { + detxxt[i] *= 2.0; + } + xtxtlen = scale_expansion_zeroelim(xtlen, detxt, cdxtail, detxtxt); + x1len = fast_expansion_sum_zeroelim(xxlen, detxx, xxtlen, detxxt, x1); + x2len = fast_expansion_sum_zeroelim(x1len, x1, xtxtlen, detxtxt, x2); + + ylen = scale_expansion_zeroelim(temp16len, temp16, cdy, dety); + yylen = scale_expansion_zeroelim(ylen, dety, cdy, detyy); + ytlen = scale_expansion_zeroelim(temp16len, temp16, cdytail, detyt); + yytlen = scale_expansion_zeroelim(ytlen, detyt, cdy, detyyt); + for (i = 0; i < yytlen; i++) { + detyyt[i] *= 2.0; + } + ytytlen = scale_expansion_zeroelim(ytlen, detyt, cdytail, detytyt); + y1len = fast_expansion_sum_zeroelim(yylen, detyy, yytlen, detyyt, y1); + y2len = fast_expansion_sum_zeroelim(y1len, y1, ytytlen, detytyt, y2); + + clen = fast_expansion_sum_zeroelim(x2len, x2, y2len, y2, cdet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + deterlen = fast_expansion_sum_zeroelim(ablen, abdet, clen, cdet, deter); + + return deter[deterlen - 1]; +} + +REAL incircleadapt(pa, pb, pc, pd, permanent) +REAL *pa; +REAL *pb; +REAL *pc; +REAL *pd; +REAL permanent; +{ + INEXACT REAL adx, bdx, cdx, ady, bdy, cdy; + REAL det, errbound; + + INEXACT REAL bdxcdy1, cdxbdy1, cdxady1, adxcdy1, adxbdy1, bdxady1; + REAL bdxcdy0, cdxbdy0, cdxady0, adxcdy0, adxbdy0, bdxady0; + REAL bc[4], ca[4], ab[4]; + INEXACT REAL bc3, ca3, ab3; + REAL axbc[8], axxbc[16], aybc[8], ayybc[16], adet[32]; + int axbclen, axxbclen, aybclen, ayybclen, alen; + REAL bxca[8], bxxca[16], byca[8], byyca[16], bdet[32]; + int bxcalen, bxxcalen, bycalen, byycalen, blen; + REAL cxab[8], cxxab[16], cyab[8], cyyab[16], cdet[32]; + int cxablen, cxxablen, cyablen, cyyablen, clen; + REAL abdet[64]; + int ablen; + REAL fin1[1152], fin2[1152]; + REAL *finnow, *finother, *finswap; + int finlength; + + REAL adxtail, bdxtail, cdxtail, adytail, bdytail, cdytail; + INEXACT REAL adxadx1, adyady1, bdxbdx1, bdybdy1, cdxcdx1, cdycdy1; + REAL adxadx0, adyady0, bdxbdx0, bdybdy0, cdxcdx0, cdycdy0; + REAL aa[4], bb[4], cc[4]; + INEXACT REAL aa3, bb3, cc3; + INEXACT REAL ti1, tj1; + REAL ti0, tj0; + REAL u[4], v[4]; + INEXACT REAL u3, v3; + REAL temp8[8], temp16a[16], temp16b[16], temp16c[16]; + REAL temp32a[32], temp32b[32], temp48[48], temp64[64]; + int temp8len, temp16alen, temp16blen, temp16clen; + int temp32alen, temp32blen, temp48len, temp64len; + REAL axtbb[8], axtcc[8], aytbb[8], aytcc[8]; + int axtbblen, axtcclen, aytbblen, aytcclen; + REAL bxtaa[8], bxtcc[8], bytaa[8], bytcc[8]; + int bxtaalen, bxtcclen, bytaalen, bytcclen; + REAL cxtaa[8], cxtbb[8], cytaa[8], cytbb[8]; + int cxtaalen, cxtbblen, cytaalen, cytbblen; + REAL axtbc[8], aytbc[8], bxtca[8], bytca[8], cxtab[8], cytab[8]; + int axtbclen, aytbclen, bxtcalen, bytcalen, cxtablen, cytablen; + REAL axtbct[16], aytbct[16], bxtcat[16], bytcat[16], cxtabt[16], cytabt[16]; + int axtbctlen, aytbctlen, bxtcatlen, bytcatlen, cxtabtlen, cytabtlen; + REAL axtbctt[8], aytbctt[8], bxtcatt[8]; + REAL bytcatt[8], cxtabtt[8], cytabtt[8]; + int axtbcttlen, aytbcttlen, bxtcattlen, bytcattlen, cxtabttlen, cytabttlen; + REAL abt[8], bct[8], cat[8]; + int abtlen, bctlen, catlen; + REAL abtt[4], bctt[4], catt[4]; + int abttlen, bcttlen, cattlen; + INEXACT REAL abtt3, bctt3, catt3; + REAL negate; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j; + REAL _0; + + adx = (REAL) (pa[0] - pd[0]); + bdx = (REAL) (pb[0] - pd[0]); + cdx = (REAL) (pc[0] - pd[0]); + ady = (REAL) (pa[1] - pd[1]); + bdy = (REAL) (pb[1] - pd[1]); + cdy = (REAL) (pc[1] - pd[1]); + + Two_Product(bdx, cdy, bdxcdy1, bdxcdy0); + Two_Product(cdx, bdy, cdxbdy1, cdxbdy0); + Two_Two_Diff(bdxcdy1, bdxcdy0, cdxbdy1, cdxbdy0, bc3, bc[2], bc[1], bc[0]); + bc[3] = bc3; + axbclen = scale_expansion_zeroelim(4, bc, adx, axbc); + axxbclen = scale_expansion_zeroelim(axbclen, axbc, adx, axxbc); + aybclen = scale_expansion_zeroelim(4, bc, ady, aybc); + ayybclen = scale_expansion_zeroelim(aybclen, aybc, ady, ayybc); + alen = fast_expansion_sum_zeroelim(axxbclen, axxbc, ayybclen, ayybc, adet); + + Two_Product(cdx, ady, cdxady1, cdxady0); + Two_Product(adx, cdy, adxcdy1, adxcdy0); + Two_Two_Diff(cdxady1, cdxady0, adxcdy1, adxcdy0, ca3, ca[2], ca[1], ca[0]); + ca[3] = ca3; + bxcalen = scale_expansion_zeroelim(4, ca, bdx, bxca); + bxxcalen = scale_expansion_zeroelim(bxcalen, bxca, bdx, bxxca); + bycalen = scale_expansion_zeroelim(4, ca, bdy, byca); + byycalen = scale_expansion_zeroelim(bycalen, byca, bdy, byyca); + blen = fast_expansion_sum_zeroelim(bxxcalen, bxxca, byycalen, byyca, bdet); + + Two_Product(adx, bdy, adxbdy1, adxbdy0); + Two_Product(bdx, ady, bdxady1, bdxady0); + Two_Two_Diff(adxbdy1, adxbdy0, bdxady1, bdxady0, ab3, ab[2], ab[1], ab[0]); + ab[3] = ab3; + cxablen = scale_expansion_zeroelim(4, ab, cdx, cxab); + cxxablen = scale_expansion_zeroelim(cxablen, cxab, cdx, cxxab); + cyablen = scale_expansion_zeroelim(4, ab, cdy, cyab); + cyyablen = scale_expansion_zeroelim(cyablen, cyab, cdy, cyyab); + clen = fast_expansion_sum_zeroelim(cxxablen, cxxab, cyyablen, cyyab, cdet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + finlength = fast_expansion_sum_zeroelim(ablen, abdet, clen, cdet, fin1); + + det = estimate(finlength, fin1); + errbound = iccerrboundB * permanent; + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + Two_Diff_Tail(pa[0], pd[0], adx, adxtail); + Two_Diff_Tail(pa[1], pd[1], ady, adytail); + Two_Diff_Tail(pb[0], pd[0], bdx, bdxtail); + Two_Diff_Tail(pb[1], pd[1], bdy, bdytail); + Two_Diff_Tail(pc[0], pd[0], cdx, cdxtail); + Two_Diff_Tail(pc[1], pd[1], cdy, cdytail); + if ((adxtail == 0.0) && (bdxtail == 0.0) && (cdxtail == 0.0) + && (adytail == 0.0) && (bdytail == 0.0) && (cdytail == 0.0)) { + return det; + } + + errbound = iccerrboundC * permanent + resulterrbound * Absolute(det); + det += ((adx * adx + ady * ady) * ((bdx * cdytail + cdy * bdxtail) + - (bdy * cdxtail + cdx * bdytail)) + + 2.0 * (adx * adxtail + ady * adytail) * (bdx * cdy - bdy * cdx)) + + ((bdx * bdx + bdy * bdy) * ((cdx * adytail + ady * cdxtail) + - (cdy * adxtail + adx * cdytail)) + + 2.0 * (bdx * bdxtail + bdy * bdytail) * (cdx * ady - cdy * adx)) + + ((cdx * cdx + cdy * cdy) * ((adx * bdytail + bdy * adxtail) + - (ady * bdxtail + bdx * adytail)) + + 2.0 * (cdx * cdxtail + cdy * cdytail) * (adx * bdy - ady * bdx)); + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + finnow = fin1; + finother = fin2; + + if ((bdxtail != 0.0) || (bdytail != 0.0) + || (cdxtail != 0.0) || (cdytail != 0.0)) { + Square(adx, adxadx1, adxadx0); + Square(ady, adyady1, adyady0); + Two_Two_Sum(adxadx1, adxadx0, adyady1, adyady0, aa3, aa[2], aa[1], aa[0]); + aa[3] = aa3; + } + if ((cdxtail != 0.0) || (cdytail != 0.0) + || (adxtail != 0.0) || (adytail != 0.0)) { + Square(bdx, bdxbdx1, bdxbdx0); + Square(bdy, bdybdy1, bdybdy0); + Two_Two_Sum(bdxbdx1, bdxbdx0, bdybdy1, bdybdy0, bb3, bb[2], bb[1], bb[0]); + bb[3] = bb3; + } + if ((adxtail != 0.0) || (adytail != 0.0) + || (bdxtail != 0.0) || (bdytail != 0.0)) { + Square(cdx, cdxcdx1, cdxcdx0); + Square(cdy, cdycdy1, cdycdy0); + Two_Two_Sum(cdxcdx1, cdxcdx0, cdycdy1, cdycdy0, cc3, cc[2], cc[1], cc[0]); + cc[3] = cc3; + } + + if (adxtail != 0.0) { + axtbclen = scale_expansion_zeroelim(4, bc, adxtail, axtbc); + temp16alen = scale_expansion_zeroelim(axtbclen, axtbc, 2.0 * adx, + temp16a); + + axtcclen = scale_expansion_zeroelim(4, cc, adxtail, axtcc); + temp16blen = scale_expansion_zeroelim(axtcclen, axtcc, bdy, temp16b); + + axtbblen = scale_expansion_zeroelim(4, bb, adxtail, axtbb); + temp16clen = scale_expansion_zeroelim(axtbblen, axtbb, -cdy, temp16c); + + temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (adytail != 0.0) { + aytbclen = scale_expansion_zeroelim(4, bc, adytail, aytbc); + temp16alen = scale_expansion_zeroelim(aytbclen, aytbc, 2.0 * ady, + temp16a); + + aytbblen = scale_expansion_zeroelim(4, bb, adytail, aytbb); + temp16blen = scale_expansion_zeroelim(aytbblen, aytbb, cdx, temp16b); + + aytcclen = scale_expansion_zeroelim(4, cc, adytail, aytcc); + temp16clen = scale_expansion_zeroelim(aytcclen, aytcc, -bdx, temp16c); + + temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (bdxtail != 0.0) { + bxtcalen = scale_expansion_zeroelim(4, ca, bdxtail, bxtca); + temp16alen = scale_expansion_zeroelim(bxtcalen, bxtca, 2.0 * bdx, + temp16a); + + bxtaalen = scale_expansion_zeroelim(4, aa, bdxtail, bxtaa); + temp16blen = scale_expansion_zeroelim(bxtaalen, bxtaa, cdy, temp16b); + + bxtcclen = scale_expansion_zeroelim(4, cc, bdxtail, bxtcc); + temp16clen = scale_expansion_zeroelim(bxtcclen, bxtcc, -ady, temp16c); + + temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (bdytail != 0.0) { + bytcalen = scale_expansion_zeroelim(4, ca, bdytail, bytca); + temp16alen = scale_expansion_zeroelim(bytcalen, bytca, 2.0 * bdy, + temp16a); + + bytcclen = scale_expansion_zeroelim(4, cc, bdytail, bytcc); + temp16blen = scale_expansion_zeroelim(bytcclen, bytcc, adx, temp16b); + + bytaalen = scale_expansion_zeroelim(4, aa, bdytail, bytaa); + temp16clen = scale_expansion_zeroelim(bytaalen, bytaa, -cdx, temp16c); + + temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (cdxtail != 0.0) { + cxtablen = scale_expansion_zeroelim(4, ab, cdxtail, cxtab); + temp16alen = scale_expansion_zeroelim(cxtablen, cxtab, 2.0 * cdx, + temp16a); + + cxtbblen = scale_expansion_zeroelim(4, bb, cdxtail, cxtbb); + temp16blen = scale_expansion_zeroelim(cxtbblen, cxtbb, ady, temp16b); + + cxtaalen = scale_expansion_zeroelim(4, aa, cdxtail, cxtaa); + temp16clen = scale_expansion_zeroelim(cxtaalen, cxtaa, -bdy, temp16c); + + temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (cdytail != 0.0) { + cytablen = scale_expansion_zeroelim(4, ab, cdytail, cytab); + temp16alen = scale_expansion_zeroelim(cytablen, cytab, 2.0 * cdy, + temp16a); + + cytaalen = scale_expansion_zeroelim(4, aa, cdytail, cytaa); + temp16blen = scale_expansion_zeroelim(cytaalen, cytaa, bdx, temp16b); + + cytbblen = scale_expansion_zeroelim(4, bb, cdytail, cytbb); + temp16clen = scale_expansion_zeroelim(cytbblen, cytbb, -adx, temp16c); + + temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + + if ((adxtail != 0.0) || (adytail != 0.0)) { + if ((bdxtail != 0.0) || (bdytail != 0.0) + || (cdxtail != 0.0) || (cdytail != 0.0)) { + Two_Product(bdxtail, cdy, ti1, ti0); + Two_Product(bdx, cdytail, tj1, tj0); + Two_Two_Sum(ti1, ti0, tj1, tj0, u3, u[2], u[1], u[0]); + u[3] = u3; + negate = -bdy; + Two_Product(cdxtail, negate, ti1, ti0); + negate = -bdytail; + Two_Product(cdx, negate, tj1, tj0); + Two_Two_Sum(ti1, ti0, tj1, tj0, v3, v[2], v[1], v[0]); + v[3] = v3; + bctlen = fast_expansion_sum_zeroelim(4, u, 4, v, bct); + + Two_Product(bdxtail, cdytail, ti1, ti0); + Two_Product(cdxtail, bdytail, tj1, tj0); + Two_Two_Diff(ti1, ti0, tj1, tj0, bctt3, bctt[2], bctt[1], bctt[0]); + bctt[3] = bctt3; + bcttlen = 4; + } else { + bct[0] = 0.0; + bctlen = 1; + bctt[0] = 0.0; + bcttlen = 1; + } + + if (adxtail != 0.0) { + temp16alen = scale_expansion_zeroelim(axtbclen, axtbc, adxtail, temp16a); + axtbctlen = scale_expansion_zeroelim(bctlen, bct, adxtail, axtbct); + temp32alen = scale_expansion_zeroelim(axtbctlen, axtbct, 2.0 * adx, + temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + if (bdytail != 0.0) { + temp8len = scale_expansion_zeroelim(4, cc, adxtail, temp8); + temp16alen = scale_expansion_zeroelim(temp8len, temp8, bdytail, + temp16a); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, + temp16a, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (cdytail != 0.0) { + temp8len = scale_expansion_zeroelim(4, bb, -adxtail, temp8); + temp16alen = scale_expansion_zeroelim(temp8len, temp8, cdytail, + temp16a); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, + temp16a, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + + temp32alen = scale_expansion_zeroelim(axtbctlen, axtbct, adxtail, + temp32a); + axtbcttlen = scale_expansion_zeroelim(bcttlen, bctt, adxtail, axtbctt); + temp16alen = scale_expansion_zeroelim(axtbcttlen, axtbctt, 2.0 * adx, + temp16a); + temp16blen = scale_expansion_zeroelim(axtbcttlen, axtbctt, adxtail, + temp16b); + temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32b); + temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, + temp64, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (adytail != 0.0) { + temp16alen = scale_expansion_zeroelim(aytbclen, aytbc, adytail, temp16a); + aytbctlen = scale_expansion_zeroelim(bctlen, bct, adytail, aytbct); + temp32alen = scale_expansion_zeroelim(aytbctlen, aytbct, 2.0 * ady, + temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + + + temp32alen = scale_expansion_zeroelim(aytbctlen, aytbct, adytail, + temp32a); + aytbcttlen = scale_expansion_zeroelim(bcttlen, bctt, adytail, aytbctt); + temp16alen = scale_expansion_zeroelim(aytbcttlen, aytbctt, 2.0 * ady, + temp16a); + temp16blen = scale_expansion_zeroelim(aytbcttlen, aytbctt, adytail, + temp16b); + temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32b); + temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, + temp64, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + } + if ((bdxtail != 0.0) || (bdytail != 0.0)) { + if ((cdxtail != 0.0) || (cdytail != 0.0) + || (adxtail != 0.0) || (adytail != 0.0)) { + Two_Product(cdxtail, ady, ti1, ti0); + Two_Product(cdx, adytail, tj1, tj0); + Two_Two_Sum(ti1, ti0, tj1, tj0, u3, u[2], u[1], u[0]); + u[3] = u3; + negate = -cdy; + Two_Product(adxtail, negate, ti1, ti0); + negate = -cdytail; + Two_Product(adx, negate, tj1, tj0); + Two_Two_Sum(ti1, ti0, tj1, tj0, v3, v[2], v[1], v[0]); + v[3] = v3; + catlen = fast_expansion_sum_zeroelim(4, u, 4, v, cat); + + Two_Product(cdxtail, adytail, ti1, ti0); + Two_Product(adxtail, cdytail, tj1, tj0); + Two_Two_Diff(ti1, ti0, tj1, tj0, catt3, catt[2], catt[1], catt[0]); + catt[3] = catt3; + cattlen = 4; + } else { + cat[0] = 0.0; + catlen = 1; + catt[0] = 0.0; + cattlen = 1; + } + + if (bdxtail != 0.0) { + temp16alen = scale_expansion_zeroelim(bxtcalen, bxtca, bdxtail, temp16a); + bxtcatlen = scale_expansion_zeroelim(catlen, cat, bdxtail, bxtcat); + temp32alen = scale_expansion_zeroelim(bxtcatlen, bxtcat, 2.0 * bdx, + temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + if (cdytail != 0.0) { + temp8len = scale_expansion_zeroelim(4, aa, bdxtail, temp8); + temp16alen = scale_expansion_zeroelim(temp8len, temp8, cdytail, + temp16a); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, + temp16a, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (adytail != 0.0) { + temp8len = scale_expansion_zeroelim(4, cc, -bdxtail, temp8); + temp16alen = scale_expansion_zeroelim(temp8len, temp8, adytail, + temp16a); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, + temp16a, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + + temp32alen = scale_expansion_zeroelim(bxtcatlen, bxtcat, bdxtail, + temp32a); + bxtcattlen = scale_expansion_zeroelim(cattlen, catt, bdxtail, bxtcatt); + temp16alen = scale_expansion_zeroelim(bxtcattlen, bxtcatt, 2.0 * bdx, + temp16a); + temp16blen = scale_expansion_zeroelim(bxtcattlen, bxtcatt, bdxtail, + temp16b); + temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32b); + temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, + temp64, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (bdytail != 0.0) { + temp16alen = scale_expansion_zeroelim(bytcalen, bytca, bdytail, temp16a); + bytcatlen = scale_expansion_zeroelim(catlen, cat, bdytail, bytcat); + temp32alen = scale_expansion_zeroelim(bytcatlen, bytcat, 2.0 * bdy, + temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + + + temp32alen = scale_expansion_zeroelim(bytcatlen, bytcat, bdytail, + temp32a); + bytcattlen = scale_expansion_zeroelim(cattlen, catt, bdytail, bytcatt); + temp16alen = scale_expansion_zeroelim(bytcattlen, bytcatt, 2.0 * bdy, + temp16a); + temp16blen = scale_expansion_zeroelim(bytcattlen, bytcatt, bdytail, + temp16b); + temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32b); + temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, + temp64, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + } + if ((cdxtail != 0.0) || (cdytail != 0.0)) { + if ((adxtail != 0.0) || (adytail != 0.0) + || (bdxtail != 0.0) || (bdytail != 0.0)) { + Two_Product(adxtail, bdy, ti1, ti0); + Two_Product(adx, bdytail, tj1, tj0); + Two_Two_Sum(ti1, ti0, tj1, tj0, u3, u[2], u[1], u[0]); + u[3] = u3; + negate = -ady; + Two_Product(bdxtail, negate, ti1, ti0); + negate = -adytail; + Two_Product(bdx, negate, tj1, tj0); + Two_Two_Sum(ti1, ti0, tj1, tj0, v3, v[2], v[1], v[0]); + v[3] = v3; + abtlen = fast_expansion_sum_zeroelim(4, u, 4, v, abt); + + Two_Product(adxtail, bdytail, ti1, ti0); + Two_Product(bdxtail, adytail, tj1, tj0); + Two_Two_Diff(ti1, ti0, tj1, tj0, abtt3, abtt[2], abtt[1], abtt[0]); + abtt[3] = abtt3; + abttlen = 4; + } else { + abt[0] = 0.0; + abtlen = 1; + abtt[0] = 0.0; + abttlen = 1; + } + + if (cdxtail != 0.0) { + temp16alen = scale_expansion_zeroelim(cxtablen, cxtab, cdxtail, temp16a); + cxtabtlen = scale_expansion_zeroelim(abtlen, abt, cdxtail, cxtabt); + temp32alen = scale_expansion_zeroelim(cxtabtlen, cxtabt, 2.0 * cdx, + temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + if (adytail != 0.0) { + temp8len = scale_expansion_zeroelim(4, bb, cdxtail, temp8); + temp16alen = scale_expansion_zeroelim(temp8len, temp8, adytail, + temp16a); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, + temp16a, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (bdytail != 0.0) { + temp8len = scale_expansion_zeroelim(4, aa, -cdxtail, temp8); + temp16alen = scale_expansion_zeroelim(temp8len, temp8, bdytail, + temp16a); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, + temp16a, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + + temp32alen = scale_expansion_zeroelim(cxtabtlen, cxtabt, cdxtail, + temp32a); + cxtabttlen = scale_expansion_zeroelim(abttlen, abtt, cdxtail, cxtabtt); + temp16alen = scale_expansion_zeroelim(cxtabttlen, cxtabtt, 2.0 * cdx, + temp16a); + temp16blen = scale_expansion_zeroelim(cxtabttlen, cxtabtt, cdxtail, + temp16b); + temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32b); + temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, + temp64, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (cdytail != 0.0) { + temp16alen = scale_expansion_zeroelim(cytablen, cytab, cdytail, temp16a); + cytabtlen = scale_expansion_zeroelim(abtlen, abt, cdytail, cytabt); + temp32alen = scale_expansion_zeroelim(cytabtlen, cytabt, 2.0 * cdy, + temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + + + temp32alen = scale_expansion_zeroelim(cytabtlen, cytabt, cdytail, + temp32a); + cytabttlen = scale_expansion_zeroelim(abttlen, abtt, cdytail, cytabtt); + temp16alen = scale_expansion_zeroelim(cytabttlen, cytabtt, 2.0 * cdy, + temp16a); + temp16blen = scale_expansion_zeroelim(cytabttlen, cytabtt, cdytail, + temp16b); + temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32b); + temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, + temp64, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + } + + return finnow[finlength - 1]; +} + +REAL incircle(pa, pb, pc, pd) +REAL *pa; +REAL *pb; +REAL *pc; +REAL *pd; +{ + REAL adx, bdx, cdx, ady, bdy, cdy; + REAL bdxcdy, cdxbdy, cdxady, adxcdy, adxbdy, bdxady; + REAL alift, blift, clift; + REAL det; + REAL permanent, errbound; + + adx = pa[0] - pd[0]; + bdx = pb[0] - pd[0]; + cdx = pc[0] - pd[0]; + ady = pa[1] - pd[1]; + bdy = pb[1] - pd[1]; + cdy = pc[1] - pd[1]; + + bdxcdy = bdx * cdy; + cdxbdy = cdx * bdy; + alift = adx * adx + ady * ady; + + cdxady = cdx * ady; + adxcdy = adx * cdy; + blift = bdx * bdx + bdy * bdy; + + adxbdy = adx * bdy; + bdxady = bdx * ady; + clift = cdx * cdx + cdy * cdy; + + det = alift * (bdxcdy - cdxbdy) + + blift * (cdxady - adxcdy) + + clift * (adxbdy - bdxady); + + permanent = (Absolute(bdxcdy) + Absolute(cdxbdy)) * alift + + (Absolute(cdxady) + Absolute(adxcdy)) * blift + + (Absolute(adxbdy) + Absolute(bdxady)) * clift; + errbound = iccerrboundA * permanent; + if ((det > errbound) || (-det > errbound)) { + return det; + } + + return incircleadapt(pa, pb, pc, pd, permanent); +} + +/*****************************************************************************/ +/* */ +/* inspherefast() Approximate 3D insphere test. Nonrobust. */ +/* insphereexact() Exact 3D insphere test. Robust. */ +/* insphereslow() Another exact 3D insphere test. Robust. */ +/* insphere() Adaptive exact 3D insphere test. Robust. */ +/* */ +/* Return a positive value if the point pe lies inside the */ +/* sphere passing through pa, pb, pc, and pd; a negative value */ +/* if it lies outside; and zero if the five points are */ +/* cospherical. The points pa, pb, pc, and pd must be ordered */ +/* so that they have a positive orientation (as defined by */ +/* orient3d()), or the sign of the result will be reversed. */ +/* */ +/* Only the first and last routine should be used; the middle two are for */ +/* timings. */ +/* */ +/* The last three use exact arithmetic to ensure a correct answer. The */ +/* result returned is the determinant of a matrix. In insphere() only, */ +/* this determinant is computed adaptively, in the sense that exact */ +/* arithmetic is used only to the degree it is needed to ensure that the */ +/* returned value has the correct sign. Hence, insphere() is usually quite */ +/* fast, but will run more slowly when the input points are cospherical or */ +/* nearly so. */ +/* */ +/*****************************************************************************/ + +REAL inspherefast(pa, pb, pc, pd, pe) +REAL *pa; +REAL *pb; +REAL *pc; +REAL *pd; +REAL *pe; +{ + REAL aex, bex, cex, dex; + REAL aey, bey, cey, dey; + REAL aez, bez, cez, dez; + REAL alift, blift, clift, dlift; + REAL ab, bc, cd, da, ac, bd; + REAL abc, bcd, cda, dab; + + aex = pa[0] - pe[0]; + bex = pb[0] - pe[0]; + cex = pc[0] - pe[0]; + dex = pd[0] - pe[0]; + aey = pa[1] - pe[1]; + bey = pb[1] - pe[1]; + cey = pc[1] - pe[1]; + dey = pd[1] - pe[1]; + aez = pa[2] - pe[2]; + bez = pb[2] - pe[2]; + cez = pc[2] - pe[2]; + dez = pd[2] - pe[2]; + + ab = aex * bey - bex * aey; + bc = bex * cey - cex * bey; + cd = cex * dey - dex * cey; + da = dex * aey - aex * dey; + + ac = aex * cey - cex * aey; + bd = bex * dey - dex * bey; + + abc = aez * bc - bez * ac + cez * ab; + bcd = bez * cd - cez * bd + dez * bc; + cda = cez * da + dez * ac + aez * cd; + dab = dez * ab + aez * bd + bez * da; + + alift = aex * aex + aey * aey + aez * aez; + blift = bex * bex + bey * bey + bez * bez; + clift = cex * cex + cey * cey + cez * cez; + dlift = dex * dex + dey * dey + dez * dez; + + return (dlift * abc - clift * dab) + (blift * cda - alift * bcd); +} + +REAL insphereexact(pa, pb, pc, pd, pe) +REAL *pa; +REAL *pb; +REAL *pc; +REAL *pd; +REAL *pe; +{ + INEXACT REAL axby1, bxcy1, cxdy1, dxey1, exay1; + INEXACT REAL bxay1, cxby1, dxcy1, exdy1, axey1; + INEXACT REAL axcy1, bxdy1, cxey1, dxay1, exby1; + INEXACT REAL cxay1, dxby1, excy1, axdy1, bxey1; + REAL axby0, bxcy0, cxdy0, dxey0, exay0; + REAL bxay0, cxby0, dxcy0, exdy0, axey0; + REAL axcy0, bxdy0, cxey0, dxay0, exby0; + REAL cxay0, dxby0, excy0, axdy0, bxey0; + REAL ab[4], bc[4], cd[4], de[4], ea[4]; + REAL ac[4], bd[4], ce[4], da[4], eb[4]; + REAL temp8a[8], temp8b[8], temp16[16]; + int temp8alen, temp8blen, temp16len; + REAL abc[24], bcd[24], cde[24], dea[24], eab[24]; + REAL abd[24], bce[24], cda[24], deb[24], eac[24]; + int abclen, bcdlen, cdelen, dealen, eablen; + int abdlen, bcelen, cdalen, deblen, eaclen; + REAL temp48a[48], temp48b[48]; + int temp48alen, temp48blen; + REAL abcd[96], bcde[96], cdea[96], deab[96], eabc[96]; + int abcdlen, bcdelen, cdealen, deablen, eabclen; + REAL temp192[192]; + REAL det384x[384], det384y[384], det384z[384]; + int xlen, ylen, zlen; + REAL detxy[768]; + int xylen; + REAL adet[1152], bdet[1152], cdet[1152], ddet[1152], edet[1152]; + int alen, blen, clen, dlen, elen; + REAL abdet[2304], cddet[2304], cdedet[3456]; + int ablen, cdlen; + REAL deter[5760]; + int deterlen; + int i; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j; + REAL _0; + + Two_Product(pa[0], pb[1], axby1, axby0); + Two_Product(pb[0], pa[1], bxay1, bxay0); + Two_Two_Diff(axby1, axby0, bxay1, bxay0, ab[3], ab[2], ab[1], ab[0]); + + Two_Product(pb[0], pc[1], bxcy1, bxcy0); + Two_Product(pc[0], pb[1], cxby1, cxby0); + Two_Two_Diff(bxcy1, bxcy0, cxby1, cxby0, bc[3], bc[2], bc[1], bc[0]); + + Two_Product(pc[0], pd[1], cxdy1, cxdy0); + Two_Product(pd[0], pc[1], dxcy1, dxcy0); + Two_Two_Diff(cxdy1, cxdy0, dxcy1, dxcy0, cd[3], cd[2], cd[1], cd[0]); + + Two_Product(pd[0], pe[1], dxey1, dxey0); + Two_Product(pe[0], pd[1], exdy1, exdy0); + Two_Two_Diff(dxey1, dxey0, exdy1, exdy0, de[3], de[2], de[1], de[0]); + + Two_Product(pe[0], pa[1], exay1, exay0); + Two_Product(pa[0], pe[1], axey1, axey0); + Two_Two_Diff(exay1, exay0, axey1, axey0, ea[3], ea[2], ea[1], ea[0]); + + Two_Product(pa[0], pc[1], axcy1, axcy0); + Two_Product(pc[0], pa[1], cxay1, cxay0); + Two_Two_Diff(axcy1, axcy0, cxay1, cxay0, ac[3], ac[2], ac[1], ac[0]); + + Two_Product(pb[0], pd[1], bxdy1, bxdy0); + Two_Product(pd[0], pb[1], dxby1, dxby0); + Two_Two_Diff(bxdy1, bxdy0, dxby1, dxby0, bd[3], bd[2], bd[1], bd[0]); + + Two_Product(pc[0], pe[1], cxey1, cxey0); + Two_Product(pe[0], pc[1], excy1, excy0); + Two_Two_Diff(cxey1, cxey0, excy1, excy0, ce[3], ce[2], ce[1], ce[0]); + + Two_Product(pd[0], pa[1], dxay1, dxay0); + Two_Product(pa[0], pd[1], axdy1, axdy0); + Two_Two_Diff(dxay1, dxay0, axdy1, axdy0, da[3], da[2], da[1], da[0]); + + Two_Product(pe[0], pb[1], exby1, exby0); + Two_Product(pb[0], pe[1], bxey1, bxey0); + Two_Two_Diff(exby1, exby0, bxey1, bxey0, eb[3], eb[2], eb[1], eb[0]); + + temp8alen = scale_expansion_zeroelim(4, bc, pa[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, ac, -pb[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, ab, pc[2], temp8a); + abclen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + abc); + + temp8alen = scale_expansion_zeroelim(4, cd, pb[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, bd, -pc[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, bc, pd[2], temp8a); + bcdlen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + bcd); + + temp8alen = scale_expansion_zeroelim(4, de, pc[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, ce, -pd[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, cd, pe[2], temp8a); + cdelen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + cde); + + temp8alen = scale_expansion_zeroelim(4, ea, pd[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, da, -pe[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, de, pa[2], temp8a); + dealen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + dea); + + temp8alen = scale_expansion_zeroelim(4, ab, pe[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, eb, -pa[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, ea, pb[2], temp8a); + eablen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + eab); + + temp8alen = scale_expansion_zeroelim(4, bd, pa[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, da, pb[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, ab, pd[2], temp8a); + abdlen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + abd); + + temp8alen = scale_expansion_zeroelim(4, ce, pb[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, eb, pc[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, bc, pe[2], temp8a); + bcelen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + bce); + + temp8alen = scale_expansion_zeroelim(4, da, pc[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, ac, pd[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, cd, pa[2], temp8a); + cdalen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + cda); + + temp8alen = scale_expansion_zeroelim(4, eb, pd[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, bd, pe[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, de, pb[2], temp8a); + deblen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + deb); + + temp8alen = scale_expansion_zeroelim(4, ac, pe[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, ce, pa[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, ea, pc[2], temp8a); + eaclen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + eac); + + temp48alen = fast_expansion_sum_zeroelim(cdelen, cde, bcelen, bce, temp48a); + temp48blen = fast_expansion_sum_zeroelim(deblen, deb, bcdlen, bcd, temp48b); + for (i = 0; i < temp48blen; i++) { + temp48b[i] = -temp48b[i]; + } + bcdelen = fast_expansion_sum_zeroelim(temp48alen, temp48a, + temp48blen, temp48b, bcde); + xlen = scale_expansion_zeroelim(bcdelen, bcde, pa[0], temp192); + xlen = scale_expansion_zeroelim(xlen, temp192, pa[0], det384x); + ylen = scale_expansion_zeroelim(bcdelen, bcde, pa[1], temp192); + ylen = scale_expansion_zeroelim(ylen, temp192, pa[1], det384y); + zlen = scale_expansion_zeroelim(bcdelen, bcde, pa[2], temp192); + zlen = scale_expansion_zeroelim(zlen, temp192, pa[2], det384z); + xylen = fast_expansion_sum_zeroelim(xlen, det384x, ylen, det384y, detxy); + alen = fast_expansion_sum_zeroelim(xylen, detxy, zlen, det384z, adet); + + temp48alen = fast_expansion_sum_zeroelim(dealen, dea, cdalen, cda, temp48a); + temp48blen = fast_expansion_sum_zeroelim(eaclen, eac, cdelen, cde, temp48b); + for (i = 0; i < temp48blen; i++) { + temp48b[i] = -temp48b[i]; + } + cdealen = fast_expansion_sum_zeroelim(temp48alen, temp48a, + temp48blen, temp48b, cdea); + xlen = scale_expansion_zeroelim(cdealen, cdea, pb[0], temp192); + xlen = scale_expansion_zeroelim(xlen, temp192, pb[0], det384x); + ylen = scale_expansion_zeroelim(cdealen, cdea, pb[1], temp192); + ylen = scale_expansion_zeroelim(ylen, temp192, pb[1], det384y); + zlen = scale_expansion_zeroelim(cdealen, cdea, pb[2], temp192); + zlen = scale_expansion_zeroelim(zlen, temp192, pb[2], det384z); + xylen = fast_expansion_sum_zeroelim(xlen, det384x, ylen, det384y, detxy); + blen = fast_expansion_sum_zeroelim(xylen, detxy, zlen, det384z, bdet); + + temp48alen = fast_expansion_sum_zeroelim(eablen, eab, deblen, deb, temp48a); + temp48blen = fast_expansion_sum_zeroelim(abdlen, abd, dealen, dea, temp48b); + for (i = 0; i < temp48blen; i++) { + temp48b[i] = -temp48b[i]; + } + deablen = fast_expansion_sum_zeroelim(temp48alen, temp48a, + temp48blen, temp48b, deab); + xlen = scale_expansion_zeroelim(deablen, deab, pc[0], temp192); + xlen = scale_expansion_zeroelim(xlen, temp192, pc[0], det384x); + ylen = scale_expansion_zeroelim(deablen, deab, pc[1], temp192); + ylen = scale_expansion_zeroelim(ylen, temp192, pc[1], det384y); + zlen = scale_expansion_zeroelim(deablen, deab, pc[2], temp192); + zlen = scale_expansion_zeroelim(zlen, temp192, pc[2], det384z); + xylen = fast_expansion_sum_zeroelim(xlen, det384x, ylen, det384y, detxy); + clen = fast_expansion_sum_zeroelim(xylen, detxy, zlen, det384z, cdet); + + temp48alen = fast_expansion_sum_zeroelim(abclen, abc, eaclen, eac, temp48a); + temp48blen = fast_expansion_sum_zeroelim(bcelen, bce, eablen, eab, temp48b); + for (i = 0; i < temp48blen; i++) { + temp48b[i] = -temp48b[i]; + } + eabclen = fast_expansion_sum_zeroelim(temp48alen, temp48a, + temp48blen, temp48b, eabc); + xlen = scale_expansion_zeroelim(eabclen, eabc, pd[0], temp192); + xlen = scale_expansion_zeroelim(xlen, temp192, pd[0], det384x); + ylen = scale_expansion_zeroelim(eabclen, eabc, pd[1], temp192); + ylen = scale_expansion_zeroelim(ylen, temp192, pd[1], det384y); + zlen = scale_expansion_zeroelim(eabclen, eabc, pd[2], temp192); + zlen = scale_expansion_zeroelim(zlen, temp192, pd[2], det384z); + xylen = fast_expansion_sum_zeroelim(xlen, det384x, ylen, det384y, detxy); + dlen = fast_expansion_sum_zeroelim(xylen, detxy, zlen, det384z, ddet); + + temp48alen = fast_expansion_sum_zeroelim(bcdlen, bcd, abdlen, abd, temp48a); + temp48blen = fast_expansion_sum_zeroelim(cdalen, cda, abclen, abc, temp48b); + for (i = 0; i < temp48blen; i++) { + temp48b[i] = -temp48b[i]; + } + abcdlen = fast_expansion_sum_zeroelim(temp48alen, temp48a, + temp48blen, temp48b, abcd); + xlen = scale_expansion_zeroelim(abcdlen, abcd, pe[0], temp192); + xlen = scale_expansion_zeroelim(xlen, temp192, pe[0], det384x); + ylen = scale_expansion_zeroelim(abcdlen, abcd, pe[1], temp192); + ylen = scale_expansion_zeroelim(ylen, temp192, pe[1], det384y); + zlen = scale_expansion_zeroelim(abcdlen, abcd, pe[2], temp192); + zlen = scale_expansion_zeroelim(zlen, temp192, pe[2], det384z); + xylen = fast_expansion_sum_zeroelim(xlen, det384x, ylen, det384y, detxy); + elen = fast_expansion_sum_zeroelim(xylen, detxy, zlen, det384z, edet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + cdlen = fast_expansion_sum_zeroelim(clen, cdet, dlen, ddet, cddet); + cdelen = fast_expansion_sum_zeroelim(cdlen, cddet, elen, edet, cdedet); + deterlen = fast_expansion_sum_zeroelim(ablen, abdet, cdelen, cdedet, deter); + + return deter[deterlen - 1]; +} + +REAL insphereslow(pa, pb, pc, pd, pe) +REAL *pa; +REAL *pb; +REAL *pc; +REAL *pd; +REAL *pe; +{ + INEXACT REAL aex, bex, cex, dex, aey, bey, cey, dey, aez, bez, cez, dez; + REAL aextail, bextail, cextail, dextail; + REAL aeytail, beytail, ceytail, deytail; + REAL aeztail, beztail, ceztail, deztail; + REAL negate, negatetail; + INEXACT REAL axby7, bxcy7, cxdy7, dxay7, axcy7, bxdy7; + INEXACT REAL bxay7, cxby7, dxcy7, axdy7, cxay7, dxby7; + REAL axby[8], bxcy[8], cxdy[8], dxay[8], axcy[8], bxdy[8]; + REAL bxay[8], cxby[8], dxcy[8], axdy[8], cxay[8], dxby[8]; + REAL ab[16], bc[16], cd[16], da[16], ac[16], bd[16]; + int ablen, bclen, cdlen, dalen, aclen, bdlen; + REAL temp32a[32], temp32b[32], temp64a[64], temp64b[64], temp64c[64]; + int temp32alen, temp32blen, temp64alen, temp64blen, temp64clen; + REAL temp128[128], temp192[192]; + int temp128len, temp192len; + REAL detx[384], detxx[768], detxt[384], detxxt[768], detxtxt[768]; + int xlen, xxlen, xtlen, xxtlen, xtxtlen; + REAL x1[1536], x2[2304]; + int x1len, x2len; + REAL dety[384], detyy[768], detyt[384], detyyt[768], detytyt[768]; + int ylen, yylen, ytlen, yytlen, ytytlen; + REAL y1[1536], y2[2304]; + int y1len, y2len; + REAL detz[384], detzz[768], detzt[384], detzzt[768], detztzt[768]; + int zlen, zzlen, ztlen, zztlen, ztztlen; + REAL z1[1536], z2[2304]; + int z1len, z2len; + REAL detxy[4608]; + int xylen; + REAL adet[6912], bdet[6912], cdet[6912], ddet[6912]; + int alen, blen, clen, dlen; + REAL abdet[13824], cddet[13824], deter[27648]; + int deterlen; + int i; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL a0hi, a0lo, a1hi, a1lo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j, _k, _l, _m, _n; + REAL _0, _1, _2; + + Two_Diff(pa[0], pe[0], aex, aextail); + Two_Diff(pa[1], pe[1], aey, aeytail); + Two_Diff(pa[2], pe[2], aez, aeztail); + Two_Diff(pb[0], pe[0], bex, bextail); + Two_Diff(pb[1], pe[1], bey, beytail); + Two_Diff(pb[2], pe[2], bez, beztail); + Two_Diff(pc[0], pe[0], cex, cextail); + Two_Diff(pc[1], pe[1], cey, ceytail); + Two_Diff(pc[2], pe[2], cez, ceztail); + Two_Diff(pd[0], pe[0], dex, dextail); + Two_Diff(pd[1], pe[1], dey, deytail); + Two_Diff(pd[2], pe[2], dez, deztail); + + Two_Two_Product(aex, aextail, bey, beytail, + axby7, axby[6], axby[5], axby[4], + axby[3], axby[2], axby[1], axby[0]); + axby[7] = axby7; + negate = -aey; + negatetail = -aeytail; + Two_Two_Product(bex, bextail, negate, negatetail, + bxay7, bxay[6], bxay[5], bxay[4], + bxay[3], bxay[2], bxay[1], bxay[0]); + bxay[7] = bxay7; + ablen = fast_expansion_sum_zeroelim(8, axby, 8, bxay, ab); + Two_Two_Product(bex, bextail, cey, ceytail, + bxcy7, bxcy[6], bxcy[5], bxcy[4], + bxcy[3], bxcy[2], bxcy[1], bxcy[0]); + bxcy[7] = bxcy7; + negate = -bey; + negatetail = -beytail; + Two_Two_Product(cex, cextail, negate, negatetail, + cxby7, cxby[6], cxby[5], cxby[4], + cxby[3], cxby[2], cxby[1], cxby[0]); + cxby[7] = cxby7; + bclen = fast_expansion_sum_zeroelim(8, bxcy, 8, cxby, bc); + Two_Two_Product(cex, cextail, dey, deytail, + cxdy7, cxdy[6], cxdy[5], cxdy[4], + cxdy[3], cxdy[2], cxdy[1], cxdy[0]); + cxdy[7] = cxdy7; + negate = -cey; + negatetail = -ceytail; + Two_Two_Product(dex, dextail, negate, negatetail, + dxcy7, dxcy[6], dxcy[5], dxcy[4], + dxcy[3], dxcy[2], dxcy[1], dxcy[0]); + dxcy[7] = dxcy7; + cdlen = fast_expansion_sum_zeroelim(8, cxdy, 8, dxcy, cd); + Two_Two_Product(dex, dextail, aey, aeytail, + dxay7, dxay[6], dxay[5], dxay[4], + dxay[3], dxay[2], dxay[1], dxay[0]); + dxay[7] = dxay7; + negate = -dey; + negatetail = -deytail; + Two_Two_Product(aex, aextail, negate, negatetail, + axdy7, axdy[6], axdy[5], axdy[4], + axdy[3], axdy[2], axdy[1], axdy[0]); + axdy[7] = axdy7; + dalen = fast_expansion_sum_zeroelim(8, dxay, 8, axdy, da); + Two_Two_Product(aex, aextail, cey, ceytail, + axcy7, axcy[6], axcy[5], axcy[4], + axcy[3], axcy[2], axcy[1], axcy[0]); + axcy[7] = axcy7; + negate = -aey; + negatetail = -aeytail; + Two_Two_Product(cex, cextail, negate, negatetail, + cxay7, cxay[6], cxay[5], cxay[4], + cxay[3], cxay[2], cxay[1], cxay[0]); + cxay[7] = cxay7; + aclen = fast_expansion_sum_zeroelim(8, axcy, 8, cxay, ac); + Two_Two_Product(bex, bextail, dey, deytail, + bxdy7, bxdy[6], bxdy[5], bxdy[4], + bxdy[3], bxdy[2], bxdy[1], bxdy[0]); + bxdy[7] = bxdy7; + negate = -bey; + negatetail = -beytail; + Two_Two_Product(dex, dextail, negate, negatetail, + dxby7, dxby[6], dxby[5], dxby[4], + dxby[3], dxby[2], dxby[1], dxby[0]); + dxby[7] = dxby7; + bdlen = fast_expansion_sum_zeroelim(8, bxdy, 8, dxby, bd); + + temp32alen = scale_expansion_zeroelim(cdlen, cd, -bez, temp32a); + temp32blen = scale_expansion_zeroelim(cdlen, cd, -beztail, temp32b); + temp64alen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64a); + temp32alen = scale_expansion_zeroelim(bdlen, bd, cez, temp32a); + temp32blen = scale_expansion_zeroelim(bdlen, bd, ceztail, temp32b); + temp64blen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64b); + temp32alen = scale_expansion_zeroelim(bclen, bc, -dez, temp32a); + temp32blen = scale_expansion_zeroelim(bclen, bc, -deztail, temp32b); + temp64clen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64c); + temp128len = fast_expansion_sum_zeroelim(temp64alen, temp64a, + temp64blen, temp64b, temp128); + temp192len = fast_expansion_sum_zeroelim(temp64clen, temp64c, + temp128len, temp128, temp192); + xlen = scale_expansion_zeroelim(temp192len, temp192, aex, detx); + xxlen = scale_expansion_zeroelim(xlen, detx, aex, detxx); + xtlen = scale_expansion_zeroelim(temp192len, temp192, aextail, detxt); + xxtlen = scale_expansion_zeroelim(xtlen, detxt, aex, detxxt); + for (i = 0; i < xxtlen; i++) { + detxxt[i] *= 2.0; + } + xtxtlen = scale_expansion_zeroelim(xtlen, detxt, aextail, detxtxt); + x1len = fast_expansion_sum_zeroelim(xxlen, detxx, xxtlen, detxxt, x1); + x2len = fast_expansion_sum_zeroelim(x1len, x1, xtxtlen, detxtxt, x2); + ylen = scale_expansion_zeroelim(temp192len, temp192, aey, dety); + yylen = scale_expansion_zeroelim(ylen, dety, aey, detyy); + ytlen = scale_expansion_zeroelim(temp192len, temp192, aeytail, detyt); + yytlen = scale_expansion_zeroelim(ytlen, detyt, aey, detyyt); + for (i = 0; i < yytlen; i++) { + detyyt[i] *= 2.0; + } + ytytlen = scale_expansion_zeroelim(ytlen, detyt, aeytail, detytyt); + y1len = fast_expansion_sum_zeroelim(yylen, detyy, yytlen, detyyt, y1); + y2len = fast_expansion_sum_zeroelim(y1len, y1, ytytlen, detytyt, y2); + zlen = scale_expansion_zeroelim(temp192len, temp192, aez, detz); + zzlen = scale_expansion_zeroelim(zlen, detz, aez, detzz); + ztlen = scale_expansion_zeroelim(temp192len, temp192, aeztail, detzt); + zztlen = scale_expansion_zeroelim(ztlen, detzt, aez, detzzt); + for (i = 0; i < zztlen; i++) { + detzzt[i] *= 2.0; + } + ztztlen = scale_expansion_zeroelim(ztlen, detzt, aeztail, detztzt); + z1len = fast_expansion_sum_zeroelim(zzlen, detzz, zztlen, detzzt, z1); + z2len = fast_expansion_sum_zeroelim(z1len, z1, ztztlen, detztzt, z2); + xylen = fast_expansion_sum_zeroelim(x2len, x2, y2len, y2, detxy); + alen = fast_expansion_sum_zeroelim(z2len, z2, xylen, detxy, adet); + + temp32alen = scale_expansion_zeroelim(dalen, da, cez, temp32a); + temp32blen = scale_expansion_zeroelim(dalen, da, ceztail, temp32b); + temp64alen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64a); + temp32alen = scale_expansion_zeroelim(aclen, ac, dez, temp32a); + temp32blen = scale_expansion_zeroelim(aclen, ac, deztail, temp32b); + temp64blen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64b); + temp32alen = scale_expansion_zeroelim(cdlen, cd, aez, temp32a); + temp32blen = scale_expansion_zeroelim(cdlen, cd, aeztail, temp32b); + temp64clen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64c); + temp128len = fast_expansion_sum_zeroelim(temp64alen, temp64a, + temp64blen, temp64b, temp128); + temp192len = fast_expansion_sum_zeroelim(temp64clen, temp64c, + temp128len, temp128, temp192); + xlen = scale_expansion_zeroelim(temp192len, temp192, bex, detx); + xxlen = scale_expansion_zeroelim(xlen, detx, bex, detxx); + xtlen = scale_expansion_zeroelim(temp192len, temp192, bextail, detxt); + xxtlen = scale_expansion_zeroelim(xtlen, detxt, bex, detxxt); + for (i = 0; i < xxtlen; i++) { + detxxt[i] *= 2.0; + } + xtxtlen = scale_expansion_zeroelim(xtlen, detxt, bextail, detxtxt); + x1len = fast_expansion_sum_zeroelim(xxlen, detxx, xxtlen, detxxt, x1); + x2len = fast_expansion_sum_zeroelim(x1len, x1, xtxtlen, detxtxt, x2); + ylen = scale_expansion_zeroelim(temp192len, temp192, bey, dety); + yylen = scale_expansion_zeroelim(ylen, dety, bey, detyy); + ytlen = scale_expansion_zeroelim(temp192len, temp192, beytail, detyt); + yytlen = scale_expansion_zeroelim(ytlen, detyt, bey, detyyt); + for (i = 0; i < yytlen; i++) { + detyyt[i] *= 2.0; + } + ytytlen = scale_expansion_zeroelim(ytlen, detyt, beytail, detytyt); + y1len = fast_expansion_sum_zeroelim(yylen, detyy, yytlen, detyyt, y1); + y2len = fast_expansion_sum_zeroelim(y1len, y1, ytytlen, detytyt, y2); + zlen = scale_expansion_zeroelim(temp192len, temp192, bez, detz); + zzlen = scale_expansion_zeroelim(zlen, detz, bez, detzz); + ztlen = scale_expansion_zeroelim(temp192len, temp192, beztail, detzt); + zztlen = scale_expansion_zeroelim(ztlen, detzt, bez, detzzt); + for (i = 0; i < zztlen; i++) { + detzzt[i] *= 2.0; + } + ztztlen = scale_expansion_zeroelim(ztlen, detzt, beztail, detztzt); + z1len = fast_expansion_sum_zeroelim(zzlen, detzz, zztlen, detzzt, z1); + z2len = fast_expansion_sum_zeroelim(z1len, z1, ztztlen, detztzt, z2); + xylen = fast_expansion_sum_zeroelim(x2len, x2, y2len, y2, detxy); + blen = fast_expansion_sum_zeroelim(z2len, z2, xylen, detxy, bdet); + + temp32alen = scale_expansion_zeroelim(ablen, ab, -dez, temp32a); + temp32blen = scale_expansion_zeroelim(ablen, ab, -deztail, temp32b); + temp64alen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64a); + temp32alen = scale_expansion_zeroelim(bdlen, bd, -aez, temp32a); + temp32blen = scale_expansion_zeroelim(bdlen, bd, -aeztail, temp32b); + temp64blen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64b); + temp32alen = scale_expansion_zeroelim(dalen, da, -bez, temp32a); + temp32blen = scale_expansion_zeroelim(dalen, da, -beztail, temp32b); + temp64clen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64c); + temp128len = fast_expansion_sum_zeroelim(temp64alen, temp64a, + temp64blen, temp64b, temp128); + temp192len = fast_expansion_sum_zeroelim(temp64clen, temp64c, + temp128len, temp128, temp192); + xlen = scale_expansion_zeroelim(temp192len, temp192, cex, detx); + xxlen = scale_expansion_zeroelim(xlen, detx, cex, detxx); + xtlen = scale_expansion_zeroelim(temp192len, temp192, cextail, detxt); + xxtlen = scale_expansion_zeroelim(xtlen, detxt, cex, detxxt); + for (i = 0; i < xxtlen; i++) { + detxxt[i] *= 2.0; + } + xtxtlen = scale_expansion_zeroelim(xtlen, detxt, cextail, detxtxt); + x1len = fast_expansion_sum_zeroelim(xxlen, detxx, xxtlen, detxxt, x1); + x2len = fast_expansion_sum_zeroelim(x1len, x1, xtxtlen, detxtxt, x2); + ylen = scale_expansion_zeroelim(temp192len, temp192, cey, dety); + yylen = scale_expansion_zeroelim(ylen, dety, cey, detyy); + ytlen = scale_expansion_zeroelim(temp192len, temp192, ceytail, detyt); + yytlen = scale_expansion_zeroelim(ytlen, detyt, cey, detyyt); + for (i = 0; i < yytlen; i++) { + detyyt[i] *= 2.0; + } + ytytlen = scale_expansion_zeroelim(ytlen, detyt, ceytail, detytyt); + y1len = fast_expansion_sum_zeroelim(yylen, detyy, yytlen, detyyt, y1); + y2len = fast_expansion_sum_zeroelim(y1len, y1, ytytlen, detytyt, y2); + zlen = scale_expansion_zeroelim(temp192len, temp192, cez, detz); + zzlen = scale_expansion_zeroelim(zlen, detz, cez, detzz); + ztlen = scale_expansion_zeroelim(temp192len, temp192, ceztail, detzt); + zztlen = scale_expansion_zeroelim(ztlen, detzt, cez, detzzt); + for (i = 0; i < zztlen; i++) { + detzzt[i] *= 2.0; + } + ztztlen = scale_expansion_zeroelim(ztlen, detzt, ceztail, detztzt); + z1len = fast_expansion_sum_zeroelim(zzlen, detzz, zztlen, detzzt, z1); + z2len = fast_expansion_sum_zeroelim(z1len, z1, ztztlen, detztzt, z2); + xylen = fast_expansion_sum_zeroelim(x2len, x2, y2len, y2, detxy); + clen = fast_expansion_sum_zeroelim(z2len, z2, xylen, detxy, cdet); + + temp32alen = scale_expansion_zeroelim(bclen, bc, aez, temp32a); + temp32blen = scale_expansion_zeroelim(bclen, bc, aeztail, temp32b); + temp64alen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64a); + temp32alen = scale_expansion_zeroelim(aclen, ac, -bez, temp32a); + temp32blen = scale_expansion_zeroelim(aclen, ac, -beztail, temp32b); + temp64blen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64b); + temp32alen = scale_expansion_zeroelim(ablen, ab, cez, temp32a); + temp32blen = scale_expansion_zeroelim(ablen, ab, ceztail, temp32b); + temp64clen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64c); + temp128len = fast_expansion_sum_zeroelim(temp64alen, temp64a, + temp64blen, temp64b, temp128); + temp192len = fast_expansion_sum_zeroelim(temp64clen, temp64c, + temp128len, temp128, temp192); + xlen = scale_expansion_zeroelim(temp192len, temp192, dex, detx); + xxlen = scale_expansion_zeroelim(xlen, detx, dex, detxx); + xtlen = scale_expansion_zeroelim(temp192len, temp192, dextail, detxt); + xxtlen = scale_expansion_zeroelim(xtlen, detxt, dex, detxxt); + for (i = 0; i < xxtlen; i++) { + detxxt[i] *= 2.0; + } + xtxtlen = scale_expansion_zeroelim(xtlen, detxt, dextail, detxtxt); + x1len = fast_expansion_sum_zeroelim(xxlen, detxx, xxtlen, detxxt, x1); + x2len = fast_expansion_sum_zeroelim(x1len, x1, xtxtlen, detxtxt, x2); + ylen = scale_expansion_zeroelim(temp192len, temp192, dey, dety); + yylen = scale_expansion_zeroelim(ylen, dety, dey, detyy); + ytlen = scale_expansion_zeroelim(temp192len, temp192, deytail, detyt); + yytlen = scale_expansion_zeroelim(ytlen, detyt, dey, detyyt); + for (i = 0; i < yytlen; i++) { + detyyt[i] *= 2.0; + } + ytytlen = scale_expansion_zeroelim(ytlen, detyt, deytail, detytyt); + y1len = fast_expansion_sum_zeroelim(yylen, detyy, yytlen, detyyt, y1); + y2len = fast_expansion_sum_zeroelim(y1len, y1, ytytlen, detytyt, y2); + zlen = scale_expansion_zeroelim(temp192len, temp192, dez, detz); + zzlen = scale_expansion_zeroelim(zlen, detz, dez, detzz); + ztlen = scale_expansion_zeroelim(temp192len, temp192, deztail, detzt); + zztlen = scale_expansion_zeroelim(ztlen, detzt, dez, detzzt); + for (i = 0; i < zztlen; i++) { + detzzt[i] *= 2.0; + } + ztztlen = scale_expansion_zeroelim(ztlen, detzt, deztail, detztzt); + z1len = fast_expansion_sum_zeroelim(zzlen, detzz, zztlen, detzzt, z1); + z2len = fast_expansion_sum_zeroelim(z1len, z1, ztztlen, detztzt, z2); + xylen = fast_expansion_sum_zeroelim(x2len, x2, y2len, y2, detxy); + dlen = fast_expansion_sum_zeroelim(z2len, z2, xylen, detxy, ddet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + cdlen = fast_expansion_sum_zeroelim(clen, cdet, dlen, ddet, cddet); + deterlen = fast_expansion_sum_zeroelim(ablen, abdet, cdlen, cddet, deter); + + return deter[deterlen - 1]; +} + +REAL insphereadapt(pa, pb, pc, pd, pe, permanent) +REAL *pa; +REAL *pb; +REAL *pc; +REAL *pd; +REAL *pe; +REAL permanent; +{ + INEXACT REAL aex, bex, cex, dex, aey, bey, cey, dey, aez, bez, cez, dez; + REAL det, errbound; + + INEXACT REAL aexbey1, bexaey1, bexcey1, cexbey1; + INEXACT REAL cexdey1, dexcey1, dexaey1, aexdey1; + INEXACT REAL aexcey1, cexaey1, bexdey1, dexbey1; + REAL aexbey0, bexaey0, bexcey0, cexbey0; + REAL cexdey0, dexcey0, dexaey0, aexdey0; + REAL aexcey0, cexaey0, bexdey0, dexbey0; + REAL ab[4], bc[4], cd[4], da[4], ac[4], bd[4]; + INEXACT REAL ab3, bc3, cd3, da3, ac3, bd3; + REAL abeps, bceps, cdeps, daeps, aceps, bdeps; + REAL temp8a[8], temp8b[8], temp8c[8], temp16[16], temp24[24], temp48[48]; + int temp8alen, temp8blen, temp8clen, temp16len, temp24len, temp48len; + REAL xdet[96], ydet[96], zdet[96], xydet[192]; + int xlen, ylen, zlen, xylen; + REAL adet[288], bdet[288], cdet[288], ddet[288]; + int alen, blen, clen, dlen; + REAL abdet[576], cddet[576]; + int ablen, cdlen; + REAL fin1[1152]; + int finlength; + + REAL aextail, bextail, cextail, dextail; + REAL aeytail, beytail, ceytail, deytail; + REAL aeztail, beztail, ceztail, deztail; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j; + REAL _0; + + aex = (REAL) (pa[0] - pe[0]); + bex = (REAL) (pb[0] - pe[0]); + cex = (REAL) (pc[0] - pe[0]); + dex = (REAL) (pd[0] - pe[0]); + aey = (REAL) (pa[1] - pe[1]); + bey = (REAL) (pb[1] - pe[1]); + cey = (REAL) (pc[1] - pe[1]); + dey = (REAL) (pd[1] - pe[1]); + aez = (REAL) (pa[2] - pe[2]); + bez = (REAL) (pb[2] - pe[2]); + cez = (REAL) (pc[2] - pe[2]); + dez = (REAL) (pd[2] - pe[2]); + + Two_Product(aex, bey, aexbey1, aexbey0); + Two_Product(bex, aey, bexaey1, bexaey0); + Two_Two_Diff(aexbey1, aexbey0, bexaey1, bexaey0, ab3, ab[2], ab[1], ab[0]); + ab[3] = ab3; + + Two_Product(bex, cey, bexcey1, bexcey0); + Two_Product(cex, bey, cexbey1, cexbey0); + Two_Two_Diff(bexcey1, bexcey0, cexbey1, cexbey0, bc3, bc[2], bc[1], bc[0]); + bc[3] = bc3; + + Two_Product(cex, dey, cexdey1, cexdey0); + Two_Product(dex, cey, dexcey1, dexcey0); + Two_Two_Diff(cexdey1, cexdey0, dexcey1, dexcey0, cd3, cd[2], cd[1], cd[0]); + cd[3] = cd3; + + Two_Product(dex, aey, dexaey1, dexaey0); + Two_Product(aex, dey, aexdey1, aexdey0); + Two_Two_Diff(dexaey1, dexaey0, aexdey1, aexdey0, da3, da[2], da[1], da[0]); + da[3] = da3; + + Two_Product(aex, cey, aexcey1, aexcey0); + Two_Product(cex, aey, cexaey1, cexaey0); + Two_Two_Diff(aexcey1, aexcey0, cexaey1, cexaey0, ac3, ac[2], ac[1], ac[0]); + ac[3] = ac3; + + Two_Product(bex, dey, bexdey1, bexdey0); + Two_Product(dex, bey, dexbey1, dexbey0); + Two_Two_Diff(bexdey1, bexdey0, dexbey1, dexbey0, bd3, bd[2], bd[1], bd[0]); + bd[3] = bd3; + + temp8alen = scale_expansion_zeroelim(4, cd, bez, temp8a); + temp8blen = scale_expansion_zeroelim(4, bd, -cez, temp8b); + temp8clen = scale_expansion_zeroelim(4, bc, dez, temp8c); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, + temp8blen, temp8b, temp16); + temp24len = fast_expansion_sum_zeroelim(temp8clen, temp8c, + temp16len, temp16, temp24); + temp48len = scale_expansion_zeroelim(temp24len, temp24, aex, temp48); + xlen = scale_expansion_zeroelim(temp48len, temp48, -aex, xdet); + temp48len = scale_expansion_zeroelim(temp24len, temp24, aey, temp48); + ylen = scale_expansion_zeroelim(temp48len, temp48, -aey, ydet); + temp48len = scale_expansion_zeroelim(temp24len, temp24, aez, temp48); + zlen = scale_expansion_zeroelim(temp48len, temp48, -aez, zdet); + xylen = fast_expansion_sum_zeroelim(xlen, xdet, ylen, ydet, xydet); + alen = fast_expansion_sum_zeroelim(xylen, xydet, zlen, zdet, adet); + + temp8alen = scale_expansion_zeroelim(4, da, cez, temp8a); + temp8blen = scale_expansion_zeroelim(4, ac, dez, temp8b); + temp8clen = scale_expansion_zeroelim(4, cd, aez, temp8c); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, + temp8blen, temp8b, temp16); + temp24len = fast_expansion_sum_zeroelim(temp8clen, temp8c, + temp16len, temp16, temp24); + temp48len = scale_expansion_zeroelim(temp24len, temp24, bex, temp48); + xlen = scale_expansion_zeroelim(temp48len, temp48, bex, xdet); + temp48len = scale_expansion_zeroelim(temp24len, temp24, bey, temp48); + ylen = scale_expansion_zeroelim(temp48len, temp48, bey, ydet); + temp48len = scale_expansion_zeroelim(temp24len, temp24, bez, temp48); + zlen = scale_expansion_zeroelim(temp48len, temp48, bez, zdet); + xylen = fast_expansion_sum_zeroelim(xlen, xdet, ylen, ydet, xydet); + blen = fast_expansion_sum_zeroelim(xylen, xydet, zlen, zdet, bdet); + + temp8alen = scale_expansion_zeroelim(4, ab, dez, temp8a); + temp8blen = scale_expansion_zeroelim(4, bd, aez, temp8b); + temp8clen = scale_expansion_zeroelim(4, da, bez, temp8c); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, + temp8blen, temp8b, temp16); + temp24len = fast_expansion_sum_zeroelim(temp8clen, temp8c, + temp16len, temp16, temp24); + temp48len = scale_expansion_zeroelim(temp24len, temp24, cex, temp48); + xlen = scale_expansion_zeroelim(temp48len, temp48, -cex, xdet); + temp48len = scale_expansion_zeroelim(temp24len, temp24, cey, temp48); + ylen = scale_expansion_zeroelim(temp48len, temp48, -cey, ydet); + temp48len = scale_expansion_zeroelim(temp24len, temp24, cez, temp48); + zlen = scale_expansion_zeroelim(temp48len, temp48, -cez, zdet); + xylen = fast_expansion_sum_zeroelim(xlen, xdet, ylen, ydet, xydet); + clen = fast_expansion_sum_zeroelim(xylen, xydet, zlen, zdet, cdet); + + temp8alen = scale_expansion_zeroelim(4, bc, aez, temp8a); + temp8blen = scale_expansion_zeroelim(4, ac, -bez, temp8b); + temp8clen = scale_expansion_zeroelim(4, ab, cez, temp8c); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, + temp8blen, temp8b, temp16); + temp24len = fast_expansion_sum_zeroelim(temp8clen, temp8c, + temp16len, temp16, temp24); + temp48len = scale_expansion_zeroelim(temp24len, temp24, dex, temp48); + xlen = scale_expansion_zeroelim(temp48len, temp48, dex, xdet); + temp48len = scale_expansion_zeroelim(temp24len, temp24, dey, temp48); + ylen = scale_expansion_zeroelim(temp48len, temp48, dey, ydet); + temp48len = scale_expansion_zeroelim(temp24len, temp24, dez, temp48); + zlen = scale_expansion_zeroelim(temp48len, temp48, dez, zdet); + xylen = fast_expansion_sum_zeroelim(xlen, xdet, ylen, ydet, xydet); + dlen = fast_expansion_sum_zeroelim(xylen, xydet, zlen, zdet, ddet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + cdlen = fast_expansion_sum_zeroelim(clen, cdet, dlen, ddet, cddet); + finlength = fast_expansion_sum_zeroelim(ablen, abdet, cdlen, cddet, fin1); + + det = estimate(finlength, fin1); + errbound = isperrboundB * permanent; + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + Two_Diff_Tail(pa[0], pe[0], aex, aextail); + Two_Diff_Tail(pa[1], pe[1], aey, aeytail); + Two_Diff_Tail(pa[2], pe[2], aez, aeztail); + Two_Diff_Tail(pb[0], pe[0], bex, bextail); + Two_Diff_Tail(pb[1], pe[1], bey, beytail); + Two_Diff_Tail(pb[2], pe[2], bez, beztail); + Two_Diff_Tail(pc[0], pe[0], cex, cextail); + Two_Diff_Tail(pc[1], pe[1], cey, ceytail); + Two_Diff_Tail(pc[2], pe[2], cez, ceztail); + Two_Diff_Tail(pd[0], pe[0], dex, dextail); + Two_Diff_Tail(pd[1], pe[1], dey, deytail); + Two_Diff_Tail(pd[2], pe[2], dez, deztail); + if ((aextail == 0.0) && (aeytail == 0.0) && (aeztail == 0.0) + && (bextail == 0.0) && (beytail == 0.0) && (beztail == 0.0) + && (cextail == 0.0) && (ceytail == 0.0) && (ceztail == 0.0) + && (dextail == 0.0) && (deytail == 0.0) && (deztail == 0.0)) { + return det; + } + + errbound = isperrboundC * permanent + resulterrbound * Absolute(det); + abeps = (aex * beytail + bey * aextail) + - (aey * bextail + bex * aeytail); + bceps = (bex * ceytail + cey * bextail) + - (bey * cextail + cex * beytail); + cdeps = (cex * deytail + dey * cextail) + - (cey * dextail + dex * ceytail); + daeps = (dex * aeytail + aey * dextail) + - (dey * aextail + aex * deytail); + aceps = (aex * ceytail + cey * aextail) + - (aey * cextail + cex * aeytail); + bdeps = (bex * deytail + dey * bextail) + - (bey * dextail + dex * beytail); + det += (((bex * bex + bey * bey + bez * bez) + * ((cez * daeps + dez * aceps + aez * cdeps) + + (ceztail * da3 + deztail * ac3 + aeztail * cd3)) + + (dex * dex + dey * dey + dez * dez) + * ((aez * bceps - bez * aceps + cez * abeps) + + (aeztail * bc3 - beztail * ac3 + ceztail * ab3))) + - ((aex * aex + aey * aey + aez * aez) + * ((bez * cdeps - cez * bdeps + dez * bceps) + + (beztail * cd3 - ceztail * bd3 + deztail * bc3)) + + (cex * cex + cey * cey + cez * cez) + * ((dez * abeps + aez * bdeps + bez * daeps) + + (deztail * ab3 + aeztail * bd3 + beztail * da3)))) + + 2.0 * (((bex * bextail + bey * beytail + bez * beztail) + * (cez * da3 + dez * ac3 + aez * cd3) + + (dex * dextail + dey * deytail + dez * deztail) + * (aez * bc3 - bez * ac3 + cez * ab3)) + - ((aex * aextail + aey * aeytail + aez * aeztail) + * (bez * cd3 - cez * bd3 + dez * bc3) + + (cex * cextail + cey * ceytail + cez * ceztail) + * (dez * ab3 + aez * bd3 + bez * da3))); + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + return insphereexact(pa, pb, pc, pd, pe); +} + +REAL insphere(pa, pb, pc, pd, pe) +REAL *pa; +REAL *pb; +REAL *pc; +REAL *pd; +REAL *pe; +{ + REAL aex, bex, cex, dex; + REAL aey, bey, cey, dey; + REAL aez, bez, cez, dez; + REAL aexbey, bexaey, bexcey, cexbey, cexdey, dexcey, dexaey, aexdey; + REAL aexcey, cexaey, bexdey, dexbey; + REAL alift, blift, clift, dlift; + REAL ab, bc, cd, da, ac, bd; + REAL abc, bcd, cda, dab; + REAL aezplus, bezplus, cezplus, dezplus; + REAL aexbeyplus, bexaeyplus, bexceyplus, cexbeyplus; + REAL cexdeyplus, dexceyplus, dexaeyplus, aexdeyplus; + REAL aexceyplus, cexaeyplus, bexdeyplus, dexbeyplus; + REAL det; + REAL permanent, errbound; + + aex = pa[0] - pe[0]; + bex = pb[0] - pe[0]; + cex = pc[0] - pe[0]; + dex = pd[0] - pe[0]; + aey = pa[1] - pe[1]; + bey = pb[1] - pe[1]; + cey = pc[1] - pe[1]; + dey = pd[1] - pe[1]; + aez = pa[2] - pe[2]; + bez = pb[2] - pe[2]; + cez = pc[2] - pe[2]; + dez = pd[2] - pe[2]; + + aexbey = aex * bey; + bexaey = bex * aey; + ab = aexbey - bexaey; + bexcey = bex * cey; + cexbey = cex * bey; + bc = bexcey - cexbey; + cexdey = cex * dey; + dexcey = dex * cey; + cd = cexdey - dexcey; + dexaey = dex * aey; + aexdey = aex * dey; + da = dexaey - aexdey; + + aexcey = aex * cey; + cexaey = cex * aey; + ac = aexcey - cexaey; + bexdey = bex * dey; + dexbey = dex * bey; + bd = bexdey - dexbey; + + abc = aez * bc - bez * ac + cez * ab; + bcd = bez * cd - cez * bd + dez * bc; + cda = cez * da + dez * ac + aez * cd; + dab = dez * ab + aez * bd + bez * da; + + alift = aex * aex + aey * aey + aez * aez; + blift = bex * bex + bey * bey + bez * bez; + clift = cex * cex + cey * cey + cez * cez; + dlift = dex * dex + dey * dey + dez * dez; + + det = (dlift * abc - clift * dab) + (blift * cda - alift * bcd); + + aezplus = Absolute(aez); + bezplus = Absolute(bez); + cezplus = Absolute(cez); + dezplus = Absolute(dez); + aexbeyplus = Absolute(aexbey); + bexaeyplus = Absolute(bexaey); + bexceyplus = Absolute(bexcey); + cexbeyplus = Absolute(cexbey); + cexdeyplus = Absolute(cexdey); + dexceyplus = Absolute(dexcey); + dexaeyplus = Absolute(dexaey); + aexdeyplus = Absolute(aexdey); + aexceyplus = Absolute(aexcey); + cexaeyplus = Absolute(cexaey); + bexdeyplus = Absolute(bexdey); + dexbeyplus = Absolute(dexbey); + permanent = ((cexdeyplus + dexceyplus) * bezplus + + (dexbeyplus + bexdeyplus) * cezplus + + (bexceyplus + cexbeyplus) * dezplus) + * alift + + ((dexaeyplus + aexdeyplus) * cezplus + + (aexceyplus + cexaeyplus) * dezplus + + (cexdeyplus + dexceyplus) * aezplus) + * blift + + ((aexbeyplus + bexaeyplus) * dezplus + + (bexdeyplus + dexbeyplus) * aezplus + + (dexaeyplus + aexdeyplus) * bezplus) + * clift + + ((bexceyplus + cexbeyplus) * aezplus + + (cexaeyplus + aexceyplus) * bezplus + + (aexbeyplus + bexaeyplus) * cezplus) + * dlift; + errbound = isperrboundA * permanent; + if ((det > errbound) || (-det > errbound)) { + return det; + } + + return insphereadapt(pa, pb, pc, pd, pe, permanent); +} +#else + +/*****************************************************************************/ +/* */ +/* Routines for Arbitrary Precision Floating-point Arithmetic */ +/* and Fast Robust Geometric Predicates */ +/* (predicates.c) */ +/* */ +/* May 18, 1996 */ +/* */ +/* Placed in the public domain by */ +/* Jonathan Richard Shewchuk */ +/* School of Computer Science */ +/* Carnegie Mellon University */ +/* 5000 Forbes Avenue */ +/* Pittsburgh, Pennsylvania 15213-3891 */ +/* jrs@cs.cmu.edu */ +/* */ +/* This file contains C implementation of algorithms for exact addition */ +/* and multiplication of floating-point numbers, and predicates for */ +/* robustly performing the orientation and incircle tests used in */ +/* computational geometry. The algorithms and underlying theory are */ +/* described in Jonathan Richard Shewchuk. "Adaptive Precision Floating- */ +/* Point Arithmetic and Fast Robust Geometric Predicates." Technical */ +/* Report CMU-CS-96-140, School of Computer Science, Carnegie Mellon */ +/* University, Pittsburgh, Pennsylvania, May 1996. (Submitted to */ +/* Discrete & Computational Geometry.) */ +/* */ +/* This file, the paper listed above, and other information are available */ +/* from the Web page http://www.cs.cmu.edu/~quake/robust.html . */ +/* */ +/*****************************************************************************/ + +/*****************************************************************************/ +/* */ +/* Using this code: */ +/* */ +/* First, read the short or long version of the paper (from the Web page */ +/* above). */ +/* */ +/* Be sure to call exactinit() once, before calling any of the arithmetic */ +/* functions or geometric predicates. Also be sure to turn on the */ +/* optimizer when compiling this file. */ +/* */ +/* */ +/* Several geometric predicates are defined. Their parameters are all */ +/* points. Each point is an array of two or three floating-point */ +/* numbers. The geometric predicates, described in the papers, are */ +/* */ +/* orient2d(pa, pb, pc) */ +/* orient2dfast(pa, pb, pc) */ +/* orient3d(pa, pb, pc, pd) */ +/* orient3dfast(pa, pb, pc, pd) */ +/* incircle(pa, pb, pc, pd) */ +/* incirclefast(pa, pb, pc, pd) */ +/* insphere(pa, pb, pc, pd, pe) */ +/* inspherefast(pa, pb, pc, pd, pe) */ +/* */ +/* Those with suffix "fast" are approximate, non-robust versions. Those */ +/* without the suffix are adaptive precision, robust versions. There */ +/* are also versions with the suffices "exact" and "slow", which are */ +/* non-adaptive, exact arithmetic versions, which I use only for timings */ +/* in my arithmetic papers. */ +/* */ +/* */ +/* An expansion is represented by an array of floating-point numbers, */ +/* sorted from smallest to largest magnitude (possibly with interspersed */ +/* zeros). The length of each expansion is stored as a separate integer, */ +/* and each arithmetic function returns an integer which is the length */ +/* of the expansion it created. */ +/* */ +/* Several arithmetic functions are defined. Their parameters are */ +/* */ +/* e, f Input expansions */ +/* elen, flen Lengths of input expansions (must be >= 1) */ +/* h Output expansion */ +/* b Input scalar */ +/* */ +/* The arithmetic functions are */ +/* */ +/* grow_expansion(elen, e, b, h) */ +/* grow_expansion_zeroelim(elen, e, b, h) */ +/* expansion_sum(elen, e, flen, f, h) */ +/* expansion_sum_zeroelim1(elen, e, flen, f, h) */ +/* expansion_sum_zeroelim2(elen, e, flen, f, h) */ +/* fast_expansion_sum(elen, e, flen, f, h) */ +/* fast_expansion_sum_zeroelim(elen, e, flen, f, h) */ +/* linear_expansion_sum(elen, e, flen, f, h) */ +/* linear_expansion_sum_zeroelim(elen, e, flen, f, h) */ +/* scale_expansion(elen, e, b, h) */ +/* scale_expansion_zeroelim(elen, e, b, h) */ +/* compress(elen, e, h) */ +/* */ +/* All of these are described in the long version of the paper; some are */ +/* described in the short version. All return an integer that is the */ +/* length of h. Those with suffix _zeroelim perform zero elimination, */ +/* and are recommended over their counterparts. The procedure */ +/* fast_expansion_sum_zeroelim() (or linear_expansion_sum_zeroelim() on */ +/* processors that do not use the round-to-even tiebreaking rule) is */ +/* recommended over expansion_sum_zeroelim(). Each procedure has a */ +/* little note next to it (in the code below) that tells you whether or */ +/* not the output expansion may be the same array as one of the input */ +/* expansions. */ +/* */ +/* */ +/* If you look around below, you'll also find macros for a bunch of */ +/* simple unrolled arithmetic operations, and procedures for printing */ +/* expansions (commented out because they don't work with all C */ +/* compilers) and for generating random floating-point numbers whose */ +/* significand bits are all random. Most of the macros have undocumented */ +/* requirements that certain of their parameters should not be the same */ +/* variable; for safety, better to make sure all the parameters are */ +/* distinct variables. Feel free to send email to jrs@cs.cmu.edu if you */ +/* have questions. */ +/* */ +/*****************************************************************************/ + +#ifdef _WIN32 +#pragma warning(push) +#pragma warning(disable: 4131) +#endif // ifdef _WIN32 + +#include +#include +#include +//#include + +/* On some machines, the exact arithmetic routines might be defeated by the */ +/* use of internal extended precision floating-point registers. Sometimes */ +/* this problem can be fixed by defining certain values to be volatile, */ +/* thus forcing them to be stored to memory and rounded off. This isn't */ +/* a great solution, though, as it slows the arithmetic down. */ +/* */ +/* To try this out, write "#define INEXACT volatile" below. Normally, */ +/* however, INEXACT should be defined to be nothing. ("#define INEXACT".) */ + +#define INEXACT /* Nothing */ +/* #define INEXACT volatile */ + +#define REAL double /* float or double */ +#define REALPRINT doubleprint +#define REALRAND doublerand +#define NARROWRAND narrowdoublerand +#define UNIFORMRAND uniformdoublerand + +/* Which of the following two methods of finding the absolute values is */ +/* fastest is compiler-dependent. A few compilers can inline and optimize */ +/* the fabs() call; but most will incur the overhead of a function call, */ +/* which is disastrously slow. A faster way on IEEE machines might be to */ +/* mask the appropriate bit, but that's difficult to do in C. */ + +#define Absolute(a) ((a) >= 0.0 ? (a) : -(a)) +/* #define Absolute(a) fabs(a) */ + +/* Many of the operations are broken up into two pieces, a main part that */ +/* performs an approximate operation, and a "tail" that computes the */ +/* roundoff error of that operation. */ +/* */ +/* The operations Fast_Two_Sum(), Fast_Two_Diff(), Two_Sum(), Two_Diff(), */ +/* Split(), and Two_Product() are all implemented as described in the */ +/* reference. Each of these macros requires certain variables to be */ +/* defined in the calling routine. The variables `bvirt', `c', `abig', */ +/* `_i', `_j', `_k', `_l', `_m', and `_n' are declared `INEXACT' because */ +/* they store the result of an operation that may incur roundoff error. */ +/* The input parameter `x' (or the highest numbered `x_' parameter) must */ +/* also be declared `INEXACT'. */ + +#define Fast_Two_Sum_Tail(a, b, x, y) \ + bvirt = x - a; \ + y = b - bvirt + +#define Fast_Two_Sum(a, b, x, y) \ + x = (REAL)(a + b); \ + Fast_Two_Sum_Tail(a, b, x, y) + +#define Fast_Two_Diff_Tail(a, b, x, y) \ + bvirt = a - x; \ + y = bvirt - b + +#define Fast_Two_Diff(a, b, x, y) \ + x = (REAL)(a - b); \ + Fast_Two_Diff_Tail(a, b, x, y) + +#define Two_Sum_Tail(a, b, x, y) \ + bvirt = (REAL)(x - a); \ + avirt = x - bvirt; \ + bround = b - bvirt; \ + around = a - avirt; \ + y = around + bround + +#define Two_Sum(a, b, x, y) \ + x = (REAL)(a + b); \ + Two_Sum_Tail(a, b, x, y) + +#define Two_Diff_Tail(a, b, x, y) \ + bvirt = (REAL)(a - x); \ + avirt = x + bvirt; \ + bround = bvirt - b; \ + around = a - avirt; \ + y = around + bround + +#define Two_Diff(a, b, x, y) \ + x = (REAL)(a - b); \ + Two_Diff_Tail(a, b, x, y) + +#define Split(a, ahi, alo) \ + c = (REAL)(splitter * a); \ + abig = (REAL)(c - a); \ + ahi = c - abig; \ + alo = a - ahi + +#define Two_Product_Tail(a, b, x, y) \ + Split(a, ahi, alo); \ + Split(b, bhi, blo); \ + err1 = x - (ahi * bhi); \ + err2 = err1 - (alo * bhi); \ + err3 = err2 - (ahi * blo); \ + y = (alo * blo) - err3 + +#define Two_Product(a, b, x, y) \ + x = (REAL)(a * b); \ + Two_Product_Tail(a, b, x, y) + +/* Two_Product_Presplit() is Two_Product() where one of the inputs has */ +/* already been split. Avoids redundant splitting. */ + +#define Two_Product_Presplit(a, b, bhi, blo, x, y) \ + x = (REAL)(a * b); \ + Split(a, ahi, alo); \ + err1 = x - (ahi * bhi); \ + err2 = err1 - (alo * bhi); \ + err3 = err2 - (ahi * blo); \ + y = (alo * blo) - err3 + +/* Two_Product_2Presplit() is Two_Product() where both of the inputs have */ +/* already been split. Avoids redundant splitting. */ + +#define Two_Product_2Presplit(a, ahi, alo, b, bhi, blo, x, y) \ + x = (REAL)(a * b); \ + err1 = x - (ahi * bhi); \ + err2 = err1 - (alo * bhi); \ + err3 = err2 - (ahi * blo); \ + y = (alo * blo) - err3 + +/* Square() can be done more quickly than Two_Product(). */ + +#define Square_Tail(a, x, y) \ + Split(a, ahi, alo); \ + err1 = x - (ahi * ahi); \ + err3 = err1 - ((ahi + ahi) * alo); \ + y = (alo * alo) - err3 + +#define Square(a, x, y) \ + x = (REAL)(a * a); \ + Square_Tail(a, x, y) + +/* Macros for summing expansions of various fixed lengths. These are all */ +/* unrolled versions of Expansion_Sum(). */ + +#define Two_One_Sum(a1, a0, b, x2, x1, x0) \ + Two_Sum(a0, b, _i, x0); \ + Two_Sum(a1, _i, x2, x1) + +#define Two_One_Diff(a1, a0, b, x2, x1, x0) \ + Two_Diff(a0, b, _i, x0); \ + Two_Sum(a1, _i, x2, x1) + +#define Two_Two_Sum(a1, a0, b1, b0, x3, x2, x1, x0) \ + Two_One_Sum(a1, a0, b0, _j, _0, x0); \ + Two_One_Sum(_j, _0, b1, x3, x2, x1) + +#define Two_Two_Diff(a1, a0, b1, b0, x3, x2, x1, x0) \ + Two_One_Diff(a1, a0, b0, _j, _0, x0); \ + Two_One_Diff(_j, _0, b1, x3, x2, x1) + +#define Four_One_Sum(a3, a2, a1, a0, b, x4, x3, x2, x1, x0) \ + Two_One_Sum(a1, a0, b, _j, x1, x0); \ + Two_One_Sum(a3, a2, _j, x4, x3, x2) + +#define Four_Two_Sum(a3, a2, a1, a0, b1, b0, x5, x4, x3, x2, x1, x0) \ + Four_One_Sum(a3, a2, a1, a0, b0, _k, _2, _1, _0, x0); \ + Four_One_Sum(_k, _2, _1, _0, b1, x5, x4, x3, x2, x1) + +#define Four_Four_Sum(a3, a2, a1, a0, b4, b3, b1, b0, x7, x6, x5, x4, x3, x2, \ + x1, x0) \ + Four_Two_Sum(a3, a2, a1, a0, b1, b0, _l, _2, _1, _0, x1, x0); \ + Four_Two_Sum(_l, _2, _1, _0, b4, b3, x7, x6, x5, x4, x3, x2) + +#define Eight_One_Sum(a7, a6, a5, a4, a3, a2, a1, a0, b, x8, x7, x6, x5, x4, \ + x3, x2, x1, x0) \ + Four_One_Sum(a3, a2, a1, a0, b, _j, x3, x2, x1, x0); \ + Four_One_Sum(a7, a6, a5, a4, _j, x8, x7, x6, x5, x4) + +#define Eight_Two_Sum(a7, a6, a5, a4, a3, a2, a1, a0, b1, b0, x9, x8, x7, \ + x6, x5, x4, x3, x2, x1, x0) \ + Eight_One_Sum(a7, a6, a5, a4, a3, a2, a1, a0, b0, _k, _6, _5, _4, _3, _2, \ + _1, _0, x0); \ + Eight_One_Sum(_k, _6, _5, _4, _3, _2, _1, _0, b1, x9, x8, x7, x6, x5, x4, \ + x3, x2, x1) + +#define Eight_Four_Sum(a7, a6, a5, a4, a3, a2, a1, a0, b4, b3, b1, b0, x11, \ + x10, x9, x8, x7, x6, x5, x4, x3, x2, x1, x0) \ + Eight_Two_Sum(a7, a6, a5, a4, a3, a2, a1, a0, b1, b0, _l, _6, _5, _4, _3, \ + _2, _1, _0, x1, x0); \ + Eight_Two_Sum(_l, _6, _5, _4, _3, _2, _1, _0, b4, b3, x11, x10, x9, x8, \ + x7, x6, x5, x4, x3, x2) + +/* Macros for multiplying expansions of various fixed lengths. */ + +#define Two_One_Product(a1, a0, b, x3, x2, x1, x0) \ + Split(b, bhi, blo); \ + Two_Product_Presplit(a0, b, bhi, blo, _i, x0); \ + Two_Product_Presplit(a1, b, bhi, blo, _j, _0); \ + Two_Sum(_i, _0, _k, x1); \ + Fast_Two_Sum(_j, _k, x3, x2) + +#define Four_One_Product(a3, a2, a1, a0, b, x7, x6, x5, x4, x3, x2, x1, x0) \ + Split(b, bhi, blo); \ + Two_Product_Presplit(a0, b, bhi, blo, _i, x0); \ + Two_Product_Presplit(a1, b, bhi, blo, _j, _0); \ + Two_Sum(_i, _0, _k, x1); \ + Fast_Two_Sum(_j, _k, _i, x2); \ + Two_Product_Presplit(a2, b, bhi, blo, _j, _0); \ + Two_Sum(_i, _0, _k, x3); \ + Fast_Two_Sum(_j, _k, _i, x4); \ + Two_Product_Presplit(a3, b, bhi, blo, _j, _0); \ + Two_Sum(_i, _0, _k, x5); \ + Fast_Two_Sum(_j, _k, x7, x6) + +#define Two_Two_Product(a1, a0, b1, b0, x7, x6, x5, x4, x3, x2, x1, x0) \ + Split(a0, a0hi, a0lo); \ + Split(b0, bhi, blo); \ + Two_Product_2Presplit(a0, a0hi, a0lo, b0, bhi, blo, _i, x0); \ + Split(a1, a1hi, a1lo); \ + Two_Product_2Presplit(a1, a1hi, a1lo, b0, bhi, blo, _j, _0); \ + Two_Sum(_i, _0, _k, _1); \ + Fast_Two_Sum(_j, _k, _l, _2); \ + Split(b1, bhi, blo); \ + Two_Product_2Presplit(a0, a0hi, a0lo, b1, bhi, blo, _i, _0); \ + Two_Sum(_1, _0, _k, x1); \ + Two_Sum(_2, _k, _j, _1); \ + Two_Sum(_l, _j, _m, _2); \ + Two_Product_2Presplit(a1, a1hi, a1lo, b1, bhi, blo, _j, _0); \ + Two_Sum(_i, _0, _n, _0); \ + Two_Sum(_1, _0, _i, x2); \ + Two_Sum(_2, _i, _k, _1); \ + Two_Sum(_m, _k, _l, _2); \ + Two_Sum(_j, _n, _k, _0); \ + Two_Sum(_1, _0, _j, x3); \ + Two_Sum(_2, _j, _i, _1); \ + Two_Sum(_l, _i, _m, _2); \ + Two_Sum(_1, _k, _i, x4); \ + Two_Sum(_2, _i, _k, x5); \ + Two_Sum(_m, _k, x7, x6) + +/* An expansion of length two can be squared more quickly than finding the */ +/* product of two different expansions of length two, and the result is */ +/* guaranteed to have no more than six (rather than eight) components. */ + +#define Two_Square(a1, a0, x5, x4, x3, x2, x1, x0) \ + Square(a0, _j, x0); \ + _0 = a0 + a0; \ + Two_Product(a1, _0, _k, _1); \ + Two_One_Sum(_k, _1, _j, _l, _2, x1); \ + Square(a1, _j, _1); \ + Two_Two_Sum(_j, _1, _l, _2, x5, x4, x3, x2) + +REAL splitter; /* = 2^ceiling(p / 2) + 1. Used to split floats in half. */ +REAL epsilon; /* = 2^(-p). Used to estimate roundoff errors. */ +/* A set of coefficients used to calculate maximum roundoff errors. */ +REAL resulterrbound; +REAL ccwerrboundA, ccwerrboundB, ccwerrboundC; +REAL o3derrboundA, o3derrboundB, o3derrboundC; +REAL iccerrboundA, iccerrboundB, iccerrboundC; +REAL isperrboundA, isperrboundB, isperrboundC; + +/*****************************************************************************/ +/* */ +/* doubleprint() Print the bit representation of a double. */ +/* */ +/* Useful for debugging exact arithmetic routines. */ +/* */ +/*****************************************************************************/ + +/* +void doubleprint(number) +double number; +{ + unsigned long long no; + unsigned long long sign, expo; + int exponent; + int i, bottomi; + + no = *(unsigned long long *) &number; + sign = no & 0x8000000000000000ll; + expo = (no >> 52) & 0x7ffll; + exponent = (int) expo; + exponent = exponent - 1023; + if (sign) { + printf("-"); + } else { + printf(" "); + } + if (exponent == -1023) { + printf( + "0.0000000000000000000000000000000000000000000000000000_ ( )"); + } else { + printf("1."); + bottomi = -1; + for (i = 0; i < 52; i++) { + if (no & 0x0008000000000000ll) { + printf("1"); + bottomi = i; + } else { + printf("0"); + } + no <<= 1; + } + printf("_%d (%d)", exponent, exponent - 1 - bottomi); + } +} +*/ + +/*****************************************************************************/ +/* */ +/* floatprint() Print the bit representation of a float. */ +/* */ +/* Useful for debugging exact arithmetic routines. */ +/* */ +/*****************************************************************************/ + +/* +void floatprint(number) +float number; +{ + unsigned no; + unsigned sign, expo; + int exponent; + int i, bottomi; + + no = *(unsigned *) &number; + sign = no & 0x80000000; + expo = (no >> 23) & 0xff; + exponent = (int) expo; + exponent = exponent - 127; + if (sign) { + printf("-"); + } else { + printf(" "); + } + if (exponent == -127) { + printf("0.00000000000000000000000_ ( )"); + } else { + printf("1."); + bottomi = -1; + for (i = 0; i < 23; i++) { + if (no & 0x00400000) { + printf("1"); + bottomi = i; + } else { + printf("0"); + } + no <<= 1; + } + printf("_%3d (%3d)", exponent, exponent - 1 - bottomi); + } +} +*/ + +/*****************************************************************************/ +/* */ +/* expansion_print() Print the bit representation of an expansion. */ +/* */ +/* Useful for debugging exact arithmetic routines. */ +/* */ +/*****************************************************************************/ + +/* +void expansion_print(elen, e) +int elen; +REAL *e; +{ + int i; + + for (i = elen - 1; i >= 0; i--) { + REALPRINT(e[i]); + if (i > 0) { + printf(" +\n"); + } else { + printf("\n"); + } + } +} +*/ + +#if 0 +/*****************************************************************************/ +/* */ +/* doublerand() Generate a double with random 53-bit significand and a */ +/* random exponent in [0, 511]. */ +/* */ +/*****************************************************************************/ + +double doublerand() +{ + double result; + double expo; + long a, b, c; + long i; + + a = random(); + b = random(); + c = random(); + result = (double)(a - 1073741824) * 8388608.0 + (double)(b >> 8); + for (i = 512, expo = 2; i <= 131072; i *= 2, expo = expo * expo) { + if (c & i) { + result *= expo; + } + } + return result; +} + +/*****************************************************************************/ +/* */ +/* narrowdoublerand() Generate a double with random 53-bit significand */ +/* and a random exponent in [0, 7]. */ +/* */ +/*****************************************************************************/ + +double narrowdoublerand() +{ + double result; + double expo; + long a, b, c; + long i; + + a = random(); + b = random(); + c = random(); + result = (double)(a - 1073741824) * 8388608.0 + (double)(b >> 8); + for (i = 512, expo = 2; i <= 2048; i *= 2, expo = expo * expo) { + if (c & i) { + result *= expo; + } + } + return result; +} + +/*****************************************************************************/ +/* */ +/* uniformdoublerand() Generate a double with random 53-bit significand. */ +/* */ +/*****************************************************************************/ + +double uniformdoublerand() +{ + double result; + long a, b; + + a = random(); + b = random(); + result = (double)(a - 1073741824) * 8388608.0 + (double)(b >> 8); + return result; +} + +/*****************************************************************************/ +/* */ +/* floatrand() Generate a float with random 24-bit significand and a */ +/* random exponent in [0, 63]. */ +/* */ +/*****************************************************************************/ + +float floatrand() +{ + float result; + float expo; + long a, c; + long i; + + a = random(); + c = random(); + result = (float)((a - 1073741824) >> 6); + for (i = 512, expo = 2; i <= 16384; i *= 2, expo = expo * expo) { + if (c & i) { + result *= expo; + } + } + return result; +} + +/*****************************************************************************/ +/* */ +/* narrowfloatrand() Generate a float with random 24-bit significand and */ +/* a random exponent in [0, 7]. */ +/* */ +/*****************************************************************************/ + +float narrowfloatrand() +{ + float result; + float expo; + long a, c; + long i; + + a = random(); + c = random(); + result = (float)((a - 1073741824) >> 6); + for (i = 512, expo = 2; i <= 2048; i *= 2, expo = expo * expo) { + if (c & i) { + result *= expo; + } + } + return result; +} + +/*****************************************************************************/ +/* */ +/* uniformfloatrand() Generate a float with random 24-bit significand. */ +/* */ +/*****************************************************************************/ + +float uniformfloatrand() +{ + float result; + long a; + + a = random(); + result = (float)((a - 1073741824) >> 6); + return result; +} +#endif +/*****************************************************************************/ +/* */ +/* exactinit() Initialize the variables used for exact arithmetic. */ +/* */ +/* `epsilon' is the largest power of two such that 1.0 + epsilon = 1.0 in */ +/* floating-point arithmetic. `epsilon' bounds the relative roundoff */ +/* error. It is used for floating-point error analysis. */ +/* */ +/* `splitter' is used to split floating-point numbers into two half- */ +/* length significands for exact multiplication. */ +/* */ +/* I imagine that a highly optimizing compiler might be too smart for its */ +/* own good, and somehow cause this routine to fail, if it pretends that */ +/* floating-point arithmetic is too much like real arithmetic. */ +/* */ +/* Don't change this routine unless you fully understand it. */ +/* */ +/*****************************************************************************/ + +void exactinit() +{ + REAL half; + REAL check, lastcheck; + int every_other; + + every_other = 1; + half = 0.5; + epsilon = 1.0; + splitter = 1.0; + check = 1.0; + /* Repeatedly divide `epsilon' by two until it is too small to add to */ + /* one without causing roundoff. (Also check if the sum is equal to */ + /* the previous sum, for machines that round up instead of using exact */ + /* rounding. Not that this library will work on such machines anyway. */ + do { + lastcheck = check; + epsilon *= half; + if (every_other) { + splitter *= 2.0; + } + every_other = !every_other; + check = 1.0 + epsilon; + } while ((check != 1.0) && (check != lastcheck)); + splitter += 1.0; + + /* Error bounds for orientation and incircle tests. */ + resulterrbound = (3.0 + 8.0 * epsilon) * epsilon; + ccwerrboundA = (3.0 + 16.0 * epsilon) * epsilon; + ccwerrboundB = (2.0 + 12.0 * epsilon) * epsilon; + ccwerrboundC = (9.0 + 64.0 * epsilon) * epsilon * epsilon; + o3derrboundA = (7.0 + 56.0 * epsilon) * epsilon; + o3derrboundB = (3.0 + 28.0 * epsilon) * epsilon; + o3derrboundC = (26.0 + 288.0 * epsilon) * epsilon * epsilon; + iccerrboundA = (10.0 + 96.0 * epsilon) * epsilon; + iccerrboundB = (4.0 + 48.0 * epsilon) * epsilon; + iccerrboundC = (44.0 + 576.0 * epsilon) * epsilon * epsilon; + isperrboundA = (16.0 + 224.0 * epsilon) * epsilon; + isperrboundB = (5.0 + 72.0 * epsilon) * epsilon; + isperrboundC = (71.0 + 1408.0 * epsilon) * epsilon * epsilon; +} + +/*****************************************************************************/ +/* */ +/* grow_expansion() Add a scalar to an expansion. */ +/* */ +/* Sets h = e + b. See the long version of my paper for details. */ +/* */ +/* Maintains the nonoverlapping property. If round-to-even is used (as */ +/* with IEEE 754), maintains the strongly nonoverlapping and nonadjacent */ +/* properties as well. (That is, if e has one of these properties, so */ +/* will h.) */ +/* */ +/*****************************************************************************/ + +int grow_expansion(elen, e, b, h) /* e and h can be the same. */ + int elen; +REAL* e; +REAL b; +REAL* h; +{ + REAL Q; + INEXACT REAL Qnew; + int eindex; + REAL enow; + INEXACT REAL bvirt; + REAL avirt, bround, around; + + Q = b; + for (eindex = 0; eindex < elen; eindex++) { + enow = e[eindex]; + Two_Sum(Q, enow, Qnew, h[eindex]); + Q = Qnew; + } + h[eindex] = Q; + return eindex + 1; +} + +/*****************************************************************************/ +/* */ +/* grow_expansion_zeroelim() Add a scalar to an expansion, eliminating */ +/* zero components from the output expansion. */ +/* */ +/* Sets h = e + b. See the long version of my paper for details. */ +/* */ +/* Maintains the nonoverlapping property. If round-to-even is used (as */ +/* with IEEE 754), maintains the strongly nonoverlapping and nonadjacent */ +/* properties as well. (That is, if e has one of these properties, so */ +/* will h.) */ +/* */ +/*****************************************************************************/ + +int grow_expansion_zeroelim(elen, e, b, h) /* e and h can be the same. */ + int elen; +REAL* e; +REAL b; +REAL* h; +{ + REAL Q, hh; + INEXACT REAL Qnew; + int eindex, hindex; + REAL enow; + INEXACT REAL bvirt; + REAL avirt, bround, around; + + hindex = 0; + Q = b; + for (eindex = 0; eindex < elen; eindex++) { + enow = e[eindex]; + Two_Sum(Q, enow, Qnew, hh); + Q = Qnew; + if (hh != 0.0) { + h[hindex++] = hh; + } + } + if ((Q != 0.0) || (hindex == 0)) { + h[hindex++] = Q; + } + return hindex; +} + +/*****************************************************************************/ +/* */ +/* expansion_sum() Sum two expansions. */ +/* */ +/* Sets h = e + f. See the long version of my paper for details. */ +/* */ +/* Maintains the nonoverlapping property. If round-to-even is used (as */ +/* with IEEE 754), maintains the nonadjacent property as well. (That is, */ +/* if e has one of these properties, so will h.) Does NOT maintain the */ +/* strongly nonoverlapping property. */ +/* */ +/*****************************************************************************/ + +int expansion_sum(elen, e, flen, f, h) + /* e and h can be the same, but f and h cannot. */ + int elen; +REAL* e; +int flen; +REAL* f; +REAL* h; +{ + REAL Q; + INEXACT REAL Qnew; + int findex, hindex, hlast; + REAL hnow; + INEXACT REAL bvirt; + REAL avirt, bround, around; + + Q = f[0]; + for (hindex = 0; hindex < elen; hindex++) { + hnow = e[hindex]; + Two_Sum(Q, hnow, Qnew, h[hindex]); + Q = Qnew; + } + h[hindex] = Q; + hlast = hindex; + for (findex = 1; findex < flen; findex++) { + Q = f[findex]; + for (hindex = findex; hindex <= hlast; hindex++) { + hnow = h[hindex]; + Two_Sum(Q, hnow, Qnew, h[hindex]); + Q = Qnew; + } + h[++hlast] = Q; + } + return hlast + 1; +} + +/*****************************************************************************/ +/* */ +/* expansion_sum_zeroelim1() Sum two expansions, eliminating zero */ +/* components from the output expansion. */ +/* */ +/* Sets h = e + f. See the long version of my paper for details. */ +/* */ +/* Maintains the nonoverlapping property. If round-to-even is used (as */ +/* with IEEE 754), maintains the nonadjacent property as well. (That is, */ +/* if e has one of these properties, so will h.) Does NOT maintain the */ +/* strongly nonoverlapping property. */ +/* */ +/*****************************************************************************/ + +int expansion_sum_zeroelim1(elen, e, flen, f, h) + /* e and h can be the same, but f and h cannot. */ + int elen; +REAL* e; +int flen; +REAL* f; +REAL* h; +{ + REAL Q; + INEXACT REAL Qnew; + int index, findex, hindex, hlast; + REAL hnow; + INEXACT REAL bvirt; + REAL avirt, bround, around; + + Q = f[0]; + for (hindex = 0; hindex < elen; hindex++) { + hnow = e[hindex]; + Two_Sum(Q, hnow, Qnew, h[hindex]); + Q = Qnew; + } + h[hindex] = Q; + hlast = hindex; + for (findex = 1; findex < flen; findex++) { + Q = f[findex]; + for (hindex = findex; hindex <= hlast; hindex++) { + hnow = h[hindex]; + Two_Sum(Q, hnow, Qnew, h[hindex]); + Q = Qnew; + } + h[++hlast] = Q; + } + hindex = -1; + for (index = 0; index <= hlast; index++) { + hnow = h[index]; + if (hnow != 0.0) { + h[++hindex] = hnow; + } + } + if (hindex == -1) { + return 1; + } else { + return hindex + 1; + } +} + +/*****************************************************************************/ +/* */ +/* expansion_sum_zeroelim2() Sum two expansions, eliminating zero */ +/* components from the output expansion. */ +/* */ +/* Sets h = e + f. See the long version of my paper for details. */ +/* */ +/* Maintains the nonoverlapping property. If round-to-even is used (as */ +/* with IEEE 754), maintains the nonadjacent property as well. (That is, */ +/* if e has one of these properties, so will h.) Does NOT maintain the */ +/* strongly nonoverlapping property. */ +/* */ +/*****************************************************************************/ + +int expansion_sum_zeroelim2(elen, e, flen, f, h) + /* e and h can be the same, but f and h cannot. */ + int elen; +REAL* e; +int flen; +REAL* f; +REAL* h; +{ + REAL Q, hh; + INEXACT REAL Qnew; + int eindex, findex, hindex, hlast; + REAL enow; + INEXACT REAL bvirt; + REAL avirt, bround, around; + + hindex = 0; + Q = f[0]; + for (eindex = 0; eindex < elen; eindex++) { + enow = e[eindex]; + Two_Sum(Q, enow, Qnew, hh); + Q = Qnew; + if (hh != 0.0) { + h[hindex++] = hh; + } + } + h[hindex] = Q; + hlast = hindex; + for (findex = 1; findex < flen; findex++) { + hindex = 0; + Q = f[findex]; + for (eindex = 0; eindex <= hlast; eindex++) { + enow = h[eindex]; + Two_Sum(Q, enow, Qnew, hh); + Q = Qnew; + if (hh != 0) { + h[hindex++] = hh; + } + } + h[hindex] = Q; + hlast = hindex; + } + return hlast + 1; +} + +/*****************************************************************************/ +/* */ +/* fast_expansion_sum() Sum two expansions. */ +/* */ +/* Sets h = e + f. See the long version of my paper for details. */ +/* */ +/* If round-to-even is used (as with IEEE 754), maintains the strongly */ +/* nonoverlapping property. (That is, if e is strongly nonoverlapping, h */ +/* will be also.) Does NOT maintain the nonoverlapping or nonadjacent */ +/* properties. */ +/* */ +/*****************************************************************************/ + +int fast_expansion_sum(elen, e, flen, f, h) /* h cannot be e or f. */ + int elen; +REAL* e; +int flen; +REAL* f; +REAL* h; +{ + REAL Q; + INEXACT REAL Qnew; + INEXACT REAL bvirt; + REAL avirt, bround, around; + int eindex, findex, hindex; + REAL enow, fnow; + + enow = e[0]; + fnow = f[0]; + eindex = findex = 0; + if ((fnow > enow) == (fnow > -enow)) { + Q = enow; + enow = e[++eindex]; + } else { + Q = fnow; + fnow = f[++findex]; + } + hindex = 0; + if ((eindex < elen) && (findex < flen)) { + if ((fnow > enow) == (fnow > -enow)) { + Fast_Two_Sum(enow, Q, Qnew, h[0]); + enow = e[++eindex]; + } else { + Fast_Two_Sum(fnow, Q, Qnew, h[0]); + fnow = f[++findex]; + } + Q = Qnew; + hindex = 1; + while ((eindex < elen) && (findex < flen)) { + if ((fnow > enow) == (fnow > -enow)) { + Two_Sum(Q, enow, Qnew, h[hindex]); + enow = e[++eindex]; + } else { + Two_Sum(Q, fnow, Qnew, h[hindex]); + fnow = f[++findex]; + } + Q = Qnew; + hindex++; + } + } + while (eindex < elen) { + Two_Sum(Q, enow, Qnew, h[hindex]); + enow = e[++eindex]; + Q = Qnew; + hindex++; + } + while (findex < flen) { + Two_Sum(Q, fnow, Qnew, h[hindex]); + fnow = f[++findex]; + Q = Qnew; + hindex++; + } + h[hindex] = Q; + return hindex + 1; +} + +/*****************************************************************************/ +/* */ +/* fast_expansion_sum_zeroelim() Sum two expansions, eliminating zero */ +/* components from the output expansion. */ +/* */ +/* Sets h = e + f. See the long version of my paper for details. */ +/* */ +/* If round-to-even is used (as with IEEE 754), maintains the strongly */ +/* nonoverlapping property. (That is, if e is strongly nonoverlapping, h */ +/* will be also.) Does NOT maintain the nonoverlapping or nonadjacent */ +/* properties. */ +/* */ +/*****************************************************************************/ + +int fast_expansion_sum_zeroelim(elen, e, flen, f, h) /* h cannot be e or f. */ + int elen; +REAL* e; +int flen; +REAL* f; +REAL* h; +{ + REAL Q; + INEXACT REAL Qnew; + INEXACT REAL hh; + INEXACT REAL bvirt; + REAL avirt, bround, around; + int eindex, findex, hindex; + REAL enow, fnow; + + enow = e[0]; + fnow = f[0]; + eindex = findex = 0; + if ((fnow > enow) == (fnow > -enow)) { + Q = enow; + enow = e[++eindex]; + } else { + Q = fnow; + fnow = f[++findex]; + } + hindex = 0; + if ((eindex < elen) && (findex < flen)) { + if ((fnow > enow) == (fnow > -enow)) { + Fast_Two_Sum(enow, Q, Qnew, hh); + enow = e[++eindex]; + } else { + Fast_Two_Sum(fnow, Q, Qnew, hh); + fnow = f[++findex]; + } + Q = Qnew; + if (hh != 0.0) { + h[hindex++] = hh; + } + while ((eindex < elen) && (findex < flen)) { + if ((fnow > enow) == (fnow > -enow)) { + Two_Sum(Q, enow, Qnew, hh); + enow = e[++eindex]; + } else { + Two_Sum(Q, fnow, Qnew, hh); + fnow = f[++findex]; + } + Q = Qnew; + if (hh != 0.0) { + h[hindex++] = hh; + } + } + } + while (eindex < elen) { + Two_Sum(Q, enow, Qnew, hh); + enow = e[++eindex]; + Q = Qnew; + if (hh != 0.0) { + h[hindex++] = hh; + } + } + while (findex < flen) { + Two_Sum(Q, fnow, Qnew, hh); + fnow = f[++findex]; + Q = Qnew; + if (hh != 0.0) { + h[hindex++] = hh; + } + } + if ((Q != 0.0) || (hindex == 0)) { + h[hindex++] = Q; + } + return hindex; +} + +/*****************************************************************************/ +/* */ +/* linear_expansion_sum() Sum two expansions. */ +/* */ +/* Sets h = e + f. See either version of my paper for details. */ +/* */ +/* Maintains the nonoverlapping property. (That is, if e is */ +/* nonoverlapping, h will be also.) */ +/* */ +/*****************************************************************************/ + +int linear_expansion_sum(elen, e, flen, f, h) /* h cannot be e or f. */ + int elen; +REAL* e; +int flen; +REAL* f; +REAL* h; +{ + REAL Q, q; + INEXACT REAL Qnew; + INEXACT REAL R; + INEXACT REAL bvirt; + REAL avirt, bround, around; + int eindex, findex, hindex; + REAL enow, fnow; + REAL g0; + + enow = e[0]; + fnow = f[0]; + eindex = findex = 0; + if ((fnow > enow) == (fnow > -enow)) { + g0 = enow; + enow = e[++eindex]; + } else { + g0 = fnow; + fnow = f[++findex]; + } + if ((eindex < elen) && ((findex >= flen) || ((fnow > enow) == (fnow > -enow)))) { + Fast_Two_Sum(enow, g0, Qnew, q); + enow = e[++eindex]; + } else { + Fast_Two_Sum(fnow, g0, Qnew, q); + fnow = f[++findex]; + } + Q = Qnew; + for (hindex = 0; hindex < elen + flen - 2; hindex++) { + if ((eindex < elen) && ((findex >= flen) || ((fnow > enow) == (fnow > -enow)))) { + Fast_Two_Sum(enow, q, R, h[hindex]); + enow = e[++eindex]; + } else { + Fast_Two_Sum(fnow, q, R, h[hindex]); + fnow = f[++findex]; + } + Two_Sum(Q, R, Qnew, q); + Q = Qnew; + } + h[hindex] = q; + h[hindex + 1] = Q; + return hindex + 2; +} + +/*****************************************************************************/ +/* */ +/* linear_expansion_sum_zeroelim() Sum two expansions, eliminating zero */ +/* components from the output expansion. */ +/* */ +/* Sets h = e + f. See either version of my paper for details. */ +/* */ +/* Maintains the nonoverlapping property. (That is, if e is */ +/* nonoverlapping, h will be also.) */ +/* */ +/*****************************************************************************/ + +int linear_expansion_sum_zeroelim(elen, e, flen, f, h) /* h cannot be e or f. */ + int elen; +REAL* e; +int flen; +REAL* f; +REAL* h; +{ + REAL Q, q, hh; + INEXACT REAL Qnew; + INEXACT REAL R; + INEXACT REAL bvirt; + REAL avirt, bround, around; + int eindex, findex, hindex; + int count; + REAL enow, fnow; + REAL g0; + + enow = e[0]; + fnow = f[0]; + eindex = findex = 0; + hindex = 0; + if ((fnow > enow) == (fnow > -enow)) { + g0 = enow; + enow = e[++eindex]; + } else { + g0 = fnow; + fnow = f[++findex]; + } + if ((eindex < elen) && ((findex >= flen) || ((fnow > enow) == (fnow > -enow)))) { + Fast_Two_Sum(enow, g0, Qnew, q); + enow = e[++eindex]; + } else { + Fast_Two_Sum(fnow, g0, Qnew, q); + fnow = f[++findex]; + } + Q = Qnew; + for (count = 2; count < elen + flen; count++) { + if ((eindex < elen) && ((findex >= flen) || ((fnow > enow) == (fnow > -enow)))) { + Fast_Two_Sum(enow, q, R, hh); + enow = e[++eindex]; + } else { + Fast_Two_Sum(fnow, q, R, hh); + fnow = f[++findex]; + } + Two_Sum(Q, R, Qnew, q); + Q = Qnew; + if (hh != 0) { + h[hindex++] = hh; + } + } + if (q != 0) { + h[hindex++] = q; + } + if ((Q != 0.0) || (hindex == 0)) { + h[hindex++] = Q; + } + return hindex; +} + +/*****************************************************************************/ +/* */ +/* scale_expansion() Multiply an expansion by a scalar. */ +/* */ +/* Sets h = be. See either version of my paper for details. */ +/* */ +/* Maintains the nonoverlapping property. If round-to-even is used (as */ +/* with IEEE 754), maintains the strongly nonoverlapping and nonadjacent */ +/* properties as well. (That is, if e has one of these properties, so */ +/* will h.) */ +/* */ +/*****************************************************************************/ + +int scale_expansion(elen, e, b, h) /* e and h cannot be the same. */ + int elen; +REAL* e; +REAL b; +REAL* h; +{ + INEXACT REAL Q; + INEXACT REAL sum; + INEXACT REAL product1; + REAL product0; + int eindex, hindex; + REAL enow; + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + + Split(b, bhi, blo); + Two_Product_Presplit(e[0], b, bhi, blo, Q, h[0]); + hindex = 1; + for (eindex = 1; eindex < elen; eindex++) { + enow = e[eindex]; + Two_Product_Presplit(enow, b, bhi, blo, product1, product0); + Two_Sum(Q, product0, sum, h[hindex]); + hindex++; + Two_Sum(product1, sum, Q, h[hindex]); + hindex++; + } + h[hindex] = Q; + return elen + elen; +} + +/*****************************************************************************/ +/* */ +/* scale_expansion_zeroelim() Multiply an expansion by a scalar, */ +/* eliminating zero components from the */ +/* output expansion. */ +/* */ +/* Sets h = be. See either version of my paper for details. */ +/* */ +/* Maintains the nonoverlapping property. If round-to-even is used (as */ +/* with IEEE 754), maintains the strongly nonoverlapping and nonadjacent */ +/* properties as well. (That is, if e has one of these properties, so */ +/* will h.) */ +/* */ +/*****************************************************************************/ + +int scale_expansion_zeroelim(elen, e, b, h) /* e and h cannot be the same. */ + int elen; +REAL* e; +REAL b; +REAL* h; +{ + INEXACT REAL Q, sum; + REAL hh; + INEXACT REAL product1; + REAL product0; + int eindex, hindex; + REAL enow; + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + + Split(b, bhi, blo); + Two_Product_Presplit(e[0], b, bhi, blo, Q, hh); + hindex = 0; + if (hh != 0) { + h[hindex++] = hh; + } + for (eindex = 1; eindex < elen; eindex++) { + enow = e[eindex]; + Two_Product_Presplit(enow, b, bhi, blo, product1, product0); + Two_Sum(Q, product0, sum, hh); + if (hh != 0) { + h[hindex++] = hh; + } + Fast_Two_Sum(product1, sum, Q, hh); + if (hh != 0) { + h[hindex++] = hh; + } + } + if ((Q != 0.0) || (hindex == 0)) { + h[hindex++] = Q; + } + return hindex; +} + +/*****************************************************************************/ +/* */ +/* compress() Compress an expansion. */ +/* */ +/* See the long version of my paper for details. */ +/* */ +/* Maintains the nonoverlapping property. If round-to-even is used (as */ +/* with IEEE 754), then any nonoverlapping expansion is converted to a */ +/* nonadjacent expansion. */ +/* */ +/*****************************************************************************/ + +//int compress(elen, e, h) /* e and h may be the same. */ +// int elen; +//REAL* e; +//REAL* h; +//{ +// REAL Q, q; +// INEXACT REAL Qnew; +// int eindex, hindex; +// INEXACT REAL bvirt; +// REAL enow, hnow; +// int top, bottom; +// +// bottom = elen - 1; +// Q = e[bottom]; +// for (eindex = elen - 2; eindex >= 0; eindex--) { +// enow = e[eindex]; +// Fast_Two_Sum(Q, enow, Qnew, q); +// if (q != 0) { +// h[bottom--] = Qnew; +// Q = q; +// } else { +// Q = Qnew; +// } +// } +// top = 0; +// for (hindex = bottom + 1; hindex < elen; hindex++) { +// hnow = h[hindex]; +// Fast_Two_Sum(hnow, Q, Qnew, q); +// if (q != 0) { +// h[top++] = q; +// } +// Q = Qnew; +// } +// h[top] = Q; +// return top + 1; +//} + +/*****************************************************************************/ +/* */ +/* estimate() Produce a one-word estimate of an expansion's value. */ +/* */ +/* See either version of my paper for details. */ +/* */ +/*****************************************************************************/ + +REAL estimate(elen, e) int elen; +REAL* e; +{ + REAL Q; + int eindex; + + Q = e[0]; + for (eindex = 1; eindex < elen; eindex++) { + Q += e[eindex]; + } + return Q; +} + +/*****************************************************************************/ +/* */ +/* orient2dfast() Approximate 2D orientation test. Nonrobust. */ +/* orient2dexact() Exact 2D orientation test. Robust. */ +/* orient2dslow() Another exact 2D orientation test. Robust. */ +/* orient2d() Adaptive exact 2D orientation test. Robust. */ +/* */ +/* Return a positive value if the points pa, pb, and pc occur */ +/* in counterclockwise order; a negative value if they occur */ +/* in clockwise order; and zero if they are collinear. The */ +/* result is also a rough approximation of twice the signed */ +/* area of the triangle defined by the three points. */ +/* */ +/* Only the first and last routine should be used; the middle two are for */ +/* timings. */ +/* */ +/* The last three use exact arithmetic to ensure a correct answer. The */ +/* result returned is the determinant of a matrix. In orient2d() only, */ +/* this determinant is computed adaptively, in the sense that exact */ +/* arithmetic is used only to the degree it is needed to ensure that the */ +/* returned value has the correct sign. Hence, orient2d() is usually quite */ +/* fast, but will run more slowly when the input points are collinear or */ +/* nearly so. */ +/* */ +/*****************************************************************************/ + +REAL orient2dfast(pa, pb, pc) + REAL* pa; +REAL* pb; +REAL* pc; +{ + REAL acx, bcx, acy, bcy; + + acx = pa[0] - pc[0]; + bcx = pb[0] - pc[0]; + acy = pa[1] - pc[1]; + bcy = pb[1] - pc[1]; + return acx * bcy - acy * bcx; +} + +REAL orient2dexact(pa, pb, pc) + REAL* pa; +REAL* pb; +REAL* pc; +{ + INEXACT REAL axby1, axcy1, bxcy1, bxay1, cxay1, cxby1; + REAL axby0, axcy0, bxcy0, bxay0, cxay0, cxby0; + REAL aterms[4], bterms[4], cterms[4]; + INEXACT REAL aterms3, bterms3, cterms3; + REAL v[8], w[12]; + int vlength, wlength; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j; + REAL _0; + + Two_Product(pa[0], pb[1], axby1, axby0); + Two_Product(pa[0], pc[1], axcy1, axcy0); + Two_Two_Diff(axby1, axby0, axcy1, axcy0, + aterms3, aterms[2], aterms[1], aterms[0]); + aterms[3] = aterms3; + + Two_Product(pb[0], pc[1], bxcy1, bxcy0); + Two_Product(pb[0], pa[1], bxay1, bxay0); + Two_Two_Diff(bxcy1, bxcy0, bxay1, bxay0, + bterms3, bterms[2], bterms[1], bterms[0]); + bterms[3] = bterms3; + + Two_Product(pc[0], pa[1], cxay1, cxay0); + Two_Product(pc[0], pb[1], cxby1, cxby0); + Two_Two_Diff(cxay1, cxay0, cxby1, cxby0, + cterms3, cterms[2], cterms[1], cterms[0]); + cterms[3] = cterms3; + + vlength = fast_expansion_sum_zeroelim(4, aterms, 4, bterms, v); + wlength = fast_expansion_sum_zeroelim(vlength, v, 4, cterms, w); + + return w[wlength - 1]; +} + +REAL orient2dslow(pa, pb, pc) + REAL* pa; +REAL* pb; +REAL* pc; +{ + INEXACT REAL acx, acy, bcx, bcy; + REAL acxtail, acytail; + REAL bcxtail, bcytail; + REAL negate, negatetail; + REAL axby[8], bxay[8]; + INEXACT REAL axby7, bxay7; + REAL deter[16]; + int deterlen; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL a0hi, a0lo, a1hi, a1lo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j, _k, _l, _m, _n; + REAL _0, _1, _2; + + Two_Diff(pa[0], pc[0], acx, acxtail); + Two_Diff(pa[1], pc[1], acy, acytail); + Two_Diff(pb[0], pc[0], bcx, bcxtail); + Two_Diff(pb[1], pc[1], bcy, bcytail); + + Two_Two_Product(acx, acxtail, bcy, bcytail, + axby7, axby[6], axby[5], axby[4], + axby[3], axby[2], axby[1], axby[0]); + axby[7] = axby7; + negate = -acy; + negatetail = -acytail; + Two_Two_Product(bcx, bcxtail, negate, negatetail, + bxay7, bxay[6], bxay[5], bxay[4], + bxay[3], bxay[2], bxay[1], bxay[0]); + bxay[7] = bxay7; + + deterlen = fast_expansion_sum_zeroelim(8, axby, 8, bxay, deter); + + return deter[deterlen - 1]; +} + +REAL orient2dadapt(pa, pb, pc, detsum) + REAL* pa; +REAL* pb; +REAL* pc; +REAL detsum; +{ + INEXACT REAL acx, acy, bcx, bcy; + REAL acxtail, acytail, bcxtail, bcytail; + INEXACT REAL detleft, detright; + REAL detlefttail, detrighttail; + REAL det, errbound; + REAL B[4], C1[8], C2[12], D[16]; + INEXACT REAL B3; + int C1length, C2length, Dlength; + REAL u[4]; + INEXACT REAL u3; + INEXACT REAL s1, t1; + REAL s0, t0; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j; + REAL _0; + + acx = (REAL)(pa[0] - pc[0]); + bcx = (REAL)(pb[0] - pc[0]); + acy = (REAL)(pa[1] - pc[1]); + bcy = (REAL)(pb[1] - pc[1]); + + Two_Product(acx, bcy, detleft, detlefttail); + Two_Product(acy, bcx, detright, detrighttail); + + Two_Two_Diff(detleft, detlefttail, detright, detrighttail, + B3, B[2], B[1], B[0]); + B[3] = B3; + + det = estimate(4, B); + errbound = ccwerrboundB * detsum; + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + Two_Diff_Tail(pa[0], pc[0], acx, acxtail); + Two_Diff_Tail(pb[0], pc[0], bcx, bcxtail); + Two_Diff_Tail(pa[1], pc[1], acy, acytail); + Two_Diff_Tail(pb[1], pc[1], bcy, bcytail); + + if ((acxtail == 0.0) && (acytail == 0.0) + && (bcxtail == 0.0) && (bcytail == 0.0)) { + return det; + } + + errbound = ccwerrboundC * detsum + resulterrbound * Absolute(det); + det += (acx * bcytail + bcy * acxtail) + - (acy * bcxtail + bcx * acytail); + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + Two_Product(acxtail, bcy, s1, s0); + Two_Product(acytail, bcx, t1, t0); + Two_Two_Diff(s1, s0, t1, t0, u3, u[2], u[1], u[0]); + u[3] = u3; + C1length = fast_expansion_sum_zeroelim(4, B, 4, u, C1); + + Two_Product(acx, bcytail, s1, s0); + Two_Product(acy, bcxtail, t1, t0); + Two_Two_Diff(s1, s0, t1, t0, u3, u[2], u[1], u[0]); + u[3] = u3; + C2length = fast_expansion_sum_zeroelim(C1length, C1, 4, u, C2); + + Two_Product(acxtail, bcytail, s1, s0); + Two_Product(acytail, bcxtail, t1, t0); + Two_Two_Diff(s1, s0, t1, t0, u3, u[2], u[1], u[0]); + u[3] = u3; + Dlength = fast_expansion_sum_zeroelim(C2length, C2, 4, u, D); + + return (D[Dlength - 1]); +} + +REAL orient2d(pa, pb, pc) + REAL* pa; +REAL* pb; +REAL* pc; +{ + REAL detleft, detright, det; + REAL detsum, errbound; + + detleft = (pa[0] - pc[0]) * (pb[1] - pc[1]); + detright = (pa[1] - pc[1]) * (pb[0] - pc[0]); + det = detleft - detright; + + if (detleft > 0.0) { + if (detright <= 0.0) { + return det; + } else { + detsum = detleft + detright; + } + } else if (detleft < 0.0) { + if (detright >= 0.0) { + return det; + } else { + detsum = -detleft - detright; + } + } else { + return det; + } + + errbound = ccwerrboundA * detsum; + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + return orient2dadapt(pa, pb, pc, detsum); +} + +/*****************************************************************************/ +/* */ +/* orient3dfast() Approximate 3D orientation test. Nonrobust. */ +/* orient3dexact() Exact 3D orientation test. Robust. */ +/* orient3dslow() Another exact 3D orientation test. Robust. */ +/* orient3d() Adaptive exact 3D orientation test. Robust. */ +/* */ +/* Return a positive value if the point pd lies below the */ +/* plane passing through pa, pb, and pc; "below" is defined so */ +/* that pa, pb, and pc appear in counterclockwise order when */ +/* viewed from above the plane. Returns a negative value if */ +/* pd lies above the plane. Returns zero if the points are */ +/* coplanar. The result is also a rough approximation of six */ +/* times the signed volume of the tetrahedron defined by the */ +/* four points. */ +/* */ +/* Only the first and last routine should be used; the middle two are for */ +/* timings. */ +/* */ +/* The last three use exact arithmetic to ensure a correct answer. The */ +/* result returned is the determinant of a matrix. In orient3d() only, */ +/* this determinant is computed adaptively, in the sense that exact */ +/* arithmetic is used only to the degree it is needed to ensure that the */ +/* returned value has the correct sign. Hence, orient3d() is usually quite */ +/* fast, but will run more slowly when the input points are coplanar or */ +/* nearly so. */ +/* */ +/*****************************************************************************/ + +REAL orient3dfast(pa, pb, pc, pd) + REAL* pa; +REAL* pb; +REAL* pc; +REAL* pd; +{ + REAL adx, bdx, cdx; + REAL ady, bdy, cdy; + REAL adz, bdz, cdz; + + adx = pa[0] - pd[0]; + bdx = pb[0] - pd[0]; + cdx = pc[0] - pd[0]; + ady = pa[1] - pd[1]; + bdy = pb[1] - pd[1]; + cdy = pc[1] - pd[1]; + adz = pa[2] - pd[2]; + bdz = pb[2] - pd[2]; + cdz = pc[2] - pd[2]; + + return adx * (bdy * cdz - bdz * cdy) + + bdx * (cdy * adz - cdz * ady) + + cdx * (ady * bdz - adz * bdy); +} + +REAL orient3dexact(pa, pb, pc, pd) + REAL* pa; +REAL* pb; +REAL* pc; +REAL* pd; +{ + INEXACT REAL axby1, bxcy1, cxdy1, dxay1, axcy1, bxdy1; + INEXACT REAL bxay1, cxby1, dxcy1, axdy1, cxay1, dxby1; + REAL axby0, bxcy0, cxdy0, dxay0, axcy0, bxdy0; + REAL bxay0, cxby0, dxcy0, axdy0, cxay0, dxby0; + REAL ab[4], bc[4], cd[4], da[4], ac[4], bd[4]; + REAL temp8[8]; + int templen; + REAL abc[12], bcd[12], cda[12], dab[12]; + int abclen, bcdlen, cdalen, dablen; + REAL adet[24], bdet[24], cdet[24], ddet[24]; + int alen, blen, clen, dlen; + REAL abdet[48], cddet[48]; + int ablen, cdlen; + REAL deter[96]; + int deterlen; + int i; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j; + REAL _0; + + Two_Product(pa[0], pb[1], axby1, axby0); + Two_Product(pb[0], pa[1], bxay1, bxay0); + Two_Two_Diff(axby1, axby0, bxay1, bxay0, ab[3], ab[2], ab[1], ab[0]); + + Two_Product(pb[0], pc[1], bxcy1, bxcy0); + Two_Product(pc[0], pb[1], cxby1, cxby0); + Two_Two_Diff(bxcy1, bxcy0, cxby1, cxby0, bc[3], bc[2], bc[1], bc[0]); + + Two_Product(pc[0], pd[1], cxdy1, cxdy0); + Two_Product(pd[0], pc[1], dxcy1, dxcy0); + Two_Two_Diff(cxdy1, cxdy0, dxcy1, dxcy0, cd[3], cd[2], cd[1], cd[0]); + + Two_Product(pd[0], pa[1], dxay1, dxay0); + Two_Product(pa[0], pd[1], axdy1, axdy0); + Two_Two_Diff(dxay1, dxay0, axdy1, axdy0, da[3], da[2], da[1], da[0]); + + Two_Product(pa[0], pc[1], axcy1, axcy0); + Two_Product(pc[0], pa[1], cxay1, cxay0); + Two_Two_Diff(axcy1, axcy0, cxay1, cxay0, ac[3], ac[2], ac[1], ac[0]); + + Two_Product(pb[0], pd[1], bxdy1, bxdy0); + Two_Product(pd[0], pb[1], dxby1, dxby0); + Two_Two_Diff(bxdy1, bxdy0, dxby1, dxby0, bd[3], bd[2], bd[1], bd[0]); + + templen = fast_expansion_sum_zeroelim(4, cd, 4, da, temp8); + cdalen = fast_expansion_sum_zeroelim(templen, temp8, 4, ac, cda); + templen = fast_expansion_sum_zeroelim(4, da, 4, ab, temp8); + dablen = fast_expansion_sum_zeroelim(templen, temp8, 4, bd, dab); + for (i = 0; i < 4; i++) { + bd[i] = -bd[i]; + ac[i] = -ac[i]; + } + templen = fast_expansion_sum_zeroelim(4, ab, 4, bc, temp8); + abclen = fast_expansion_sum_zeroelim(templen, temp8, 4, ac, abc); + templen = fast_expansion_sum_zeroelim(4, bc, 4, cd, temp8); + bcdlen = fast_expansion_sum_zeroelim(templen, temp8, 4, bd, bcd); + + alen = scale_expansion_zeroelim(bcdlen, bcd, pa[2], adet); + blen = scale_expansion_zeroelim(cdalen, cda, -pb[2], bdet); + clen = scale_expansion_zeroelim(dablen, dab, pc[2], cdet); + dlen = scale_expansion_zeroelim(abclen, abc, -pd[2], ddet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + cdlen = fast_expansion_sum_zeroelim(clen, cdet, dlen, ddet, cddet); + deterlen = fast_expansion_sum_zeroelim(ablen, abdet, cdlen, cddet, deter); + + return deter[deterlen - 1]; +} + +REAL orient3dslow(pa, pb, pc, pd) + REAL* pa; +REAL* pb; +REAL* pc; +REAL* pd; +{ + INEXACT REAL adx, ady, adz, bdx, bdy, bdz, cdx, cdy, cdz; + REAL adxtail, adytail, adztail; + REAL bdxtail, bdytail, bdztail; + REAL cdxtail, cdytail, cdztail; + REAL negate, negatetail; + INEXACT REAL axby7, bxcy7, axcy7, bxay7, cxby7, cxay7; + REAL axby[8], bxcy[8], axcy[8], bxay[8], cxby[8], cxay[8]; + REAL temp16[16], temp32[32], temp32t[32]; + int temp16len, temp32len, temp32tlen; + REAL adet[64], bdet[64], cdet[64]; + int alen, blen, clen; + REAL abdet[128]; + int ablen; + REAL deter[192]; + int deterlen; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL a0hi, a0lo, a1hi, a1lo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j, _k, _l, _m, _n; + REAL _0, _1, _2; + + Two_Diff(pa[0], pd[0], adx, adxtail); + Two_Diff(pa[1], pd[1], ady, adytail); + Two_Diff(pa[2], pd[2], adz, adztail); + Two_Diff(pb[0], pd[0], bdx, bdxtail); + Two_Diff(pb[1], pd[1], bdy, bdytail); + Two_Diff(pb[2], pd[2], bdz, bdztail); + Two_Diff(pc[0], pd[0], cdx, cdxtail); + Two_Diff(pc[1], pd[1], cdy, cdytail); + Two_Diff(pc[2], pd[2], cdz, cdztail); + + Two_Two_Product(adx, adxtail, bdy, bdytail, + axby7, axby[6], axby[5], axby[4], + axby[3], axby[2], axby[1], axby[0]); + axby[7] = axby7; + negate = -ady; + negatetail = -adytail; + Two_Two_Product(bdx, bdxtail, negate, negatetail, + bxay7, bxay[6], bxay[5], bxay[4], + bxay[3], bxay[2], bxay[1], bxay[0]); + bxay[7] = bxay7; + Two_Two_Product(bdx, bdxtail, cdy, cdytail, + bxcy7, bxcy[6], bxcy[5], bxcy[4], + bxcy[3], bxcy[2], bxcy[1], bxcy[0]); + bxcy[7] = bxcy7; + negate = -bdy; + negatetail = -bdytail; + Two_Two_Product(cdx, cdxtail, negate, negatetail, + cxby7, cxby[6], cxby[5], cxby[4], + cxby[3], cxby[2], cxby[1], cxby[0]); + cxby[7] = cxby7; + Two_Two_Product(cdx, cdxtail, ady, adytail, + cxay7, cxay[6], cxay[5], cxay[4], + cxay[3], cxay[2], cxay[1], cxay[0]); + cxay[7] = cxay7; + negate = -cdy; + negatetail = -cdytail; + Two_Two_Product(adx, adxtail, negate, negatetail, + axcy7, axcy[6], axcy[5], axcy[4], + axcy[3], axcy[2], axcy[1], axcy[0]); + axcy[7] = axcy7; + + temp16len = fast_expansion_sum_zeroelim(8, bxcy, 8, cxby, temp16); + temp32len = scale_expansion_zeroelim(temp16len, temp16, adz, temp32); + temp32tlen = scale_expansion_zeroelim(temp16len, temp16, adztail, temp32t); + alen = fast_expansion_sum_zeroelim(temp32len, temp32, temp32tlen, temp32t, + adet); + + temp16len = fast_expansion_sum_zeroelim(8, cxay, 8, axcy, temp16); + temp32len = scale_expansion_zeroelim(temp16len, temp16, bdz, temp32); + temp32tlen = scale_expansion_zeroelim(temp16len, temp16, bdztail, temp32t); + blen = fast_expansion_sum_zeroelim(temp32len, temp32, temp32tlen, temp32t, + bdet); + + temp16len = fast_expansion_sum_zeroelim(8, axby, 8, bxay, temp16); + temp32len = scale_expansion_zeroelim(temp16len, temp16, cdz, temp32); + temp32tlen = scale_expansion_zeroelim(temp16len, temp16, cdztail, temp32t); + clen = fast_expansion_sum_zeroelim(temp32len, temp32, temp32tlen, temp32t, + cdet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + deterlen = fast_expansion_sum_zeroelim(ablen, abdet, clen, cdet, deter); + + return deter[deterlen - 1]; +} + +REAL orient3dadapt(pa, pb, pc, pd, permanent) + REAL* pa; +REAL* pb; +REAL* pc; +REAL* pd; +REAL permanent; +{ + INEXACT REAL adx, bdx, cdx, ady, bdy, cdy, adz, bdz, cdz; + REAL det, errbound; + + INEXACT REAL bdxcdy1, cdxbdy1, cdxady1, adxcdy1, adxbdy1, bdxady1; + REAL bdxcdy0, cdxbdy0, cdxady0, adxcdy0, adxbdy0, bdxady0; + REAL bc[4], ca[4], ab[4]; + INEXACT REAL bc3, ca3, ab3; + REAL adet[8], bdet[8], cdet[8]; + int alen, blen, clen; + REAL abdet[16]; + int ablen; + REAL *finnow, *finother, *finswap; + REAL fin1[192], fin2[192]; + int finlength; + + REAL adxtail, bdxtail, cdxtail; + REAL adytail, bdytail, cdytail; + REAL adztail, bdztail, cdztail; + INEXACT REAL at_blarge, at_clarge; + INEXACT REAL bt_clarge, bt_alarge; + INEXACT REAL ct_alarge, ct_blarge; + REAL at_b[4], at_c[4], bt_c[4], bt_a[4], ct_a[4], ct_b[4]; + int at_blen, at_clen, bt_clen, bt_alen, ct_alen, ct_blen; + INEXACT REAL bdxt_cdy1, cdxt_bdy1, cdxt_ady1; + INEXACT REAL adxt_cdy1, adxt_bdy1, bdxt_ady1; + REAL bdxt_cdy0, cdxt_bdy0, cdxt_ady0; + REAL adxt_cdy0, adxt_bdy0, bdxt_ady0; + INEXACT REAL bdyt_cdx1, cdyt_bdx1, cdyt_adx1; + INEXACT REAL adyt_cdx1, adyt_bdx1, bdyt_adx1; + REAL bdyt_cdx0, cdyt_bdx0, cdyt_adx0; + REAL adyt_cdx0, adyt_bdx0, bdyt_adx0; + REAL bct[8], cat[8], abt[8]; + int bctlen, catlen, abtlen; + INEXACT REAL bdxt_cdyt1, cdxt_bdyt1, cdxt_adyt1; + INEXACT REAL adxt_cdyt1, adxt_bdyt1, bdxt_adyt1; + REAL bdxt_cdyt0, cdxt_bdyt0, cdxt_adyt0; + REAL adxt_cdyt0, adxt_bdyt0, bdxt_adyt0; + REAL u[4], v[12], w[16]; + INEXACT REAL u3; + int vlength, wlength; + REAL negate; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j, _k; + REAL _0; + + adx = (REAL)(pa[0] - pd[0]); + bdx = (REAL)(pb[0] - pd[0]); + cdx = (REAL)(pc[0] - pd[0]); + ady = (REAL)(pa[1] - pd[1]); + bdy = (REAL)(pb[1] - pd[1]); + cdy = (REAL)(pc[1] - pd[1]); + adz = (REAL)(pa[2] - pd[2]); + bdz = (REAL)(pb[2] - pd[2]); + cdz = (REAL)(pc[2] - pd[2]); + + Two_Product(bdx, cdy, bdxcdy1, bdxcdy0); + Two_Product(cdx, bdy, cdxbdy1, cdxbdy0); + Two_Two_Diff(bdxcdy1, bdxcdy0, cdxbdy1, cdxbdy0, bc3, bc[2], bc[1], bc[0]); + bc[3] = bc3; + alen = scale_expansion_zeroelim(4, bc, adz, adet); + + Two_Product(cdx, ady, cdxady1, cdxady0); + Two_Product(adx, cdy, adxcdy1, adxcdy0); + Two_Two_Diff(cdxady1, cdxady0, adxcdy1, adxcdy0, ca3, ca[2], ca[1], ca[0]); + ca[3] = ca3; + blen = scale_expansion_zeroelim(4, ca, bdz, bdet); + + Two_Product(adx, bdy, adxbdy1, adxbdy0); + Two_Product(bdx, ady, bdxady1, bdxady0); + Two_Two_Diff(adxbdy1, adxbdy0, bdxady1, bdxady0, ab3, ab[2], ab[1], ab[0]); + ab[3] = ab3; + clen = scale_expansion_zeroelim(4, ab, cdz, cdet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + finlength = fast_expansion_sum_zeroelim(ablen, abdet, clen, cdet, fin1); + + det = estimate(finlength, fin1); + errbound = o3derrboundB * permanent; + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + Two_Diff_Tail(pa[0], pd[0], adx, adxtail); + Two_Diff_Tail(pb[0], pd[0], bdx, bdxtail); + Two_Diff_Tail(pc[0], pd[0], cdx, cdxtail); + Two_Diff_Tail(pa[1], pd[1], ady, adytail); + Two_Diff_Tail(pb[1], pd[1], bdy, bdytail); + Two_Diff_Tail(pc[1], pd[1], cdy, cdytail); + Two_Diff_Tail(pa[2], pd[2], adz, adztail); + Two_Diff_Tail(pb[2], pd[2], bdz, bdztail); + Two_Diff_Tail(pc[2], pd[2], cdz, cdztail); + + if ((adxtail == 0.0) && (bdxtail == 0.0) && (cdxtail == 0.0) + && (adytail == 0.0) && (bdytail == 0.0) && (cdytail == 0.0) + && (adztail == 0.0) && (bdztail == 0.0) && (cdztail == 0.0)) { + return det; + } + + errbound = o3derrboundC * permanent + resulterrbound * Absolute(det); + det += (adz * ((bdx * cdytail + cdy * bdxtail) - (bdy * cdxtail + cdx * bdytail)) + + adztail * (bdx * cdy - bdy * cdx)) + + (bdz * ((cdx * adytail + ady * cdxtail) - (cdy * adxtail + adx * cdytail)) + + bdztail * (cdx * ady - cdy * adx)) + + (cdz * ((adx * bdytail + bdy * adxtail) - (ady * bdxtail + bdx * adytail)) + + cdztail * (adx * bdy - ady * bdx)); + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + finnow = fin1; + finother = fin2; + + if (adxtail == 0.0) { + if (adytail == 0.0) { + at_b[0] = 0.0; + at_blen = 1; + at_c[0] = 0.0; + at_clen = 1; + } else { + negate = -adytail; + Two_Product(negate, bdx, at_blarge, at_b[0]); + at_b[1] = at_blarge; + at_blen = 2; + Two_Product(adytail, cdx, at_clarge, at_c[0]); + at_c[1] = at_clarge; + at_clen = 2; + } + } else { + if (adytail == 0.0) { + Two_Product(adxtail, bdy, at_blarge, at_b[0]); + at_b[1] = at_blarge; + at_blen = 2; + negate = -adxtail; + Two_Product(negate, cdy, at_clarge, at_c[0]); + at_c[1] = at_clarge; + at_clen = 2; + } else { + Two_Product(adxtail, bdy, adxt_bdy1, adxt_bdy0); + Two_Product(adytail, bdx, adyt_bdx1, adyt_bdx0); + Two_Two_Diff(adxt_bdy1, adxt_bdy0, adyt_bdx1, adyt_bdx0, + at_blarge, at_b[2], at_b[1], at_b[0]); + at_b[3] = at_blarge; + at_blen = 4; + Two_Product(adytail, cdx, adyt_cdx1, adyt_cdx0); + Two_Product(adxtail, cdy, adxt_cdy1, adxt_cdy0); + Two_Two_Diff(adyt_cdx1, adyt_cdx0, adxt_cdy1, adxt_cdy0, + at_clarge, at_c[2], at_c[1], at_c[0]); + at_c[3] = at_clarge; + at_clen = 4; + } + } + if (bdxtail == 0.0) { + if (bdytail == 0.0) { + bt_c[0] = 0.0; + bt_clen = 1; + bt_a[0] = 0.0; + bt_alen = 1; + } else { + negate = -bdytail; + Two_Product(negate, cdx, bt_clarge, bt_c[0]); + bt_c[1] = bt_clarge; + bt_clen = 2; + Two_Product(bdytail, adx, bt_alarge, bt_a[0]); + bt_a[1] = bt_alarge; + bt_alen = 2; + } + } else { + if (bdytail == 0.0) { + Two_Product(bdxtail, cdy, bt_clarge, bt_c[0]); + bt_c[1] = bt_clarge; + bt_clen = 2; + negate = -bdxtail; + Two_Product(negate, ady, bt_alarge, bt_a[0]); + bt_a[1] = bt_alarge; + bt_alen = 2; + } else { + Two_Product(bdxtail, cdy, bdxt_cdy1, bdxt_cdy0); + Two_Product(bdytail, cdx, bdyt_cdx1, bdyt_cdx0); + Two_Two_Diff(bdxt_cdy1, bdxt_cdy0, bdyt_cdx1, bdyt_cdx0, + bt_clarge, bt_c[2], bt_c[1], bt_c[0]); + bt_c[3] = bt_clarge; + bt_clen = 4; + Two_Product(bdytail, adx, bdyt_adx1, bdyt_adx0); + Two_Product(bdxtail, ady, bdxt_ady1, bdxt_ady0); + Two_Two_Diff(bdyt_adx1, bdyt_adx0, bdxt_ady1, bdxt_ady0, + bt_alarge, bt_a[2], bt_a[1], bt_a[0]); + bt_a[3] = bt_alarge; + bt_alen = 4; + } + } + if (cdxtail == 0.0) { + if (cdytail == 0.0) { + ct_a[0] = 0.0; + ct_alen = 1; + ct_b[0] = 0.0; + ct_blen = 1; + } else { + negate = -cdytail; + Two_Product(negate, adx, ct_alarge, ct_a[0]); + ct_a[1] = ct_alarge; + ct_alen = 2; + Two_Product(cdytail, bdx, ct_blarge, ct_b[0]); + ct_b[1] = ct_blarge; + ct_blen = 2; + } + } else { + if (cdytail == 0.0) { + Two_Product(cdxtail, ady, ct_alarge, ct_a[0]); + ct_a[1] = ct_alarge; + ct_alen = 2; + negate = -cdxtail; + Two_Product(negate, bdy, ct_blarge, ct_b[0]); + ct_b[1] = ct_blarge; + ct_blen = 2; + } else { + Two_Product(cdxtail, ady, cdxt_ady1, cdxt_ady0); + Two_Product(cdytail, adx, cdyt_adx1, cdyt_adx0); + Two_Two_Diff(cdxt_ady1, cdxt_ady0, cdyt_adx1, cdyt_adx0, + ct_alarge, ct_a[2], ct_a[1], ct_a[0]); + ct_a[3] = ct_alarge; + ct_alen = 4; + Two_Product(cdytail, bdx, cdyt_bdx1, cdyt_bdx0); + Two_Product(cdxtail, bdy, cdxt_bdy1, cdxt_bdy0); + Two_Two_Diff(cdyt_bdx1, cdyt_bdx0, cdxt_bdy1, cdxt_bdy0, + ct_blarge, ct_b[2], ct_b[1], ct_b[0]); + ct_b[3] = ct_blarge; + ct_blen = 4; + } + } + + bctlen = fast_expansion_sum_zeroelim(bt_clen, bt_c, ct_blen, ct_b, bct); + wlength = scale_expansion_zeroelim(bctlen, bct, adz, w); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, wlength, w, + finother); + finswap = finnow; + finnow = finother; + finother = finswap; + + catlen = fast_expansion_sum_zeroelim(ct_alen, ct_a, at_clen, at_c, cat); + wlength = scale_expansion_zeroelim(catlen, cat, bdz, w); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, wlength, w, + finother); + finswap = finnow; + finnow = finother; + finother = finswap; + + abtlen = fast_expansion_sum_zeroelim(at_blen, at_b, bt_alen, bt_a, abt); + wlength = scale_expansion_zeroelim(abtlen, abt, cdz, w); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, wlength, w, + finother); + finswap = finnow; + finnow = finother; + finother = finswap; + + if (adztail != 0.0) { + vlength = scale_expansion_zeroelim(4, bc, adztail, v); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, vlength, v, + finother); + finswap = finnow; + finnow = finother; + finother = finswap; + } + if (bdztail != 0.0) { + vlength = scale_expansion_zeroelim(4, ca, bdztail, v); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, vlength, v, + finother); + finswap = finnow; + finnow = finother; + finother = finswap; + } + if (cdztail != 0.0) { + vlength = scale_expansion_zeroelim(4, ab, cdztail, v); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, vlength, v, + finother); + finswap = finnow; + finnow = finother; + finother = finswap; + } + + if (adxtail != 0.0) { + if (bdytail != 0.0) { + Two_Product(adxtail, bdytail, adxt_bdyt1, adxt_bdyt0); + Two_One_Product(adxt_bdyt1, adxt_bdyt0, cdz, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; + finnow = finother; + finother = finswap; + if (cdztail != 0.0) { + Two_One_Product(adxt_bdyt1, adxt_bdyt0, cdztail, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; + finnow = finother; + finother = finswap; + } + } + if (cdytail != 0.0) { + negate = -adxtail; + Two_Product(negate, cdytail, adxt_cdyt1, adxt_cdyt0); + Two_One_Product(adxt_cdyt1, adxt_cdyt0, bdz, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; + finnow = finother; + finother = finswap; + if (bdztail != 0.0) { + Two_One_Product(adxt_cdyt1, adxt_cdyt0, bdztail, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; + finnow = finother; + finother = finswap; + } + } + } + if (bdxtail != 0.0) { + if (cdytail != 0.0) { + Two_Product(bdxtail, cdytail, bdxt_cdyt1, bdxt_cdyt0); + Two_One_Product(bdxt_cdyt1, bdxt_cdyt0, adz, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; + finnow = finother; + finother = finswap; + if (adztail != 0.0) { + Two_One_Product(bdxt_cdyt1, bdxt_cdyt0, adztail, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; + finnow = finother; + finother = finswap; + } + } + if (adytail != 0.0) { + negate = -bdxtail; + Two_Product(negate, adytail, bdxt_adyt1, bdxt_adyt0); + Two_One_Product(bdxt_adyt1, bdxt_adyt0, cdz, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; + finnow = finother; + finother = finswap; + if (cdztail != 0.0) { + Two_One_Product(bdxt_adyt1, bdxt_adyt0, cdztail, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; + finnow = finother; + finother = finswap; + } + } + } + if (cdxtail != 0.0) { + if (adytail != 0.0) { + Two_Product(cdxtail, adytail, cdxt_adyt1, cdxt_adyt0); + Two_One_Product(cdxt_adyt1, cdxt_adyt0, bdz, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; + finnow = finother; + finother = finswap; + if (bdztail != 0.0) { + Two_One_Product(cdxt_adyt1, cdxt_adyt0, bdztail, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; + finnow = finother; + finother = finswap; + } + } + if (bdytail != 0.0) { + negate = -cdxtail; + Two_Product(negate, bdytail, cdxt_bdyt1, cdxt_bdyt0); + Two_One_Product(cdxt_bdyt1, cdxt_bdyt0, adz, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; + finnow = finother; + finother = finswap; + if (adztail != 0.0) { + Two_One_Product(cdxt_bdyt1, cdxt_bdyt0, adztail, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; + finnow = finother; + finother = finswap; + } + } + } + + if (adztail != 0.0) { + wlength = scale_expansion_zeroelim(bctlen, bct, adztail, w); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, wlength, w, + finother); + finswap = finnow; + finnow = finother; + finother = finswap; + } + if (bdztail != 0.0) { + wlength = scale_expansion_zeroelim(catlen, cat, bdztail, w); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, wlength, w, + finother); + finswap = finnow; + finnow = finother; + finother = finswap; + } + if (cdztail != 0.0) { + wlength = scale_expansion_zeroelim(abtlen, abt, cdztail, w); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, wlength, w, + finother); + finswap = finnow; + finnow = finother; + finother = finswap; + } + + return finnow[finlength - 1]; +} + +REAL orient3d(pa, pb, pc, pd) + REAL* pa; +REAL* pb; +REAL* pc; +REAL* pd; +{ + REAL adx, bdx, cdx, ady, bdy, cdy, adz, bdz, cdz; + REAL bdxcdy, cdxbdy, cdxady, adxcdy, adxbdy, bdxady; + REAL det; + REAL permanent, errbound; + + adx = pa[0] - pd[0]; + bdx = pb[0] - pd[0]; + cdx = pc[0] - pd[0]; + ady = pa[1] - pd[1]; + bdy = pb[1] - pd[1]; + cdy = pc[1] - pd[1]; + adz = pa[2] - pd[2]; + bdz = pb[2] - pd[2]; + cdz = pc[2] - pd[2]; + + bdxcdy = bdx * cdy; + cdxbdy = cdx * bdy; + + cdxady = cdx * ady; + adxcdy = adx * cdy; + + adxbdy = adx * bdy; + bdxady = bdx * ady; + + det = adz * (bdxcdy - cdxbdy) + + bdz * (cdxady - adxcdy) + + cdz * (adxbdy - bdxady); + + permanent = (Absolute(bdxcdy) + Absolute(cdxbdy)) * Absolute(adz) + + (Absolute(cdxady) + Absolute(adxcdy)) * Absolute(bdz) + + (Absolute(adxbdy) + Absolute(bdxady)) * Absolute(cdz); + errbound = o3derrboundA * permanent; + if ((det > errbound) || (-det > errbound)) { + return det; + } + + return orient3dadapt(pa, pb, pc, pd, permanent); +} + +#ifdef _WIN32 +#pragma warning(pop) // #pragma warning(disable: 4131) +#endif // _WIN32 + +#if 0 +/*****************************************************************************/ +/* */ +/* incirclefast() Approximate 2D incircle test. Nonrobust. */ +/* incircleexact() Exact 2D incircle test. Robust. */ +/* incircleslow() Another exact 2D incircle test. Robust. */ +/* incircle() Adaptive exact 2D incircle test. Robust. */ +/* */ +/* Return a positive value if the point pd lies inside the */ +/* circle passing through pa, pb, and pc; a negative value if */ +/* it lies outside; and zero if the four points are cocircular.*/ +/* The points pa, pb, and pc must be in counterclockwise */ +/* order, or the sign of the result will be reversed. */ +/* */ +/* Only the first and last routine should be used; the middle two are for */ +/* timings. */ +/* */ +/* The last three use exact arithmetic to ensure a correct answer. The */ +/* result returned is the determinant of a matrix. In incircle() only, */ +/* this determinant is computed adaptively, in the sense that exact */ +/* arithmetic is used only to the degree it is needed to ensure that the */ +/* returned value has the correct sign. Hence, incircle() is usually quite */ +/* fast, but will run more slowly when the input points are cocircular or */ +/* nearly so. */ +/* */ +/*****************************************************************************/ + +REAL incirclefast(pa, pb, pc, pd) + REAL* pa; +REAL* pb; +REAL* pc; +REAL* pd; +{ + REAL adx, ady, bdx, bdy, cdx, cdy; + REAL abdet, bcdet, cadet; + REAL alift, blift, clift; + + adx = pa[0] - pd[0]; + ady = pa[1] - pd[1]; + bdx = pb[0] - pd[0]; + bdy = pb[1] - pd[1]; + cdx = pc[0] - pd[0]; + cdy = pc[1] - pd[1]; + + abdet = adx * bdy - bdx * ady; + bcdet = bdx * cdy - cdx * bdy; + cadet = cdx * ady - adx * cdy; + alift = adx * adx + ady * ady; + blift = bdx * bdx + bdy * bdy; + clift = cdx * cdx + cdy * cdy; + + return alift * bcdet + blift * cadet + clift * abdet; +} + +REAL incircleexact(pa, pb, pc, pd) + REAL* pa; +REAL* pb; +REAL* pc; +REAL* pd; +{ + INEXACT REAL axby1, bxcy1, cxdy1, dxay1, axcy1, bxdy1; + INEXACT REAL bxay1, cxby1, dxcy1, axdy1, cxay1, dxby1; + REAL axby0, bxcy0, cxdy0, dxay0, axcy0, bxdy0; + REAL bxay0, cxby0, dxcy0, axdy0, cxay0, dxby0; + REAL ab[4], bc[4], cd[4], da[4], ac[4], bd[4]; + REAL temp8[8]; + int templen; + REAL abc[12], bcd[12], cda[12], dab[12]; + int abclen, bcdlen, cdalen, dablen; + REAL det24x[24], det24y[24], det48x[48], det48y[48]; + int xlen, ylen; + REAL adet[96], bdet[96], cdet[96], ddet[96]; + int alen, blen, clen, dlen; + REAL abdet[192], cddet[192]; + int ablen, cdlen; + REAL deter[384]; + int deterlen; + int i; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j; + REAL _0; + + Two_Product(pa[0], pb[1], axby1, axby0); + Two_Product(pb[0], pa[1], bxay1, bxay0); + Two_Two_Diff(axby1, axby0, bxay1, bxay0, ab[3], ab[2], ab[1], ab[0]); + + Two_Product(pb[0], pc[1], bxcy1, bxcy0); + Two_Product(pc[0], pb[1], cxby1, cxby0); + Two_Two_Diff(bxcy1, bxcy0, cxby1, cxby0, bc[3], bc[2], bc[1], bc[0]); + + Two_Product(pc[0], pd[1], cxdy1, cxdy0); + Two_Product(pd[0], pc[1], dxcy1, dxcy0); + Two_Two_Diff(cxdy1, cxdy0, dxcy1, dxcy0, cd[3], cd[2], cd[1], cd[0]); + + Two_Product(pd[0], pa[1], dxay1, dxay0); + Two_Product(pa[0], pd[1], axdy1, axdy0); + Two_Two_Diff(dxay1, dxay0, axdy1, axdy0, da[3], da[2], da[1], da[0]); + + Two_Product(pa[0], pc[1], axcy1, axcy0); + Two_Product(pc[0], pa[1], cxay1, cxay0); + Two_Two_Diff(axcy1, axcy0, cxay1, cxay0, ac[3], ac[2], ac[1], ac[0]); + + Two_Product(pb[0], pd[1], bxdy1, bxdy0); + Two_Product(pd[0], pb[1], dxby1, dxby0); + Two_Two_Diff(bxdy1, bxdy0, dxby1, dxby0, bd[3], bd[2], bd[1], bd[0]); + + templen = fast_expansion_sum_zeroelim(4, cd, 4, da, temp8); + cdalen = fast_expansion_sum_zeroelim(templen, temp8, 4, ac, cda); + templen = fast_expansion_sum_zeroelim(4, da, 4, ab, temp8); + dablen = fast_expansion_sum_zeroelim(templen, temp8, 4, bd, dab); + for (i = 0; i < 4; i++) { + bd[i] = -bd[i]; + ac[i] = -ac[i]; + } + templen = fast_expansion_sum_zeroelim(4, ab, 4, bc, temp8); + abclen = fast_expansion_sum_zeroelim(templen, temp8, 4, ac, abc); + templen = fast_expansion_sum_zeroelim(4, bc, 4, cd, temp8); + bcdlen = fast_expansion_sum_zeroelim(templen, temp8, 4, bd, bcd); + + xlen = scale_expansion_zeroelim(bcdlen, bcd, pa[0], det24x); + xlen = scale_expansion_zeroelim(xlen, det24x, pa[0], det48x); + ylen = scale_expansion_zeroelim(bcdlen, bcd, pa[1], det24y); + ylen = scale_expansion_zeroelim(ylen, det24y, pa[1], det48y); + alen = fast_expansion_sum_zeroelim(xlen, det48x, ylen, det48y, adet); + + xlen = scale_expansion_zeroelim(cdalen, cda, pb[0], det24x); + xlen = scale_expansion_zeroelim(xlen, det24x, -pb[0], det48x); + ylen = scale_expansion_zeroelim(cdalen, cda, pb[1], det24y); + ylen = scale_expansion_zeroelim(ylen, det24y, -pb[1], det48y); + blen = fast_expansion_sum_zeroelim(xlen, det48x, ylen, det48y, bdet); + + xlen = scale_expansion_zeroelim(dablen, dab, pc[0], det24x); + xlen = scale_expansion_zeroelim(xlen, det24x, pc[0], det48x); + ylen = scale_expansion_zeroelim(dablen, dab, pc[1], det24y); + ylen = scale_expansion_zeroelim(ylen, det24y, pc[1], det48y); + clen = fast_expansion_sum_zeroelim(xlen, det48x, ylen, det48y, cdet); + + xlen = scale_expansion_zeroelim(abclen, abc, pd[0], det24x); + xlen = scale_expansion_zeroelim(xlen, det24x, -pd[0], det48x); + ylen = scale_expansion_zeroelim(abclen, abc, pd[1], det24y); + ylen = scale_expansion_zeroelim(ylen, det24y, -pd[1], det48y); + dlen = fast_expansion_sum_zeroelim(xlen, det48x, ylen, det48y, ddet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + cdlen = fast_expansion_sum_zeroelim(clen, cdet, dlen, ddet, cddet); + deterlen = fast_expansion_sum_zeroelim(ablen, abdet, cdlen, cddet, deter); + + return deter[deterlen - 1]; +} + +REAL incircleslow(pa, pb, pc, pd) + REAL* pa; +REAL* pb; +REAL* pc; +REAL* pd; +{ + INEXACT REAL adx, bdx, cdx, ady, bdy, cdy; + REAL adxtail, bdxtail, cdxtail; + REAL adytail, bdytail, cdytail; + REAL negate, negatetail; + INEXACT REAL axby7, bxcy7, axcy7, bxay7, cxby7, cxay7; + REAL axby[8], bxcy[8], axcy[8], bxay[8], cxby[8], cxay[8]; + REAL temp16[16]; + int temp16len; + REAL detx[32], detxx[64], detxt[32], detxxt[64], detxtxt[64]; + int xlen, xxlen, xtlen, xxtlen, xtxtlen; + REAL x1[128], x2[192]; + int x1len, x2len; + REAL dety[32], detyy[64], detyt[32], detyyt[64], detytyt[64]; + int ylen, yylen, ytlen, yytlen, ytytlen; + REAL y1[128], y2[192]; + int y1len, y2len; + REAL adet[384], bdet[384], cdet[384], abdet[768], deter[1152]; + int alen, blen, clen, ablen, deterlen; + int i; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL a0hi, a0lo, a1hi, a1lo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j, _k, _l, _m, _n; + REAL _0, _1, _2; + + Two_Diff(pa[0], pd[0], adx, adxtail); + Two_Diff(pa[1], pd[1], ady, adytail); + Two_Diff(pb[0], pd[0], bdx, bdxtail); + Two_Diff(pb[1], pd[1], bdy, bdytail); + Two_Diff(pc[0], pd[0], cdx, cdxtail); + Two_Diff(pc[1], pd[1], cdy, cdytail); + + Two_Two_Product(adx, adxtail, bdy, bdytail, + axby7, axby[6], axby[5], axby[4], + axby[3], axby[2], axby[1], axby[0]); + axby[7] = axby7; + negate = -ady; + negatetail = -adytail; + Two_Two_Product(bdx, bdxtail, negate, negatetail, + bxay7, bxay[6], bxay[5], bxay[4], + bxay[3], bxay[2], bxay[1], bxay[0]); + bxay[7] = bxay7; + Two_Two_Product(bdx, bdxtail, cdy, cdytail, + bxcy7, bxcy[6], bxcy[5], bxcy[4], + bxcy[3], bxcy[2], bxcy[1], bxcy[0]); + bxcy[7] = bxcy7; + negate = -bdy; + negatetail = -bdytail; + Two_Two_Product(cdx, cdxtail, negate, negatetail, + cxby7, cxby[6], cxby[5], cxby[4], + cxby[3], cxby[2], cxby[1], cxby[0]); + cxby[7] = cxby7; + Two_Two_Product(cdx, cdxtail, ady, adytail, + cxay7, cxay[6], cxay[5], cxay[4], + cxay[3], cxay[2], cxay[1], cxay[0]); + cxay[7] = cxay7; + negate = -cdy; + negatetail = -cdytail; + Two_Two_Product(adx, adxtail, negate, negatetail, + axcy7, axcy[6], axcy[5], axcy[4], + axcy[3], axcy[2], axcy[1], axcy[0]); + axcy[7] = axcy7; + + temp16len = fast_expansion_sum_zeroelim(8, bxcy, 8, cxby, temp16); + + xlen = scale_expansion_zeroelim(temp16len, temp16, adx, detx); + xxlen = scale_expansion_zeroelim(xlen, detx, adx, detxx); + xtlen = scale_expansion_zeroelim(temp16len, temp16, adxtail, detxt); + xxtlen = scale_expansion_zeroelim(xtlen, detxt, adx, detxxt); + for (i = 0; i < xxtlen; i++) { + detxxt[i] *= 2.0; + } + xtxtlen = scale_expansion_zeroelim(xtlen, detxt, adxtail, detxtxt); + x1len = fast_expansion_sum_zeroelim(xxlen, detxx, xxtlen, detxxt, x1); + x2len = fast_expansion_sum_zeroelim(x1len, x1, xtxtlen, detxtxt, x2); + + ylen = scale_expansion_zeroelim(temp16len, temp16, ady, dety); + yylen = scale_expansion_zeroelim(ylen, dety, ady, detyy); + ytlen = scale_expansion_zeroelim(temp16len, temp16, adytail, detyt); + yytlen = scale_expansion_zeroelim(ytlen, detyt, ady, detyyt); + for (i = 0; i < yytlen; i++) { + detyyt[i] *= 2.0; + } + ytytlen = scale_expansion_zeroelim(ytlen, detyt, adytail, detytyt); + y1len = fast_expansion_sum_zeroelim(yylen, detyy, yytlen, detyyt, y1); + y2len = fast_expansion_sum_zeroelim(y1len, y1, ytytlen, detytyt, y2); + + alen = fast_expansion_sum_zeroelim(x2len, x2, y2len, y2, adet); + + temp16len = fast_expansion_sum_zeroelim(8, cxay, 8, axcy, temp16); + + xlen = scale_expansion_zeroelim(temp16len, temp16, bdx, detx); + xxlen = scale_expansion_zeroelim(xlen, detx, bdx, detxx); + xtlen = scale_expansion_zeroelim(temp16len, temp16, bdxtail, detxt); + xxtlen = scale_expansion_zeroelim(xtlen, detxt, bdx, detxxt); + for (i = 0; i < xxtlen; i++) { + detxxt[i] *= 2.0; + } + xtxtlen = scale_expansion_zeroelim(xtlen, detxt, bdxtail, detxtxt); + x1len = fast_expansion_sum_zeroelim(xxlen, detxx, xxtlen, detxxt, x1); + x2len = fast_expansion_sum_zeroelim(x1len, x1, xtxtlen, detxtxt, x2); + + ylen = scale_expansion_zeroelim(temp16len, temp16, bdy, dety); + yylen = scale_expansion_zeroelim(ylen, dety, bdy, detyy); + ytlen = scale_expansion_zeroelim(temp16len, temp16, bdytail, detyt); + yytlen = scale_expansion_zeroelim(ytlen, detyt, bdy, detyyt); + for (i = 0; i < yytlen; i++) { + detyyt[i] *= 2.0; + } + ytytlen = scale_expansion_zeroelim(ytlen, detyt, bdytail, detytyt); + y1len = fast_expansion_sum_zeroelim(yylen, detyy, yytlen, detyyt, y1); + y2len = fast_expansion_sum_zeroelim(y1len, y1, ytytlen, detytyt, y2); + + blen = fast_expansion_sum_zeroelim(x2len, x2, y2len, y2, bdet); + + temp16len = fast_expansion_sum_zeroelim(8, axby, 8, bxay, temp16); + + xlen = scale_expansion_zeroelim(temp16len, temp16, cdx, detx); + xxlen = scale_expansion_zeroelim(xlen, detx, cdx, detxx); + xtlen = scale_expansion_zeroelim(temp16len, temp16, cdxtail, detxt); + xxtlen = scale_expansion_zeroelim(xtlen, detxt, cdx, detxxt); + for (i = 0; i < xxtlen; i++) { + detxxt[i] *= 2.0; + } + xtxtlen = scale_expansion_zeroelim(xtlen, detxt, cdxtail, detxtxt); + x1len = fast_expansion_sum_zeroelim(xxlen, detxx, xxtlen, detxxt, x1); + x2len = fast_expansion_sum_zeroelim(x1len, x1, xtxtlen, detxtxt, x2); + + ylen = scale_expansion_zeroelim(temp16len, temp16, cdy, dety); + yylen = scale_expansion_zeroelim(ylen, dety, cdy, detyy); + ytlen = scale_expansion_zeroelim(temp16len, temp16, cdytail, detyt); + yytlen = scale_expansion_zeroelim(ytlen, detyt, cdy, detyyt); + for (i = 0; i < yytlen; i++) { + detyyt[i] *= 2.0; + } + ytytlen = scale_expansion_zeroelim(ytlen, detyt, cdytail, detytyt); + y1len = fast_expansion_sum_zeroelim(yylen, detyy, yytlen, detyyt, y1); + y2len = fast_expansion_sum_zeroelim(y1len, y1, ytytlen, detytyt, y2); + + clen = fast_expansion_sum_zeroelim(x2len, x2, y2len, y2, cdet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + deterlen = fast_expansion_sum_zeroelim(ablen, abdet, clen, cdet, deter); + + return deter[deterlen - 1]; +} + +REAL incircleadapt(pa, pb, pc, pd, permanent) + REAL* pa; +REAL* pb; +REAL* pc; +REAL* pd; +REAL permanent; +{ + INEXACT REAL adx, bdx, cdx, ady, bdy, cdy; + REAL det, errbound; + + INEXACT REAL bdxcdy1, cdxbdy1, cdxady1, adxcdy1, adxbdy1, bdxady1; + REAL bdxcdy0, cdxbdy0, cdxady0, adxcdy0, adxbdy0, bdxady0; + REAL bc[4], ca[4], ab[4]; + INEXACT REAL bc3, ca3, ab3; + REAL axbc[8], axxbc[16], aybc[8], ayybc[16], adet[32]; + int axbclen, axxbclen, aybclen, ayybclen, alen; + REAL bxca[8], bxxca[16], byca[8], byyca[16], bdet[32]; + int bxcalen, bxxcalen, bycalen, byycalen, blen; + REAL cxab[8], cxxab[16], cyab[8], cyyab[16], cdet[32]; + int cxablen, cxxablen, cyablen, cyyablen, clen; + REAL abdet[64]; + int ablen; + REAL fin1[1152], fin2[1152]; + REAL *finnow, *finother, *finswap; + int finlength; + + REAL adxtail, bdxtail, cdxtail, adytail, bdytail, cdytail; + INEXACT REAL adxadx1, adyady1, bdxbdx1, bdybdy1, cdxcdx1, cdycdy1; + REAL adxadx0, adyady0, bdxbdx0, bdybdy0, cdxcdx0, cdycdy0; + REAL aa[4], bb[4], cc[4]; + INEXACT REAL aa3, bb3, cc3; + INEXACT REAL ti1, tj1; + REAL ti0, tj0; + REAL u[4], v[4]; + INEXACT REAL u3, v3; + REAL temp8[8], temp16a[16], temp16b[16], temp16c[16]; + REAL temp32a[32], temp32b[32], temp48[48], temp64[64]; + int temp8len, temp16alen, temp16blen, temp16clen; + int temp32alen, temp32blen, temp48len, temp64len; + REAL axtbb[8], axtcc[8], aytbb[8], aytcc[8]; + int axtbblen, axtcclen, aytbblen, aytcclen; + REAL bxtaa[8], bxtcc[8], bytaa[8], bytcc[8]; + int bxtaalen, bxtcclen, bytaalen, bytcclen; + REAL cxtaa[8], cxtbb[8], cytaa[8], cytbb[8]; + int cxtaalen, cxtbblen, cytaalen, cytbblen; + REAL axtbc[8], aytbc[8], bxtca[8], bytca[8], cxtab[8], cytab[8]; + int axtbclen, aytbclen, bxtcalen, bytcalen, cxtablen, cytablen; + REAL axtbct[16], aytbct[16], bxtcat[16], bytcat[16], cxtabt[16], cytabt[16]; + int axtbctlen, aytbctlen, bxtcatlen, bytcatlen, cxtabtlen, cytabtlen; + REAL axtbctt[8], aytbctt[8], bxtcatt[8]; + REAL bytcatt[8], cxtabtt[8], cytabtt[8]; + int axtbcttlen, aytbcttlen, bxtcattlen, bytcattlen, cxtabttlen, cytabttlen; + REAL abt[8], bct[8], cat[8]; + int abtlen, bctlen, catlen; + REAL abtt[4], bctt[4], catt[4]; + int abttlen, bcttlen, cattlen; + INEXACT REAL abtt3, bctt3, catt3; + REAL negate; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j; + REAL _0; + + adx = (REAL)(pa[0] - pd[0]); + bdx = (REAL)(pb[0] - pd[0]); + cdx = (REAL)(pc[0] - pd[0]); + ady = (REAL)(pa[1] - pd[1]); + bdy = (REAL)(pb[1] - pd[1]); + cdy = (REAL)(pc[1] - pd[1]); + + Two_Product(bdx, cdy, bdxcdy1, bdxcdy0); + Two_Product(cdx, bdy, cdxbdy1, cdxbdy0); + Two_Two_Diff(bdxcdy1, bdxcdy0, cdxbdy1, cdxbdy0, bc3, bc[2], bc[1], bc[0]); + bc[3] = bc3; + axbclen = scale_expansion_zeroelim(4, bc, adx, axbc); + axxbclen = scale_expansion_zeroelim(axbclen, axbc, adx, axxbc); + aybclen = scale_expansion_zeroelim(4, bc, ady, aybc); + ayybclen = scale_expansion_zeroelim(aybclen, aybc, ady, ayybc); + alen = fast_expansion_sum_zeroelim(axxbclen, axxbc, ayybclen, ayybc, adet); + + Two_Product(cdx, ady, cdxady1, cdxady0); + Two_Product(adx, cdy, adxcdy1, adxcdy0); + Two_Two_Diff(cdxady1, cdxady0, adxcdy1, adxcdy0, ca3, ca[2], ca[1], ca[0]); + ca[3] = ca3; + bxcalen = scale_expansion_zeroelim(4, ca, bdx, bxca); + bxxcalen = scale_expansion_zeroelim(bxcalen, bxca, bdx, bxxca); + bycalen = scale_expansion_zeroelim(4, ca, bdy, byca); + byycalen = scale_expansion_zeroelim(bycalen, byca, bdy, byyca); + blen = fast_expansion_sum_zeroelim(bxxcalen, bxxca, byycalen, byyca, bdet); + + Two_Product(adx, bdy, adxbdy1, adxbdy0); + Two_Product(bdx, ady, bdxady1, bdxady0); + Two_Two_Diff(adxbdy1, adxbdy0, bdxady1, bdxady0, ab3, ab[2], ab[1], ab[0]); + ab[3] = ab3; + cxablen = scale_expansion_zeroelim(4, ab, cdx, cxab); + cxxablen = scale_expansion_zeroelim(cxablen, cxab, cdx, cxxab); + cyablen = scale_expansion_zeroelim(4, ab, cdy, cyab); + cyyablen = scale_expansion_zeroelim(cyablen, cyab, cdy, cyyab); + clen = fast_expansion_sum_zeroelim(cxxablen, cxxab, cyyablen, cyyab, cdet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + finlength = fast_expansion_sum_zeroelim(ablen, abdet, clen, cdet, fin1); + + det = estimate(finlength, fin1); + errbound = iccerrboundB * permanent; + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + Two_Diff_Tail(pa[0], pd[0], adx, adxtail); + Two_Diff_Tail(pa[1], pd[1], ady, adytail); + Two_Diff_Tail(pb[0], pd[0], bdx, bdxtail); + Two_Diff_Tail(pb[1], pd[1], bdy, bdytail); + Two_Diff_Tail(pc[0], pd[0], cdx, cdxtail); + Two_Diff_Tail(pc[1], pd[1], cdy, cdytail); + if ((adxtail == 0.0) && (bdxtail == 0.0) && (cdxtail == 0.0) + && (adytail == 0.0) && (bdytail == 0.0) && (cdytail == 0.0)) { + return det; + } + + errbound = iccerrboundC * permanent + resulterrbound * Absolute(det); + det += ((adx * adx + ady * ady) * ((bdx * cdytail + cdy * bdxtail) - (bdy * cdxtail + cdx * bdytail)) + + 2.0 * (adx * adxtail + ady * adytail) * (bdx * cdy - bdy * cdx)) + + ((bdx * bdx + bdy * bdy) * ((cdx * adytail + ady * cdxtail) - (cdy * adxtail + adx * cdytail)) + + 2.0 * (bdx * bdxtail + bdy * bdytail) * (cdx * ady - cdy * adx)) + + ((cdx * cdx + cdy * cdy) * ((adx * bdytail + bdy * adxtail) - (ady * bdxtail + bdx * adytail)) + + 2.0 * (cdx * cdxtail + cdy * cdytail) * (adx * bdy - ady * bdx)); + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + finnow = fin1; + finother = fin2; + + if ((bdxtail != 0.0) || (bdytail != 0.0) + || (cdxtail != 0.0) || (cdytail != 0.0)) { + Square(adx, adxadx1, adxadx0); + Square(ady, adyady1, adyady0); + Two_Two_Sum(adxadx1, adxadx0, adyady1, adyady0, aa3, aa[2], aa[1], aa[0]); + aa[3] = aa3; + } + if ((cdxtail != 0.0) || (cdytail != 0.0) + || (adxtail != 0.0) || (adytail != 0.0)) { + Square(bdx, bdxbdx1, bdxbdx0); + Square(bdy, bdybdy1, bdybdy0); + Two_Two_Sum(bdxbdx1, bdxbdx0, bdybdy1, bdybdy0, bb3, bb[2], bb[1], bb[0]); + bb[3] = bb3; + } + if ((adxtail != 0.0) || (adytail != 0.0) + || (bdxtail != 0.0) || (bdytail != 0.0)) { + Square(cdx, cdxcdx1, cdxcdx0); + Square(cdy, cdycdy1, cdycdy0); + Two_Two_Sum(cdxcdx1, cdxcdx0, cdycdy1, cdycdy0, cc3, cc[2], cc[1], cc[0]); + cc[3] = cc3; + } + + if (adxtail != 0.0) { + axtbclen = scale_expansion_zeroelim(4, bc, adxtail, axtbc); + temp16alen = scale_expansion_zeroelim(axtbclen, axtbc, 2.0 * adx, + temp16a); + + axtcclen = scale_expansion_zeroelim(4, cc, adxtail, axtcc); + temp16blen = scale_expansion_zeroelim(axtcclen, axtcc, bdy, temp16b); + + axtbblen = scale_expansion_zeroelim(4, bb, adxtail, axtbb); + temp16clen = scale_expansion_zeroelim(axtbblen, axtbb, -cdy, temp16c); + + temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; + finnow = finother; + finother = finswap; + } + if (adytail != 0.0) { + aytbclen = scale_expansion_zeroelim(4, bc, adytail, aytbc); + temp16alen = scale_expansion_zeroelim(aytbclen, aytbc, 2.0 * ady, + temp16a); + + aytbblen = scale_expansion_zeroelim(4, bb, adytail, aytbb); + temp16blen = scale_expansion_zeroelim(aytbblen, aytbb, cdx, temp16b); + + aytcclen = scale_expansion_zeroelim(4, cc, adytail, aytcc); + temp16clen = scale_expansion_zeroelim(aytcclen, aytcc, -bdx, temp16c); + + temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; + finnow = finother; + finother = finswap; + } + if (bdxtail != 0.0) { + bxtcalen = scale_expansion_zeroelim(4, ca, bdxtail, bxtca); + temp16alen = scale_expansion_zeroelim(bxtcalen, bxtca, 2.0 * bdx, + temp16a); + + bxtaalen = scale_expansion_zeroelim(4, aa, bdxtail, bxtaa); + temp16blen = scale_expansion_zeroelim(bxtaalen, bxtaa, cdy, temp16b); + + bxtcclen = scale_expansion_zeroelim(4, cc, bdxtail, bxtcc); + temp16clen = scale_expansion_zeroelim(bxtcclen, bxtcc, -ady, temp16c); + + temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; + finnow = finother; + finother = finswap; + } + if (bdytail != 0.0) { + bytcalen = scale_expansion_zeroelim(4, ca, bdytail, bytca); + temp16alen = scale_expansion_zeroelim(bytcalen, bytca, 2.0 * bdy, + temp16a); + + bytcclen = scale_expansion_zeroelim(4, cc, bdytail, bytcc); + temp16blen = scale_expansion_zeroelim(bytcclen, bytcc, adx, temp16b); + + bytaalen = scale_expansion_zeroelim(4, aa, bdytail, bytaa); + temp16clen = scale_expansion_zeroelim(bytaalen, bytaa, -cdx, temp16c); + + temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; + finnow = finother; + finother = finswap; + } + if (cdxtail != 0.0) { + cxtablen = scale_expansion_zeroelim(4, ab, cdxtail, cxtab); + temp16alen = scale_expansion_zeroelim(cxtablen, cxtab, 2.0 * cdx, + temp16a); + + cxtbblen = scale_expansion_zeroelim(4, bb, cdxtail, cxtbb); + temp16blen = scale_expansion_zeroelim(cxtbblen, cxtbb, ady, temp16b); + + cxtaalen = scale_expansion_zeroelim(4, aa, cdxtail, cxtaa); + temp16clen = scale_expansion_zeroelim(cxtaalen, cxtaa, -bdy, temp16c); + + temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; + finnow = finother; + finother = finswap; + } + if (cdytail != 0.0) { + cytablen = scale_expansion_zeroelim(4, ab, cdytail, cytab); + temp16alen = scale_expansion_zeroelim(cytablen, cytab, 2.0 * cdy, + temp16a); + + cytaalen = scale_expansion_zeroelim(4, aa, cdytail, cytaa); + temp16blen = scale_expansion_zeroelim(cytaalen, cytaa, bdx, temp16b); + + cytbblen = scale_expansion_zeroelim(4, bb, cdytail, cytbb); + temp16clen = scale_expansion_zeroelim(cytbblen, cytbb, -adx, temp16c); + + temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; + finnow = finother; + finother = finswap; + } + + if ((adxtail != 0.0) || (adytail != 0.0)) { + if ((bdxtail != 0.0) || (bdytail != 0.0) + || (cdxtail != 0.0) || (cdytail != 0.0)) { + Two_Product(bdxtail, cdy, ti1, ti0); + Two_Product(bdx, cdytail, tj1, tj0); + Two_Two_Sum(ti1, ti0, tj1, tj0, u3, u[2], u[1], u[0]); + u[3] = u3; + negate = -bdy; + Two_Product(cdxtail, negate, ti1, ti0); + negate = -bdytail; + Two_Product(cdx, negate, tj1, tj0); + Two_Two_Sum(ti1, ti0, tj1, tj0, v3, v[2], v[1], v[0]); + v[3] = v3; + bctlen = fast_expansion_sum_zeroelim(4, u, 4, v, bct); + + Two_Product(bdxtail, cdytail, ti1, ti0); + Two_Product(cdxtail, bdytail, tj1, tj0); + Two_Two_Diff(ti1, ti0, tj1, tj0, bctt3, bctt[2], bctt[1], bctt[0]); + bctt[3] = bctt3; + bcttlen = 4; + } else { + bct[0] = 0.0; + bctlen = 1; + bctt[0] = 0.0; + bcttlen = 1; + } + + if (adxtail != 0.0) { + temp16alen = scale_expansion_zeroelim(axtbclen, axtbc, adxtail, temp16a); + axtbctlen = scale_expansion_zeroelim(bctlen, bct, adxtail, axtbct); + temp32alen = scale_expansion_zeroelim(axtbctlen, axtbct, 2.0 * adx, + temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; + finnow = finother; + finother = finswap; + if (bdytail != 0.0) { + temp8len = scale_expansion_zeroelim(4, cc, adxtail, temp8); + temp16alen = scale_expansion_zeroelim(temp8len, temp8, bdytail, + temp16a); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, + temp16a, finother); + finswap = finnow; + finnow = finother; + finother = finswap; + } + if (cdytail != 0.0) { + temp8len = scale_expansion_zeroelim(4, bb, -adxtail, temp8); + temp16alen = scale_expansion_zeroelim(temp8len, temp8, cdytail, + temp16a); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, + temp16a, finother); + finswap = finnow; + finnow = finother; + finother = finswap; + } + + temp32alen = scale_expansion_zeroelim(axtbctlen, axtbct, adxtail, + temp32a); + axtbcttlen = scale_expansion_zeroelim(bcttlen, bctt, adxtail, axtbctt); + temp16alen = scale_expansion_zeroelim(axtbcttlen, axtbctt, 2.0 * adx, + temp16a); + temp16blen = scale_expansion_zeroelim(axtbcttlen, axtbctt, adxtail, + temp16b); + temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32b); + temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, + temp64, finother); + finswap = finnow; + finnow = finother; + finother = finswap; + } + if (adytail != 0.0) { + temp16alen = scale_expansion_zeroelim(aytbclen, aytbc, adytail, temp16a); + aytbctlen = scale_expansion_zeroelim(bctlen, bct, adytail, aytbct); + temp32alen = scale_expansion_zeroelim(aytbctlen, aytbct, 2.0 * ady, + temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; + finnow = finother; + finother = finswap; + + temp32alen = scale_expansion_zeroelim(aytbctlen, aytbct, adytail, + temp32a); + aytbcttlen = scale_expansion_zeroelim(bcttlen, bctt, adytail, aytbctt); + temp16alen = scale_expansion_zeroelim(aytbcttlen, aytbctt, 2.0 * ady, + temp16a); + temp16blen = scale_expansion_zeroelim(aytbcttlen, aytbctt, adytail, + temp16b); + temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32b); + temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, + temp64, finother); + finswap = finnow; + finnow = finother; + finother = finswap; + } + } + if ((bdxtail != 0.0) || (bdytail != 0.0)) { + if ((cdxtail != 0.0) || (cdytail != 0.0) + || (adxtail != 0.0) || (adytail != 0.0)) { + Two_Product(cdxtail, ady, ti1, ti0); + Two_Product(cdx, adytail, tj1, tj0); + Two_Two_Sum(ti1, ti0, tj1, tj0, u3, u[2], u[1], u[0]); + u[3] = u3; + negate = -cdy; + Two_Product(adxtail, negate, ti1, ti0); + negate = -cdytail; + Two_Product(adx, negate, tj1, tj0); + Two_Two_Sum(ti1, ti0, tj1, tj0, v3, v[2], v[1], v[0]); + v[3] = v3; + catlen = fast_expansion_sum_zeroelim(4, u, 4, v, cat); + + Two_Product(cdxtail, adytail, ti1, ti0); + Two_Product(adxtail, cdytail, tj1, tj0); + Two_Two_Diff(ti1, ti0, tj1, tj0, catt3, catt[2], catt[1], catt[0]); + catt[3] = catt3; + cattlen = 4; + } else { + cat[0] = 0.0; + catlen = 1; + catt[0] = 0.0; + cattlen = 1; + } + + if (bdxtail != 0.0) { + temp16alen = scale_expansion_zeroelim(bxtcalen, bxtca, bdxtail, temp16a); + bxtcatlen = scale_expansion_zeroelim(catlen, cat, bdxtail, bxtcat); + temp32alen = scale_expansion_zeroelim(bxtcatlen, bxtcat, 2.0 * bdx, + temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; + finnow = finother; + finother = finswap; + if (cdytail != 0.0) { + temp8len = scale_expansion_zeroelim(4, aa, bdxtail, temp8); + temp16alen = scale_expansion_zeroelim(temp8len, temp8, cdytail, + temp16a); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, + temp16a, finother); + finswap = finnow; + finnow = finother; + finother = finswap; + } + if (adytail != 0.0) { + temp8len = scale_expansion_zeroelim(4, cc, -bdxtail, temp8); + temp16alen = scale_expansion_zeroelim(temp8len, temp8, adytail, + temp16a); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, + temp16a, finother); + finswap = finnow; + finnow = finother; + finother = finswap; + } + + temp32alen = scale_expansion_zeroelim(bxtcatlen, bxtcat, bdxtail, + temp32a); + bxtcattlen = scale_expansion_zeroelim(cattlen, catt, bdxtail, bxtcatt); + temp16alen = scale_expansion_zeroelim(bxtcattlen, bxtcatt, 2.0 * bdx, + temp16a); + temp16blen = scale_expansion_zeroelim(bxtcattlen, bxtcatt, bdxtail, + temp16b); + temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32b); + temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, + temp64, finother); + finswap = finnow; + finnow = finother; + finother = finswap; + } + if (bdytail != 0.0) { + temp16alen = scale_expansion_zeroelim(bytcalen, bytca, bdytail, temp16a); + bytcatlen = scale_expansion_zeroelim(catlen, cat, bdytail, bytcat); + temp32alen = scale_expansion_zeroelim(bytcatlen, bytcat, 2.0 * bdy, + temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; + finnow = finother; + finother = finswap; + + temp32alen = scale_expansion_zeroelim(bytcatlen, bytcat, bdytail, + temp32a); + bytcattlen = scale_expansion_zeroelim(cattlen, catt, bdytail, bytcatt); + temp16alen = scale_expansion_zeroelim(bytcattlen, bytcatt, 2.0 * bdy, + temp16a); + temp16blen = scale_expansion_zeroelim(bytcattlen, bytcatt, bdytail, + temp16b); + temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32b); + temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, + temp64, finother); + finswap = finnow; + finnow = finother; + finother = finswap; + } + } + if ((cdxtail != 0.0) || (cdytail != 0.0)) { + if ((adxtail != 0.0) || (adytail != 0.0) + || (bdxtail != 0.0) || (bdytail != 0.0)) { + Two_Product(adxtail, bdy, ti1, ti0); + Two_Product(adx, bdytail, tj1, tj0); + Two_Two_Sum(ti1, ti0, tj1, tj0, u3, u[2], u[1], u[0]); + u[3] = u3; + negate = -ady; + Two_Product(bdxtail, negate, ti1, ti0); + negate = -adytail; + Two_Product(bdx, negate, tj1, tj0); + Two_Two_Sum(ti1, ti0, tj1, tj0, v3, v[2], v[1], v[0]); + v[3] = v3; + abtlen = fast_expansion_sum_zeroelim(4, u, 4, v, abt); + + Two_Product(adxtail, bdytail, ti1, ti0); + Two_Product(bdxtail, adytail, tj1, tj0); + Two_Two_Diff(ti1, ti0, tj1, tj0, abtt3, abtt[2], abtt[1], abtt[0]); + abtt[3] = abtt3; + abttlen = 4; + } else { + abt[0] = 0.0; + abtlen = 1; + abtt[0] = 0.0; + abttlen = 1; + } + + if (cdxtail != 0.0) { + temp16alen = scale_expansion_zeroelim(cxtablen, cxtab, cdxtail, temp16a); + cxtabtlen = scale_expansion_zeroelim(abtlen, abt, cdxtail, cxtabt); + temp32alen = scale_expansion_zeroelim(cxtabtlen, cxtabt, 2.0 * cdx, + temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; + finnow = finother; + finother = finswap; + if (adytail != 0.0) { + temp8len = scale_expansion_zeroelim(4, bb, cdxtail, temp8); + temp16alen = scale_expansion_zeroelim(temp8len, temp8, adytail, + temp16a); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, + temp16a, finother); + finswap = finnow; + finnow = finother; + finother = finswap; + } + if (bdytail != 0.0) { + temp8len = scale_expansion_zeroelim(4, aa, -cdxtail, temp8); + temp16alen = scale_expansion_zeroelim(temp8len, temp8, bdytail, + temp16a); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, + temp16a, finother); + finswap = finnow; + finnow = finother; + finother = finswap; + } + + temp32alen = scale_expansion_zeroelim(cxtabtlen, cxtabt, cdxtail, + temp32a); + cxtabttlen = scale_expansion_zeroelim(abttlen, abtt, cdxtail, cxtabtt); + temp16alen = scale_expansion_zeroelim(cxtabttlen, cxtabtt, 2.0 * cdx, + temp16a); + temp16blen = scale_expansion_zeroelim(cxtabttlen, cxtabtt, cdxtail, + temp16b); + temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32b); + temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, + temp64, finother); + finswap = finnow; + finnow = finother; + finother = finswap; + } + if (cdytail != 0.0) { + temp16alen = scale_expansion_zeroelim(cytablen, cytab, cdytail, temp16a); + cytabtlen = scale_expansion_zeroelim(abtlen, abt, cdytail, cytabt); + temp32alen = scale_expansion_zeroelim(cytabtlen, cytabt, 2.0 * cdy, + temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; + finnow = finother; + finother = finswap; + + temp32alen = scale_expansion_zeroelim(cytabtlen, cytabt, cdytail, + temp32a); + cytabttlen = scale_expansion_zeroelim(abttlen, abtt, cdytail, cytabtt); + temp16alen = scale_expansion_zeroelim(cytabttlen, cytabtt, 2.0 * cdy, + temp16a); + temp16blen = scale_expansion_zeroelim(cytabttlen, cytabtt, cdytail, + temp16b); + temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32b); + temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, + temp64, finother); + finswap = finnow; + finnow = finother; + finother = finswap; + } + } + + return finnow[finlength - 1]; +} + +REAL incircle(pa, pb, pc, pd) + REAL* pa; +REAL* pb; +REAL* pc; +REAL* pd; +{ + REAL adx, bdx, cdx, ady, bdy, cdy; + REAL bdxcdy, cdxbdy, cdxady, adxcdy, adxbdy, bdxady; + REAL alift, blift, clift; + REAL det; + REAL permanent, errbound; + + adx = pa[0] - pd[0]; + bdx = pb[0] - pd[0]; + cdx = pc[0] - pd[0]; + ady = pa[1] - pd[1]; + bdy = pb[1] - pd[1]; + cdy = pc[1] - pd[1]; + + bdxcdy = bdx * cdy; + cdxbdy = cdx * bdy; + alift = adx * adx + ady * ady; + + cdxady = cdx * ady; + adxcdy = adx * cdy; + blift = bdx * bdx + bdy * bdy; + + adxbdy = adx * bdy; + bdxady = bdx * ady; + clift = cdx * cdx + cdy * cdy; + + det = alift * (bdxcdy - cdxbdy) + + blift * (cdxady - adxcdy) + + clift * (adxbdy - bdxady); + + permanent = (Absolute(bdxcdy) + Absolute(cdxbdy)) * alift + + (Absolute(cdxady) + Absolute(adxcdy)) * blift + + (Absolute(adxbdy) + Absolute(bdxady)) * clift; + errbound = iccerrboundA * permanent; + if ((det > errbound) || (-det > errbound)) { + return det; + } + + return incircleadapt(pa, pb, pc, pd, permanent); +} + +/*****************************************************************************/ +/* */ +/* inspherefast() Approximate 3D insphere test. Nonrobust. */ +/* insphereexact() Exact 3D insphere test. Robust. */ +/* insphereslow() Another exact 3D insphere test. Robust. */ +/* insphere() Adaptive exact 3D insphere test. Robust. */ +/* */ +/* Return a positive value if the point pe lies inside the */ +/* sphere passing through pa, pb, pc, and pd; a negative value */ +/* if it lies outside; and zero if the five points are */ +/* cospherical. The points pa, pb, pc, and pd must be ordered */ +/* so that they have a positive orientation (as defined by */ +/* orient3d()), or the sign of the result will be reversed. */ +/* */ +/* Only the first and last routine should be used; the middle two are for */ +/* timings. */ +/* */ +/* The last three use exact arithmetic to ensure a correct answer. The */ +/* result returned is the determinant of a matrix. In insphere() only, */ +/* this determinant is computed adaptively, in the sense that exact */ +/* arithmetic is used only to the degree it is needed to ensure that the */ +/* returned value has the correct sign. Hence, insphere() is usually quite */ +/* fast, but will run more slowly when the input points are cospherical or */ +/* nearly so. */ +/* */ +/*****************************************************************************/ + +REAL inspherefast(pa, pb, pc, pd, pe) + REAL* pa; +REAL* pb; +REAL* pc; +REAL* pd; +REAL* pe; +{ + REAL aex, bex, cex, dex; + REAL aey, bey, cey, dey; + REAL aez, bez, cez, dez; + REAL alift, blift, clift, dlift; + REAL ab, bc, cd, da, ac, bd; + REAL abc, bcd, cda, dab; + + aex = pa[0] - pe[0]; + bex = pb[0] - pe[0]; + cex = pc[0] - pe[0]; + dex = pd[0] - pe[0]; + aey = pa[1] - pe[1]; + bey = pb[1] - pe[1]; + cey = pc[1] - pe[1]; + dey = pd[1] - pe[1]; + aez = pa[2] - pe[2]; + bez = pb[2] - pe[2]; + cez = pc[2] - pe[2]; + dez = pd[2] - pe[2]; + + ab = aex * bey - bex * aey; + bc = bex * cey - cex * bey; + cd = cex * dey - dex * cey; + da = dex * aey - aex * dey; + + ac = aex * cey - cex * aey; + bd = bex * dey - dex * bey; + + abc = aez * bc - bez * ac + cez * ab; + bcd = bez * cd - cez * bd + dez * bc; + cda = cez * da + dez * ac + aez * cd; + dab = dez * ab + aez * bd + bez * da; + + alift = aex * aex + aey * aey + aez * aez; + blift = bex * bex + bey * bey + bez * bez; + clift = cex * cex + cey * cey + cez * cez; + dlift = dex * dex + dey * dey + dez * dez; + + return (dlift * abc - clift * dab) + (blift * cda - alift * bcd); +} + +REAL insphereexact(pa, pb, pc, pd, pe) + REAL* pa; +REAL* pb; +REAL* pc; +REAL* pd; +REAL* pe; +{ + INEXACT REAL axby1, bxcy1, cxdy1, dxey1, exay1; + INEXACT REAL bxay1, cxby1, dxcy1, exdy1, axey1; + INEXACT REAL axcy1, bxdy1, cxey1, dxay1, exby1; + INEXACT REAL cxay1, dxby1, excy1, axdy1, bxey1; + REAL axby0, bxcy0, cxdy0, dxey0, exay0; + REAL bxay0, cxby0, dxcy0, exdy0, axey0; + REAL axcy0, bxdy0, cxey0, dxay0, exby0; + REAL cxay0, dxby0, excy0, axdy0, bxey0; + REAL ab[4], bc[4], cd[4], de[4], ea[4]; + REAL ac[4], bd[4], ce[4], da[4], eb[4]; + REAL temp8a[8], temp8b[8], temp16[16]; + int temp8alen, temp8blen, temp16len; + REAL abc[24], bcd[24], cde[24], dea[24], eab[24]; + REAL abd[24], bce[24], cda[24], deb[24], eac[24]; + int abclen, bcdlen, cdelen, dealen, eablen; + int abdlen, bcelen, cdalen, deblen, eaclen; + REAL temp48a[48], temp48b[48]; + int temp48alen, temp48blen; + REAL abcd[96], bcde[96], cdea[96], deab[96], eabc[96]; + int abcdlen, bcdelen, cdealen, deablen, eabclen; + REAL temp192[192]; + REAL det384x[384], det384y[384], det384z[384]; + int xlen, ylen, zlen; + REAL detxy[768]; + int xylen; + REAL adet[1152], bdet[1152], cdet[1152], ddet[1152], edet[1152]; + int alen, blen, clen, dlen, elen; + REAL abdet[2304], cddet[2304], cdedet[3456]; + int ablen, cdlen; + REAL deter[5760]; + int deterlen; + int i; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j; + REAL _0; + + Two_Product(pa[0], pb[1], axby1, axby0); + Two_Product(pb[0], pa[1], bxay1, bxay0); + Two_Two_Diff(axby1, axby0, bxay1, bxay0, ab[3], ab[2], ab[1], ab[0]); + + Two_Product(pb[0], pc[1], bxcy1, bxcy0); + Two_Product(pc[0], pb[1], cxby1, cxby0); + Two_Two_Diff(bxcy1, bxcy0, cxby1, cxby0, bc[3], bc[2], bc[1], bc[0]); + + Two_Product(pc[0], pd[1], cxdy1, cxdy0); + Two_Product(pd[0], pc[1], dxcy1, dxcy0); + Two_Two_Diff(cxdy1, cxdy0, dxcy1, dxcy0, cd[3], cd[2], cd[1], cd[0]); + + Two_Product(pd[0], pe[1], dxey1, dxey0); + Two_Product(pe[0], pd[1], exdy1, exdy0); + Two_Two_Diff(dxey1, dxey0, exdy1, exdy0, de[3], de[2], de[1], de[0]); + + Two_Product(pe[0], pa[1], exay1, exay0); + Two_Product(pa[0], pe[1], axey1, axey0); + Two_Two_Diff(exay1, exay0, axey1, axey0, ea[3], ea[2], ea[1], ea[0]); + + Two_Product(pa[0], pc[1], axcy1, axcy0); + Two_Product(pc[0], pa[1], cxay1, cxay0); + Two_Two_Diff(axcy1, axcy0, cxay1, cxay0, ac[3], ac[2], ac[1], ac[0]); + + Two_Product(pb[0], pd[1], bxdy1, bxdy0); + Two_Product(pd[0], pb[1], dxby1, dxby0); + Two_Two_Diff(bxdy1, bxdy0, dxby1, dxby0, bd[3], bd[2], bd[1], bd[0]); + + Two_Product(pc[0], pe[1], cxey1, cxey0); + Two_Product(pe[0], pc[1], excy1, excy0); + Two_Two_Diff(cxey1, cxey0, excy1, excy0, ce[3], ce[2], ce[1], ce[0]); + + Two_Product(pd[0], pa[1], dxay1, dxay0); + Two_Product(pa[0], pd[1], axdy1, axdy0); + Two_Two_Diff(dxay1, dxay0, axdy1, axdy0, da[3], da[2], da[1], da[0]); + + Two_Product(pe[0], pb[1], exby1, exby0); + Two_Product(pb[0], pe[1], bxey1, bxey0); + Two_Two_Diff(exby1, exby0, bxey1, bxey0, eb[3], eb[2], eb[1], eb[0]); + + temp8alen = scale_expansion_zeroelim(4, bc, pa[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, ac, -pb[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, ab, pc[2], temp8a); + abclen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + abc); + + temp8alen = scale_expansion_zeroelim(4, cd, pb[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, bd, -pc[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, bc, pd[2], temp8a); + bcdlen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + bcd); + + temp8alen = scale_expansion_zeroelim(4, de, pc[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, ce, -pd[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, cd, pe[2], temp8a); + cdelen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + cde); + + temp8alen = scale_expansion_zeroelim(4, ea, pd[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, da, -pe[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, de, pa[2], temp8a); + dealen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + dea); + + temp8alen = scale_expansion_zeroelim(4, ab, pe[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, eb, -pa[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, ea, pb[2], temp8a); + eablen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + eab); + + temp8alen = scale_expansion_zeroelim(4, bd, pa[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, da, pb[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, ab, pd[2], temp8a); + abdlen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + abd); + + temp8alen = scale_expansion_zeroelim(4, ce, pb[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, eb, pc[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, bc, pe[2], temp8a); + bcelen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + bce); + + temp8alen = scale_expansion_zeroelim(4, da, pc[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, ac, pd[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, cd, pa[2], temp8a); + cdalen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + cda); + + temp8alen = scale_expansion_zeroelim(4, eb, pd[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, bd, pe[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, de, pb[2], temp8a); + deblen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + deb); + + temp8alen = scale_expansion_zeroelim(4, ac, pe[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, ce, pa[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, ea, pc[2], temp8a); + eaclen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + eac); + + temp48alen = fast_expansion_sum_zeroelim(cdelen, cde, bcelen, bce, temp48a); + temp48blen = fast_expansion_sum_zeroelim(deblen, deb, bcdlen, bcd, temp48b); + for (i = 0; i < temp48blen; i++) { + temp48b[i] = -temp48b[i]; + } + bcdelen = fast_expansion_sum_zeroelim(temp48alen, temp48a, + temp48blen, temp48b, bcde); + xlen = scale_expansion_zeroelim(bcdelen, bcde, pa[0], temp192); + xlen = scale_expansion_zeroelim(xlen, temp192, pa[0], det384x); + ylen = scale_expansion_zeroelim(bcdelen, bcde, pa[1], temp192); + ylen = scale_expansion_zeroelim(ylen, temp192, pa[1], det384y); + zlen = scale_expansion_zeroelim(bcdelen, bcde, pa[2], temp192); + zlen = scale_expansion_zeroelim(zlen, temp192, pa[2], det384z); + xylen = fast_expansion_sum_zeroelim(xlen, det384x, ylen, det384y, detxy); + alen = fast_expansion_sum_zeroelim(xylen, detxy, zlen, det384z, adet); + + temp48alen = fast_expansion_sum_zeroelim(dealen, dea, cdalen, cda, temp48a); + temp48blen = fast_expansion_sum_zeroelim(eaclen, eac, cdelen, cde, temp48b); + for (i = 0; i < temp48blen; i++) { + temp48b[i] = -temp48b[i]; + } + cdealen = fast_expansion_sum_zeroelim(temp48alen, temp48a, + temp48blen, temp48b, cdea); + xlen = scale_expansion_zeroelim(cdealen, cdea, pb[0], temp192); + xlen = scale_expansion_zeroelim(xlen, temp192, pb[0], det384x); + ylen = scale_expansion_zeroelim(cdealen, cdea, pb[1], temp192); + ylen = scale_expansion_zeroelim(ylen, temp192, pb[1], det384y); + zlen = scale_expansion_zeroelim(cdealen, cdea, pb[2], temp192); + zlen = scale_expansion_zeroelim(zlen, temp192, pb[2], det384z); + xylen = fast_expansion_sum_zeroelim(xlen, det384x, ylen, det384y, detxy); + blen = fast_expansion_sum_zeroelim(xylen, detxy, zlen, det384z, bdet); + + temp48alen = fast_expansion_sum_zeroelim(eablen, eab, deblen, deb, temp48a); + temp48blen = fast_expansion_sum_zeroelim(abdlen, abd, dealen, dea, temp48b); + for (i = 0; i < temp48blen; i++) { + temp48b[i] = -temp48b[i]; + } + deablen = fast_expansion_sum_zeroelim(temp48alen, temp48a, + temp48blen, temp48b, deab); + xlen = scale_expansion_zeroelim(deablen, deab, pc[0], temp192); + xlen = scale_expansion_zeroelim(xlen, temp192, pc[0], det384x); + ylen = scale_expansion_zeroelim(deablen, deab, pc[1], temp192); + ylen = scale_expansion_zeroelim(ylen, temp192, pc[1], det384y); + zlen = scale_expansion_zeroelim(deablen, deab, pc[2], temp192); + zlen = scale_expansion_zeroelim(zlen, temp192, pc[2], det384z); + xylen = fast_expansion_sum_zeroelim(xlen, det384x, ylen, det384y, detxy); + clen = fast_expansion_sum_zeroelim(xylen, detxy, zlen, det384z, cdet); + + temp48alen = fast_expansion_sum_zeroelim(abclen, abc, eaclen, eac, temp48a); + temp48blen = fast_expansion_sum_zeroelim(bcelen, bce, eablen, eab, temp48b); + for (i = 0; i < temp48blen; i++) { + temp48b[i] = -temp48b[i]; + } + eabclen = fast_expansion_sum_zeroelim(temp48alen, temp48a, + temp48blen, temp48b, eabc); + xlen = scale_expansion_zeroelim(eabclen, eabc, pd[0], temp192); + xlen = scale_expansion_zeroelim(xlen, temp192, pd[0], det384x); + ylen = scale_expansion_zeroelim(eabclen, eabc, pd[1], temp192); + ylen = scale_expansion_zeroelim(ylen, temp192, pd[1], det384y); + zlen = scale_expansion_zeroelim(eabclen, eabc, pd[2], temp192); + zlen = scale_expansion_zeroelim(zlen, temp192, pd[2], det384z); + xylen = fast_expansion_sum_zeroelim(xlen, det384x, ylen, det384y, detxy); + dlen = fast_expansion_sum_zeroelim(xylen, detxy, zlen, det384z, ddet); + + temp48alen = fast_expansion_sum_zeroelim(bcdlen, bcd, abdlen, abd, temp48a); + temp48blen = fast_expansion_sum_zeroelim(cdalen, cda, abclen, abc, temp48b); + for (i = 0; i < temp48blen; i++) { + temp48b[i] = -temp48b[i]; + } + abcdlen = fast_expansion_sum_zeroelim(temp48alen, temp48a, + temp48blen, temp48b, abcd); + xlen = scale_expansion_zeroelim(abcdlen, abcd, pe[0], temp192); + xlen = scale_expansion_zeroelim(xlen, temp192, pe[0], det384x); + ylen = scale_expansion_zeroelim(abcdlen, abcd, pe[1], temp192); + ylen = scale_expansion_zeroelim(ylen, temp192, pe[1], det384y); + zlen = scale_expansion_zeroelim(abcdlen, abcd, pe[2], temp192); + zlen = scale_expansion_zeroelim(zlen, temp192, pe[2], det384z); + xylen = fast_expansion_sum_zeroelim(xlen, det384x, ylen, det384y, detxy); + elen = fast_expansion_sum_zeroelim(xylen, detxy, zlen, det384z, edet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + cdlen = fast_expansion_sum_zeroelim(clen, cdet, dlen, ddet, cddet); + cdelen = fast_expansion_sum_zeroelim(cdlen, cddet, elen, edet, cdedet); + deterlen = fast_expansion_sum_zeroelim(ablen, abdet, cdelen, cdedet, deter); + + return deter[deterlen - 1]; +} + +REAL insphereslow(pa, pb, pc, pd, pe) + REAL* pa; +REAL* pb; +REAL* pc; +REAL* pd; +REAL* pe; +{ + INEXACT REAL aex, bex, cex, dex, aey, bey, cey, dey, aez, bez, cez, dez; + REAL aextail, bextail, cextail, dextail; + REAL aeytail, beytail, ceytail, deytail; + REAL aeztail, beztail, ceztail, deztail; + REAL negate, negatetail; + INEXACT REAL axby7, bxcy7, cxdy7, dxay7, axcy7, bxdy7; + INEXACT REAL bxay7, cxby7, dxcy7, axdy7, cxay7, dxby7; + REAL axby[8], bxcy[8], cxdy[8], dxay[8], axcy[8], bxdy[8]; + REAL bxay[8], cxby[8], dxcy[8], axdy[8], cxay[8], dxby[8]; + REAL ab[16], bc[16], cd[16], da[16], ac[16], bd[16]; + int ablen, bclen, cdlen, dalen, aclen, bdlen; + REAL temp32a[32], temp32b[32], temp64a[64], temp64b[64], temp64c[64]; + int temp32alen, temp32blen, temp64alen, temp64blen, temp64clen; + REAL temp128[128], temp192[192]; + int temp128len, temp192len; + REAL detx[384], detxx[768], detxt[384], detxxt[768], detxtxt[768]; + int xlen, xxlen, xtlen, xxtlen, xtxtlen; + REAL x1[1536], x2[2304]; + int x1len, x2len; + REAL dety[384], detyy[768], detyt[384], detyyt[768], detytyt[768]; + int ylen, yylen, ytlen, yytlen, ytytlen; + REAL y1[1536], y2[2304]; + int y1len, y2len; + REAL detz[384], detzz[768], detzt[384], detzzt[768], detztzt[768]; + int zlen, zzlen, ztlen, zztlen, ztztlen; + REAL z1[1536], z2[2304]; + int z1len, z2len; + REAL detxy[4608]; + int xylen; + REAL adet[6912], bdet[6912], cdet[6912], ddet[6912]; + int alen, blen, clen, dlen; + REAL abdet[13824], cddet[13824], deter[27648]; + int deterlen; + int i; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL a0hi, a0lo, a1hi, a1lo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j, _k, _l, _m, _n; + REAL _0, _1, _2; + + Two_Diff(pa[0], pe[0], aex, aextail); + Two_Diff(pa[1], pe[1], aey, aeytail); + Two_Diff(pa[2], pe[2], aez, aeztail); + Two_Diff(pb[0], pe[0], bex, bextail); + Two_Diff(pb[1], pe[1], bey, beytail); + Two_Diff(pb[2], pe[2], bez, beztail); + Two_Diff(pc[0], pe[0], cex, cextail); + Two_Diff(pc[1], pe[1], cey, ceytail); + Two_Diff(pc[2], pe[2], cez, ceztail); + Two_Diff(pd[0], pe[0], dex, dextail); + Two_Diff(pd[1], pe[1], dey, deytail); + Two_Diff(pd[2], pe[2], dez, deztail); + + Two_Two_Product(aex, aextail, bey, beytail, + axby7, axby[6], axby[5], axby[4], + axby[3], axby[2], axby[1], axby[0]); + axby[7] = axby7; + negate = -aey; + negatetail = -aeytail; + Two_Two_Product(bex, bextail, negate, negatetail, + bxay7, bxay[6], bxay[5], bxay[4], + bxay[3], bxay[2], bxay[1], bxay[0]); + bxay[7] = bxay7; + ablen = fast_expansion_sum_zeroelim(8, axby, 8, bxay, ab); + Two_Two_Product(bex, bextail, cey, ceytail, + bxcy7, bxcy[6], bxcy[5], bxcy[4], + bxcy[3], bxcy[2], bxcy[1], bxcy[0]); + bxcy[7] = bxcy7; + negate = -bey; + negatetail = -beytail; + Two_Two_Product(cex, cextail, negate, negatetail, + cxby7, cxby[6], cxby[5], cxby[4], + cxby[3], cxby[2], cxby[1], cxby[0]); + cxby[7] = cxby7; + bclen = fast_expansion_sum_zeroelim(8, bxcy, 8, cxby, bc); + Two_Two_Product(cex, cextail, dey, deytail, + cxdy7, cxdy[6], cxdy[5], cxdy[4], + cxdy[3], cxdy[2], cxdy[1], cxdy[0]); + cxdy[7] = cxdy7; + negate = -cey; + negatetail = -ceytail; + Two_Two_Product(dex, dextail, negate, negatetail, + dxcy7, dxcy[6], dxcy[5], dxcy[4], + dxcy[3], dxcy[2], dxcy[1], dxcy[0]); + dxcy[7] = dxcy7; + cdlen = fast_expansion_sum_zeroelim(8, cxdy, 8, dxcy, cd); + Two_Two_Product(dex, dextail, aey, aeytail, + dxay7, dxay[6], dxay[5], dxay[4], + dxay[3], dxay[2], dxay[1], dxay[0]); + dxay[7] = dxay7; + negate = -dey; + negatetail = -deytail; + Two_Two_Product(aex, aextail, negate, negatetail, + axdy7, axdy[6], axdy[5], axdy[4], + axdy[3], axdy[2], axdy[1], axdy[0]); + axdy[7] = axdy7; + dalen = fast_expansion_sum_zeroelim(8, dxay, 8, axdy, da); + Two_Two_Product(aex, aextail, cey, ceytail, + axcy7, axcy[6], axcy[5], axcy[4], + axcy[3], axcy[2], axcy[1], axcy[0]); + axcy[7] = axcy7; + negate = -aey; + negatetail = -aeytail; + Two_Two_Product(cex, cextail, negate, negatetail, + cxay7, cxay[6], cxay[5], cxay[4], + cxay[3], cxay[2], cxay[1], cxay[0]); + cxay[7] = cxay7; + aclen = fast_expansion_sum_zeroelim(8, axcy, 8, cxay, ac); + Two_Two_Product(bex, bextail, dey, deytail, + bxdy7, bxdy[6], bxdy[5], bxdy[4], + bxdy[3], bxdy[2], bxdy[1], bxdy[0]); + bxdy[7] = bxdy7; + negate = -bey; + negatetail = -beytail; + Two_Two_Product(dex, dextail, negate, negatetail, + dxby7, dxby[6], dxby[5], dxby[4], + dxby[3], dxby[2], dxby[1], dxby[0]); + dxby[7] = dxby7; + bdlen = fast_expansion_sum_zeroelim(8, bxdy, 8, dxby, bd); + + temp32alen = scale_expansion_zeroelim(cdlen, cd, -bez, temp32a); + temp32blen = scale_expansion_zeroelim(cdlen, cd, -beztail, temp32b); + temp64alen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64a); + temp32alen = scale_expansion_zeroelim(bdlen, bd, cez, temp32a); + temp32blen = scale_expansion_zeroelim(bdlen, bd, ceztail, temp32b); + temp64blen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64b); + temp32alen = scale_expansion_zeroelim(bclen, bc, -dez, temp32a); + temp32blen = scale_expansion_zeroelim(bclen, bc, -deztail, temp32b); + temp64clen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64c); + temp128len = fast_expansion_sum_zeroelim(temp64alen, temp64a, + temp64blen, temp64b, temp128); + temp192len = fast_expansion_sum_zeroelim(temp64clen, temp64c, + temp128len, temp128, temp192); + xlen = scale_expansion_zeroelim(temp192len, temp192, aex, detx); + xxlen = scale_expansion_zeroelim(xlen, detx, aex, detxx); + xtlen = scale_expansion_zeroelim(temp192len, temp192, aextail, detxt); + xxtlen = scale_expansion_zeroelim(xtlen, detxt, aex, detxxt); + for (i = 0; i < xxtlen; i++) { + detxxt[i] *= 2.0; + } + xtxtlen = scale_expansion_zeroelim(xtlen, detxt, aextail, detxtxt); + x1len = fast_expansion_sum_zeroelim(xxlen, detxx, xxtlen, detxxt, x1); + x2len = fast_expansion_sum_zeroelim(x1len, x1, xtxtlen, detxtxt, x2); + ylen = scale_expansion_zeroelim(temp192len, temp192, aey, dety); + yylen = scale_expansion_zeroelim(ylen, dety, aey, detyy); + ytlen = scale_expansion_zeroelim(temp192len, temp192, aeytail, detyt); + yytlen = scale_expansion_zeroelim(ytlen, detyt, aey, detyyt); + for (i = 0; i < yytlen; i++) { + detyyt[i] *= 2.0; + } + ytytlen = scale_expansion_zeroelim(ytlen, detyt, aeytail, detytyt); + y1len = fast_expansion_sum_zeroelim(yylen, detyy, yytlen, detyyt, y1); + y2len = fast_expansion_sum_zeroelim(y1len, y1, ytytlen, detytyt, y2); + zlen = scale_expansion_zeroelim(temp192len, temp192, aez, detz); + zzlen = scale_expansion_zeroelim(zlen, detz, aez, detzz); + ztlen = scale_expansion_zeroelim(temp192len, temp192, aeztail, detzt); + zztlen = scale_expansion_zeroelim(ztlen, detzt, aez, detzzt); + for (i = 0; i < zztlen; i++) { + detzzt[i] *= 2.0; + } + ztztlen = scale_expansion_zeroelim(ztlen, detzt, aeztail, detztzt); + z1len = fast_expansion_sum_zeroelim(zzlen, detzz, zztlen, detzzt, z1); + z2len = fast_expansion_sum_zeroelim(z1len, z1, ztztlen, detztzt, z2); + xylen = fast_expansion_sum_zeroelim(x2len, x2, y2len, y2, detxy); + alen = fast_expansion_sum_zeroelim(z2len, z2, xylen, detxy, adet); + + temp32alen = scale_expansion_zeroelim(dalen, da, cez, temp32a); + temp32blen = scale_expansion_zeroelim(dalen, da, ceztail, temp32b); + temp64alen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64a); + temp32alen = scale_expansion_zeroelim(aclen, ac, dez, temp32a); + temp32blen = scale_expansion_zeroelim(aclen, ac, deztail, temp32b); + temp64blen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64b); + temp32alen = scale_expansion_zeroelim(cdlen, cd, aez, temp32a); + temp32blen = scale_expansion_zeroelim(cdlen, cd, aeztail, temp32b); + temp64clen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64c); + temp128len = fast_expansion_sum_zeroelim(temp64alen, temp64a, + temp64blen, temp64b, temp128); + temp192len = fast_expansion_sum_zeroelim(temp64clen, temp64c, + temp128len, temp128, temp192); + xlen = scale_expansion_zeroelim(temp192len, temp192, bex, detx); + xxlen = scale_expansion_zeroelim(xlen, detx, bex, detxx); + xtlen = scale_expansion_zeroelim(temp192len, temp192, bextail, detxt); + xxtlen = scale_expansion_zeroelim(xtlen, detxt, bex, detxxt); + for (i = 0; i < xxtlen; i++) { + detxxt[i] *= 2.0; + } + xtxtlen = scale_expansion_zeroelim(xtlen, detxt, bextail, detxtxt); + x1len = fast_expansion_sum_zeroelim(xxlen, detxx, xxtlen, detxxt, x1); + x2len = fast_expansion_sum_zeroelim(x1len, x1, xtxtlen, detxtxt, x2); + ylen = scale_expansion_zeroelim(temp192len, temp192, bey, dety); + yylen = scale_expansion_zeroelim(ylen, dety, bey, detyy); + ytlen = scale_expansion_zeroelim(temp192len, temp192, beytail, detyt); + yytlen = scale_expansion_zeroelim(ytlen, detyt, bey, detyyt); + for (i = 0; i < yytlen; i++) { + detyyt[i] *= 2.0; + } + ytytlen = scale_expansion_zeroelim(ytlen, detyt, beytail, detytyt); + y1len = fast_expansion_sum_zeroelim(yylen, detyy, yytlen, detyyt, y1); + y2len = fast_expansion_sum_zeroelim(y1len, y1, ytytlen, detytyt, y2); + zlen = scale_expansion_zeroelim(temp192len, temp192, bez, detz); + zzlen = scale_expansion_zeroelim(zlen, detz, bez, detzz); + ztlen = scale_expansion_zeroelim(temp192len, temp192, beztail, detzt); + zztlen = scale_expansion_zeroelim(ztlen, detzt, bez, detzzt); + for (i = 0; i < zztlen; i++) { + detzzt[i] *= 2.0; + } + ztztlen = scale_expansion_zeroelim(ztlen, detzt, beztail, detztzt); + z1len = fast_expansion_sum_zeroelim(zzlen, detzz, zztlen, detzzt, z1); + z2len = fast_expansion_sum_zeroelim(z1len, z1, ztztlen, detztzt, z2); + xylen = fast_expansion_sum_zeroelim(x2len, x2, y2len, y2, detxy); + blen = fast_expansion_sum_zeroelim(z2len, z2, xylen, detxy, bdet); + + temp32alen = scale_expansion_zeroelim(ablen, ab, -dez, temp32a); + temp32blen = scale_expansion_zeroelim(ablen, ab, -deztail, temp32b); + temp64alen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64a); + temp32alen = scale_expansion_zeroelim(bdlen, bd, -aez, temp32a); + temp32blen = scale_expansion_zeroelim(bdlen, bd, -aeztail, temp32b); + temp64blen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64b); + temp32alen = scale_expansion_zeroelim(dalen, da, -bez, temp32a); + temp32blen = scale_expansion_zeroelim(dalen, da, -beztail, temp32b); + temp64clen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64c); + temp128len = fast_expansion_sum_zeroelim(temp64alen, temp64a, + temp64blen, temp64b, temp128); + temp192len = fast_expansion_sum_zeroelim(temp64clen, temp64c, + temp128len, temp128, temp192); + xlen = scale_expansion_zeroelim(temp192len, temp192, cex, detx); + xxlen = scale_expansion_zeroelim(xlen, detx, cex, detxx); + xtlen = scale_expansion_zeroelim(temp192len, temp192, cextail, detxt); + xxtlen = scale_expansion_zeroelim(xtlen, detxt, cex, detxxt); + for (i = 0; i < xxtlen; i++) { + detxxt[i] *= 2.0; + } + xtxtlen = scale_expansion_zeroelim(xtlen, detxt, cextail, detxtxt); + x1len = fast_expansion_sum_zeroelim(xxlen, detxx, xxtlen, detxxt, x1); + x2len = fast_expansion_sum_zeroelim(x1len, x1, xtxtlen, detxtxt, x2); + ylen = scale_expansion_zeroelim(temp192len, temp192, cey, dety); + yylen = scale_expansion_zeroelim(ylen, dety, cey, detyy); + ytlen = scale_expansion_zeroelim(temp192len, temp192, ceytail, detyt); + yytlen = scale_expansion_zeroelim(ytlen, detyt, cey, detyyt); + for (i = 0; i < yytlen; i++) { + detyyt[i] *= 2.0; + } + ytytlen = scale_expansion_zeroelim(ytlen, detyt, ceytail, detytyt); + y1len = fast_expansion_sum_zeroelim(yylen, detyy, yytlen, detyyt, y1); + y2len = fast_expansion_sum_zeroelim(y1len, y1, ytytlen, detytyt, y2); + zlen = scale_expansion_zeroelim(temp192len, temp192, cez, detz); + zzlen = scale_expansion_zeroelim(zlen, detz, cez, detzz); + ztlen = scale_expansion_zeroelim(temp192len, temp192, ceztail, detzt); + zztlen = scale_expansion_zeroelim(ztlen, detzt, cez, detzzt); + for (i = 0; i < zztlen; i++) { + detzzt[i] *= 2.0; + } + ztztlen = scale_expansion_zeroelim(ztlen, detzt, ceztail, detztzt); + z1len = fast_expansion_sum_zeroelim(zzlen, detzz, zztlen, detzzt, z1); + z2len = fast_expansion_sum_zeroelim(z1len, z1, ztztlen, detztzt, z2); + xylen = fast_expansion_sum_zeroelim(x2len, x2, y2len, y2, detxy); + clen = fast_expansion_sum_zeroelim(z2len, z2, xylen, detxy, cdet); + + temp32alen = scale_expansion_zeroelim(bclen, bc, aez, temp32a); + temp32blen = scale_expansion_zeroelim(bclen, bc, aeztail, temp32b); + temp64alen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64a); + temp32alen = scale_expansion_zeroelim(aclen, ac, -bez, temp32a); + temp32blen = scale_expansion_zeroelim(aclen, ac, -beztail, temp32b); + temp64blen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64b); + temp32alen = scale_expansion_zeroelim(ablen, ab, cez, temp32a); + temp32blen = scale_expansion_zeroelim(ablen, ab, ceztail, temp32b); + temp64clen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64c); + temp128len = fast_expansion_sum_zeroelim(temp64alen, temp64a, + temp64blen, temp64b, temp128); + temp192len = fast_expansion_sum_zeroelim(temp64clen, temp64c, + temp128len, temp128, temp192); + xlen = scale_expansion_zeroelim(temp192len, temp192, dex, detx); + xxlen = scale_expansion_zeroelim(xlen, detx, dex, detxx); + xtlen = scale_expansion_zeroelim(temp192len, temp192, dextail, detxt); + xxtlen = scale_expansion_zeroelim(xtlen, detxt, dex, detxxt); + for (i = 0; i < xxtlen; i++) { + detxxt[i] *= 2.0; + } + xtxtlen = scale_expansion_zeroelim(xtlen, detxt, dextail, detxtxt); + x1len = fast_expansion_sum_zeroelim(xxlen, detxx, xxtlen, detxxt, x1); + x2len = fast_expansion_sum_zeroelim(x1len, x1, xtxtlen, detxtxt, x2); + ylen = scale_expansion_zeroelim(temp192len, temp192, dey, dety); + yylen = scale_expansion_zeroelim(ylen, dety, dey, detyy); + ytlen = scale_expansion_zeroelim(temp192len, temp192, deytail, detyt); + yytlen = scale_expansion_zeroelim(ytlen, detyt, dey, detyyt); + for (i = 0; i < yytlen; i++) { + detyyt[i] *= 2.0; + } + ytytlen = scale_expansion_zeroelim(ytlen, detyt, deytail, detytyt); + y1len = fast_expansion_sum_zeroelim(yylen, detyy, yytlen, detyyt, y1); + y2len = fast_expansion_sum_zeroelim(y1len, y1, ytytlen, detytyt, y2); + zlen = scale_expansion_zeroelim(temp192len, temp192, dez, detz); + zzlen = scale_expansion_zeroelim(zlen, detz, dez, detzz); + ztlen = scale_expansion_zeroelim(temp192len, temp192, deztail, detzt); + zztlen = scale_expansion_zeroelim(ztlen, detzt, dez, detzzt); + for (i = 0; i < zztlen; i++) { + detzzt[i] *= 2.0; + } + ztztlen = scale_expansion_zeroelim(ztlen, detzt, deztail, detztzt); + z1len = fast_expansion_sum_zeroelim(zzlen, detzz, zztlen, detzzt, z1); + z2len = fast_expansion_sum_zeroelim(z1len, z1, ztztlen, detztzt, z2); + xylen = fast_expansion_sum_zeroelim(x2len, x2, y2len, y2, detxy); + dlen = fast_expansion_sum_zeroelim(z2len, z2, xylen, detxy, ddet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + cdlen = fast_expansion_sum_zeroelim(clen, cdet, dlen, ddet, cddet); + deterlen = fast_expansion_sum_zeroelim(ablen, abdet, cdlen, cddet, deter); + + return deter[deterlen - 1]; +} + +REAL insphereadapt(pa, pb, pc, pd, pe, permanent) + REAL* pa; +REAL* pb; +REAL* pc; +REAL* pd; +REAL* pe; +REAL permanent; +{ + INEXACT REAL aex, bex, cex, dex, aey, bey, cey, dey, aez, bez, cez, dez; + REAL det, errbound; + + INEXACT REAL aexbey1, bexaey1, bexcey1, cexbey1; + INEXACT REAL cexdey1, dexcey1, dexaey1, aexdey1; + INEXACT REAL aexcey1, cexaey1, bexdey1, dexbey1; + REAL aexbey0, bexaey0, bexcey0, cexbey0; + REAL cexdey0, dexcey0, dexaey0, aexdey0; + REAL aexcey0, cexaey0, bexdey0, dexbey0; + REAL ab[4], bc[4], cd[4], da[4], ac[4], bd[4]; + INEXACT REAL ab3, bc3, cd3, da3, ac3, bd3; + REAL abeps, bceps, cdeps, daeps, aceps, bdeps; + REAL temp8a[8], temp8b[8], temp8c[8], temp16[16], temp24[24], temp48[48]; + int temp8alen, temp8blen, temp8clen, temp16len, temp24len, temp48len; + REAL xdet[96], ydet[96], zdet[96], xydet[192]; + int xlen, ylen, zlen, xylen; + REAL adet[288], bdet[288], cdet[288], ddet[288]; + int alen, blen, clen, dlen; + REAL abdet[576], cddet[576]; + int ablen, cdlen; + REAL fin1[1152]; + int finlength; + + REAL aextail, bextail, cextail, dextail; + REAL aeytail, beytail, ceytail, deytail; + REAL aeztail, beztail, ceztail, deztail; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j; + REAL _0; + + aex = (REAL)(pa[0] - pe[0]); + bex = (REAL)(pb[0] - pe[0]); + cex = (REAL)(pc[0] - pe[0]); + dex = (REAL)(pd[0] - pe[0]); + aey = (REAL)(pa[1] - pe[1]); + bey = (REAL)(pb[1] - pe[1]); + cey = (REAL)(pc[1] - pe[1]); + dey = (REAL)(pd[1] - pe[1]); + aez = (REAL)(pa[2] - pe[2]); + bez = (REAL)(pb[2] - pe[2]); + cez = (REAL)(pc[2] - pe[2]); + dez = (REAL)(pd[2] - pe[2]); + + Two_Product(aex, bey, aexbey1, aexbey0); + Two_Product(bex, aey, bexaey1, bexaey0); + Two_Two_Diff(aexbey1, aexbey0, bexaey1, bexaey0, ab3, ab[2], ab[1], ab[0]); + ab[3] = ab3; + + Two_Product(bex, cey, bexcey1, bexcey0); + Two_Product(cex, bey, cexbey1, cexbey0); + Two_Two_Diff(bexcey1, bexcey0, cexbey1, cexbey0, bc3, bc[2], bc[1], bc[0]); + bc[3] = bc3; + + Two_Product(cex, dey, cexdey1, cexdey0); + Two_Product(dex, cey, dexcey1, dexcey0); + Two_Two_Diff(cexdey1, cexdey0, dexcey1, dexcey0, cd3, cd[2], cd[1], cd[0]); + cd[3] = cd3; + + Two_Product(dex, aey, dexaey1, dexaey0); + Two_Product(aex, dey, aexdey1, aexdey0); + Two_Two_Diff(dexaey1, dexaey0, aexdey1, aexdey0, da3, da[2], da[1], da[0]); + da[3] = da3; + + Two_Product(aex, cey, aexcey1, aexcey0); + Two_Product(cex, aey, cexaey1, cexaey0); + Two_Two_Diff(aexcey1, aexcey0, cexaey1, cexaey0, ac3, ac[2], ac[1], ac[0]); + ac[3] = ac3; + + Two_Product(bex, dey, bexdey1, bexdey0); + Two_Product(dex, bey, dexbey1, dexbey0); + Two_Two_Diff(bexdey1, bexdey0, dexbey1, dexbey0, bd3, bd[2], bd[1], bd[0]); + bd[3] = bd3; + + temp8alen = scale_expansion_zeroelim(4, cd, bez, temp8a); + temp8blen = scale_expansion_zeroelim(4, bd, -cez, temp8b); + temp8clen = scale_expansion_zeroelim(4, bc, dez, temp8c); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, + temp8blen, temp8b, temp16); + temp24len = fast_expansion_sum_zeroelim(temp8clen, temp8c, + temp16len, temp16, temp24); + temp48len = scale_expansion_zeroelim(temp24len, temp24, aex, temp48); + xlen = scale_expansion_zeroelim(temp48len, temp48, -aex, xdet); + temp48len = scale_expansion_zeroelim(temp24len, temp24, aey, temp48); + ylen = scale_expansion_zeroelim(temp48len, temp48, -aey, ydet); + temp48len = scale_expansion_zeroelim(temp24len, temp24, aez, temp48); + zlen = scale_expansion_zeroelim(temp48len, temp48, -aez, zdet); + xylen = fast_expansion_sum_zeroelim(xlen, xdet, ylen, ydet, xydet); + alen = fast_expansion_sum_zeroelim(xylen, xydet, zlen, zdet, adet); + + temp8alen = scale_expansion_zeroelim(4, da, cez, temp8a); + temp8blen = scale_expansion_zeroelim(4, ac, dez, temp8b); + temp8clen = scale_expansion_zeroelim(4, cd, aez, temp8c); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, + temp8blen, temp8b, temp16); + temp24len = fast_expansion_sum_zeroelim(temp8clen, temp8c, + temp16len, temp16, temp24); + temp48len = scale_expansion_zeroelim(temp24len, temp24, bex, temp48); + xlen = scale_expansion_zeroelim(temp48len, temp48, bex, xdet); + temp48len = scale_expansion_zeroelim(temp24len, temp24, bey, temp48); + ylen = scale_expansion_zeroelim(temp48len, temp48, bey, ydet); + temp48len = scale_expansion_zeroelim(temp24len, temp24, bez, temp48); + zlen = scale_expansion_zeroelim(temp48len, temp48, bez, zdet); + xylen = fast_expansion_sum_zeroelim(xlen, xdet, ylen, ydet, xydet); + blen = fast_expansion_sum_zeroelim(xylen, xydet, zlen, zdet, bdet); + + temp8alen = scale_expansion_zeroelim(4, ab, dez, temp8a); + temp8blen = scale_expansion_zeroelim(4, bd, aez, temp8b); + temp8clen = scale_expansion_zeroelim(4, da, bez, temp8c); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, + temp8blen, temp8b, temp16); + temp24len = fast_expansion_sum_zeroelim(temp8clen, temp8c, + temp16len, temp16, temp24); + temp48len = scale_expansion_zeroelim(temp24len, temp24, cex, temp48); + xlen = scale_expansion_zeroelim(temp48len, temp48, -cex, xdet); + temp48len = scale_expansion_zeroelim(temp24len, temp24, cey, temp48); + ylen = scale_expansion_zeroelim(temp48len, temp48, -cey, ydet); + temp48len = scale_expansion_zeroelim(temp24len, temp24, cez, temp48); + zlen = scale_expansion_zeroelim(temp48len, temp48, -cez, zdet); + xylen = fast_expansion_sum_zeroelim(xlen, xdet, ylen, ydet, xydet); + clen = fast_expansion_sum_zeroelim(xylen, xydet, zlen, zdet, cdet); + + temp8alen = scale_expansion_zeroelim(4, bc, aez, temp8a); + temp8blen = scale_expansion_zeroelim(4, ac, -bez, temp8b); + temp8clen = scale_expansion_zeroelim(4, ab, cez, temp8c); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, + temp8blen, temp8b, temp16); + temp24len = fast_expansion_sum_zeroelim(temp8clen, temp8c, + temp16len, temp16, temp24); + temp48len = scale_expansion_zeroelim(temp24len, temp24, dex, temp48); + xlen = scale_expansion_zeroelim(temp48len, temp48, dex, xdet); + temp48len = scale_expansion_zeroelim(temp24len, temp24, dey, temp48); + ylen = scale_expansion_zeroelim(temp48len, temp48, dey, ydet); + temp48len = scale_expansion_zeroelim(temp24len, temp24, dez, temp48); + zlen = scale_expansion_zeroelim(temp48len, temp48, dez, zdet); + xylen = fast_expansion_sum_zeroelim(xlen, xdet, ylen, ydet, xydet); + dlen = fast_expansion_sum_zeroelim(xylen, xydet, zlen, zdet, ddet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + cdlen = fast_expansion_sum_zeroelim(clen, cdet, dlen, ddet, cddet); + finlength = fast_expansion_sum_zeroelim(ablen, abdet, cdlen, cddet, fin1); + + det = estimate(finlength, fin1); + errbound = isperrboundB * permanent; + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + Two_Diff_Tail(pa[0], pe[0], aex, aextail); + Two_Diff_Tail(pa[1], pe[1], aey, aeytail); + Two_Diff_Tail(pa[2], pe[2], aez, aeztail); + Two_Diff_Tail(pb[0], pe[0], bex, bextail); + Two_Diff_Tail(pb[1], pe[1], bey, beytail); + Two_Diff_Tail(pb[2], pe[2], bez, beztail); + Two_Diff_Tail(pc[0], pe[0], cex, cextail); + Two_Diff_Tail(pc[1], pe[1], cey, ceytail); + Two_Diff_Tail(pc[2], pe[2], cez, ceztail); + Two_Diff_Tail(pd[0], pe[0], dex, dextail); + Two_Diff_Tail(pd[1], pe[1], dey, deytail); + Two_Diff_Tail(pd[2], pe[2], dez, deztail); + if ((aextail == 0.0) && (aeytail == 0.0) && (aeztail == 0.0) + && (bextail == 0.0) && (beytail == 0.0) && (beztail == 0.0) + && (cextail == 0.0) && (ceytail == 0.0) && (ceztail == 0.0) + && (dextail == 0.0) && (deytail == 0.0) && (deztail == 0.0)) { + return det; + } + + errbound = isperrboundC * permanent + resulterrbound * Absolute(det); + abeps = (aex * beytail + bey * aextail) + - (aey * bextail + bex * aeytail); + bceps = (bex * ceytail + cey * bextail) + - (bey * cextail + cex * beytail); + cdeps = (cex * deytail + dey * cextail) + - (cey * dextail + dex * ceytail); + daeps = (dex * aeytail + aey * dextail) + - (dey * aextail + aex * deytail); + aceps = (aex * ceytail + cey * aextail) + - (aey * cextail + cex * aeytail); + bdeps = (bex * deytail + dey * bextail) + - (bey * dextail + dex * beytail); + det += (((bex * bex + bey * bey + bez * bez) + * ((cez * daeps + dez * aceps + aez * cdeps) + + (ceztail * da3 + deztail * ac3 + aeztail * cd3)) + + (dex * dex + dey * dey + dez * dez) + * ((aez * bceps - bez * aceps + cez * abeps) + + (aeztail * bc3 - beztail * ac3 + ceztail * ab3))) + - ((aex * aex + aey * aey + aez * aez) + * ((bez * cdeps - cez * bdeps + dez * bceps) + + (beztail * cd3 - ceztail * bd3 + deztail * bc3)) + + (cex * cex + cey * cey + cez * cez) + * ((dez * abeps + aez * bdeps + bez * daeps) + + (deztail * ab3 + aeztail * bd3 + beztail * da3)))) + + 2.0 * (((bex * bextail + bey * beytail + bez * beztail) * (cez * da3 + dez * ac3 + aez * cd3) + (dex * dextail + dey * deytail + dez * deztail) * (aez * bc3 - bez * ac3 + cez * ab3)) - ((aex * aextail + aey * aeytail + aez * aeztail) * (bez * cd3 - cez * bd3 + dez * bc3) + (cex * cextail + cey * ceytail + cez * ceztail) * (dez * ab3 + aez * bd3 + bez * da3))); + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + return insphereexact(pa, pb, pc, pd, pe); +} + +REAL insphere(pa, pb, pc, pd, pe) + REAL* pa; +REAL* pb; +REAL* pc; +REAL* pd; +REAL* pe; +{ + REAL aex, bex, cex, dex; + REAL aey, bey, cey, dey; + REAL aez, bez, cez, dez; + REAL aexbey, bexaey, bexcey, cexbey, cexdey, dexcey, dexaey, aexdey; + REAL aexcey, cexaey, bexdey, dexbey; + REAL alift, blift, clift, dlift; + REAL ab, bc, cd, da, ac, bd; + REAL abc, bcd, cda, dab; + REAL aezplus, bezplus, cezplus, dezplus; + REAL aexbeyplus, bexaeyplus, bexceyplus, cexbeyplus; + REAL cexdeyplus, dexceyplus, dexaeyplus, aexdeyplus; + REAL aexceyplus, cexaeyplus, bexdeyplus, dexbeyplus; + REAL det; + REAL permanent, errbound; + + aex = pa[0] - pe[0]; + bex = pb[0] - pe[0]; + cex = pc[0] - pe[0]; + dex = pd[0] - pe[0]; + aey = pa[1] - pe[1]; + bey = pb[1] - pe[1]; + cey = pc[1] - pe[1]; + dey = pd[1] - pe[1]; + aez = pa[2] - pe[2]; + bez = pb[2] - pe[2]; + cez = pc[2] - pe[2]; + dez = pd[2] - pe[2]; + + aexbey = aex * bey; + bexaey = bex * aey; + ab = aexbey - bexaey; + bexcey = bex * cey; + cexbey = cex * bey; + bc = bexcey - cexbey; + cexdey = cex * dey; + dexcey = dex * cey; + cd = cexdey - dexcey; + dexaey = dex * aey; + aexdey = aex * dey; + da = dexaey - aexdey; + + aexcey = aex * cey; + cexaey = cex * aey; + ac = aexcey - cexaey; + bexdey = bex * dey; + dexbey = dex * bey; + bd = bexdey - dexbey; + + abc = aez * bc - bez * ac + cez * ab; + bcd = bez * cd - cez * bd + dez * bc; + cda = cez * da + dez * ac + aez * cd; + dab = dez * ab + aez * bd + bez * da; + + alift = aex * aex + aey * aey + aez * aez; + blift = bex * bex + bey * bey + bez * bez; + clift = cex * cex + cey * cey + cez * cez; + dlift = dex * dex + dey * dey + dez * dez; + + det = (dlift * abc - clift * dab) + (blift * cda - alift * bcd); + + aezplus = Absolute(aez); + bezplus = Absolute(bez); + cezplus = Absolute(cez); + dezplus = Absolute(dez); + aexbeyplus = Absolute(aexbey); + bexaeyplus = Absolute(bexaey); + bexceyplus = Absolute(bexcey); + cexbeyplus = Absolute(cexbey); + cexdeyplus = Absolute(cexdey); + dexceyplus = Absolute(dexcey); + dexaeyplus = Absolute(dexaey); + aexdeyplus = Absolute(aexdey); + aexceyplus = Absolute(aexcey); + cexaeyplus = Absolute(cexaey); + bexdeyplus = Absolute(bexdey); + dexbeyplus = Absolute(dexbey); + permanent = ((cexdeyplus + dexceyplus) * bezplus + + (dexbeyplus + bexdeyplus) * cezplus + + (bexceyplus + cexbeyplus) * dezplus) + * alift + + ((dexaeyplus + aexdeyplus) * cezplus + + (aexceyplus + cexaeyplus) * dezplus + + (cexdeyplus + dexceyplus) * aezplus) + * blift + + ((aexbeyplus + bexaeyplus) * dezplus + + (bexdeyplus + dexbeyplus) * aezplus + + (dexaeyplus + aexdeyplus) * bezplus) + * clift + + ((bexceyplus + cexbeyplus) * aezplus + + (cexaeyplus + aexceyplus) * bezplus + + (aexbeyplus + bexaeyplus) * cezplus) + * dlift; + errbound = isperrboundA * permanent; + if ((det > errbound) || (-det > errbound)) { + return det; + } + + return insphereadapt(pa, pb, pc, pd, pe, permanent); +} +#endif + +#endif \ No newline at end of file diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index c0b1ec93e3..f843b64fd5 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -9535,6 +9535,7 @@ void Plater::export_core_3mf() export_3mf(path_u8, SaveStrategy::Silence); } +#define USE_CGAL_BOOLEAN 0 void Plater::export_stl(bool extended, bool selection_only) { if (p->model.objects.empty()) { return; } @@ -9561,8 +9562,13 @@ void Plater::export_stl(bool extended, bool selection_only) if (csg::check_csgmesh_booleans(Range{ std::begin(csgmesh), std::end(csgmesh) }) == csgmesh.end()) { try { +#if USE_CGAL_BOOLEAN auto meshPtr = csg::perform_csgmesh_booleans(Range{ std::begin(csgmesh), std::end(csgmesh) }); mesh = MeshBoolean::cgal::cgal_to_triangle_mesh(*meshPtr); +#else + MeshBoolean::mcut::McutMeshPtr meshPtr = csg::perform_csgmesh_booleans_mcut(Range{std::begin(csgmesh), std::end(csgmesh)}); + mesh = MeshBoolean::mcut::mcut_to_triangle_mesh(*meshPtr); +#endif } catch (...) {} }