Initial release

This commit is contained in:
Louis Rossmann
2026-05-11 07:39:33 -05:00
commit c661ddc2eb
16967 changed files with 4075897 additions and 0 deletions

View File

@@ -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: <http://www.gnu.org/licenses/>
* (B) Commercial license.
* - email: floyd.m.chitalu@gmail.com
*
* The commercial license options is for users that wish to use MCUT in
* their products for comercial purposes but do not wish to release their
* software products under the GPL license.
*
* Author(s) : Floyd M. Chitalu
*/
#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<bounding_box_t<vec3>>& bvhAABBs,
std::vector<fd_t>& bvhLeafNodeFaces,
std::vector<bounding_box_t<vec3>>& face_bboxes,
const double& slightEnlargmentEps = double(0.0));
extern void intersectOIBVHs(
std::map<fd_t, std::vector<fd_t>>& ps_face_to_potentially_intersecting_others,
const std::vector<bounding_box_t<vec3>>& srcMeshBvhAABBs,
const std::vector<fd_t>& srcMeshBvhLeafNodeFaces,
const std::vector<bounding_box_t<vec3>>& cutMeshBvhAABBs,
const std::vector<fd_t>& cutMeshBvhLeafNodeFaces);
#else
typedef bounding_box_t<vec3> 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<vec3> 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; well 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<BVHBuildNode>& c0, std::shared_ptr<BVHBuildNode>& c1)
{
children[0] = c0;
children[1] = c1;
bounds = Union(c0->bounds, c1->bounds);
splitAxis = axis;
nPrimitives = 0;
}
bounding_box_t<vec3> bounds;
std::shared_ptr<BVHBuildNode> 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<BVHBuildNode> 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<BVHBuildNode> recursiveBuild(
std::vector<BVHPrimitiveInfo>& buildData,
uint32_t start,
uint32_t end,
uint32_t* totalNodes,
std::vector<fd_t>& orderedPrims);
int GetNodeCount() const;
const std::shared_ptr<LinearBVHNode>& 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<fd_t, std::vector<fd_t>>& 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<BVHPrimitiveInfo> buildData;
std::vector<fd_t> primitives; // ordered primitives
std::vector<BBox> primitiveOrderedBBoxes; // unsorted elements correspond to mesh indices
std::vector<std::shared_ptr<LinearBVHNode>> nodes;
};
#endif // #if defined(USE_OIBVH)
#endif // MCUT_BVH_H_

View File

@@ -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.

View File

@@ -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 <algorithm>
#include <cassert>
#include <cstdlib>
#include <iterator>
#include <memory>
#include <stack>
#include <vector>
/// 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<std::vector<std::uint32_t>> get_vertex_to_triangles_map(
const std::vector<triangle_t>& triangles,
const std::uint32_t verticesSize)
{
std::vector<std::vector<std::uint32_t>> 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<std::uint32_t, 3>& vertices = triangle.vertices;
for (std::array<std::uint32_t, 3>::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<std::size_t> mapping; ///< vertex index mapping
std::vector<std::size_t> duplicates; ///< duplicates' indices
};
/*!
* Remove elements in the range [first; last) with indices from the sorted
* unique range [ii_first, ii_last)
*/
template <class ForwardIt, class SortUniqIndsFwdIt>
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<ForwardIt>::difference_type diff_t;
typedef typename std::iterator_traits<SortUniqIndsFwdIt>::value_type ind_t;
ForwardIt destination = first + static_cast<diff_t>(*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<diff_t>(*(ii_first - 1)) + 1;
const ForwardIt source_last = ii_first != ii_last ? first + static_cast<diff_t>(*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<vec2_<T>, std::size_t> uniqueVerts; // position to index map
const std::size_t verticesSize = std::distance(first, last);
duplicates_info_t di = {
std::vector<std::size_t>(verticesSize),
std::vector<std::size_t>()
};
for (std::size_t iIn = 0, iOut = iIn; iIn < verticesSize; ++iIn, ++first) {
typename std::unordered_map<vec2_<T>, 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_<T>::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 <typename TVertex, typename TAllocator>
void remove_duplicates(
std::vector<TVertex, TAllocator>& vertices,
const std::vector<std::size_t>& 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 <typename T>
duplicates_info_t remove_duplicates(std::vector<vec2_<T>>& vertices)
{
const duplicates_info_t di = find_duplicates<T>(
vertices.begin(),
vertices.end(),
get_x_coord_vec2d<T>,
get_y_coord_vec2d<T>);
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<std::size_t>& mapping,
TGetEdgeVertexStart getStart,
TGetEdgeVertexEnd getEnd,
TMakeEdgeFromStartAndEnd makeEdge)
{
for (; first != last; ++first) {
*first = makeEdge(
static_cast<std::uint32_t>(mapping[getStart(*first)]),
static_cast<std::uint32_t>(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<edge_t>& edges, const std::vector<std::size_t>& 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<TVertex, TVertexAllocator>& 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<T>(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 <typename T>
duplicates_info_t remove_duplicates_and_remap_edges(
std::vector<vec2_<T>>& vertices,
std::vector<edge_t>& edges)
{
return remove_duplicates_and_remap_edges<T>(
vertices,
get_x_coord_vec2d<T>,
get_y_coord_vec2d<T>,
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<edge_t>
extract_edges_from_triangles(const std::vector<triangle_t>& triangles)
{
std::unordered_set<edge_t> edges;
for (std::vector<triangle_t>::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_t, std::vector<edge_t>>
edge_to_pieces_mapping(const std::unordered_map<edge_t, std::vector<edge_t>>& pieceToOriginals)
{
std::unordered_map<edge_t, std::vector<edge_t>> originalToPieces;
typedef std::unordered_map<edge_t, std::vector<edge_t>>::const_iterator Cit;
for (Cit ptoIt = pieceToOriginals.begin(); ptoIt != pieceToOriginals.end();
++ptoIt) {
const edge_t piece = ptoIt->first;
const std::vector<edge_t>& originals = ptoIt->second;
for (std::vector<edge_t>::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 <typename T>
std::unordered_map<edge_t, std::vector<std::uint32_t>> get_edge_to_split_vertices_map(
const std::unordered_map<edge_t, std::vector<edge_t>>& edgeToPieces,
const std::vector<vec2_<T>>& vertices)
{
typedef std::pair<std::uint32_t, T> VertCoordPair;
struct ComparePred {
bool operator()(const VertCoordPair& a, const VertCoordPair& b) const
{
return a.second < b.second;
}
} comparePred;
std::unordered_map<edge_t, std::vector<std::uint32_t>> edgeToSplitVerts;
for (std::unordered_map<edge_t, std::vector<edge_t>>::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<edge_t>& pieces = it->second;
std::vector<VertCoordPair> splitVerts;
// size is: 2[ends] + (pieces - 1)[split vertices] = pieces + 1
splitVerts.reserve(pieces.size() + 1);
for (std::vector<edge_t>::const_iterator it = pieces.begin(); it != pieces.end(); ++it) {
const std::array<std::uint32_t, 2> vv = { it->v1(), it->v2() };
for (std::array<std::uint32_t, 2>::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<edge_t, std::vector<std::uint32_t>> val = std::make_pair(e, std::vector<std::uint32_t>());
val.second.reserve(splitVerts.size());
for (typename std::vector<VertCoordPair>::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_<T>
namespace std {
template <typename T>
struct hash<vec2_<T>> {
size_t operator()(const vec2_<T>& xy) const
{
return std::hash<T>()(xy.x()) ^ std::hash<T>()(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 <typename T, typename TNearPointLocator>
inline bool check_topology(const cdt::triangulator_t<T, TNearPointLocator>& cdt)
{
// Check if vertices' adjacent triangles contain vertex
const std::vector<std::vector<std::uint32_t>> vertTris = cdt.is_finalized()
? get_vertex_to_triangles_map(
cdt.triangles, static_cast<std::uint32_t>(cdt.vertices.size()))
: cdt.vertTris;
for (std::uint32_t iV(0); iV < std::uint32_t(cdt.vertices.size()); ++iV) {
const std::vector<std::uint32_t>& vTris = vertTris[iV];
typedef std::vector<std::uint32_t>::const_iterator TriIndCit;
for (TriIndCit it = vTris.begin(); it != vTris.end(); ++it) {
const std::array<std::uint32_t, 3>& 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<std::uint32_t, 3>::const_iterator NCit;
for (NCit it = t.neighbors.begin(); it != t.neighbors.end(); ++it) {
if (*it == null_neighbour)
continue;
const std::array<std::uint32_t, 3>& 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<std::uint32_t, 3>::const_iterator VCit;
for (VCit it = t.vertices.begin(); it != t.vertices.end(); ++it) {
const std::vector<std::uint32_t>& 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_

View File

@@ -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 <cassert>
#include <limits>
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_<coord_type> point_type;
typedef std::pair<point_type, std::uint32_t> value_type;
typedef std::vector<std::uint32_t> point_data_vec;
typedef point_data_vec::const_iterator pd_cit;
typedef std::array<std::uint32_t, 2> 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<coord_type>::max(),
-std::numeric_limits<coord_type>::max()))
, m_max(point_type::make(
std::numeric_limits<coord_type>::max(),
std::numeric_limits<coord_type>::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<point_type>& 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<point_type>& points) const
{
value_type out;
int iTask = -1;
coord_type minDistSq = std::numeric_limits<coord_type>::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<int>(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<std::uint32_t>(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<size_t>(
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<point_type>& 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<Node> 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<NearestTask> 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<vec2_<TCoordType>>& points)
{
vec2_<TCoordType> min = points.front();
vec2_<TCoordType> max = min;
for (typename std::vector<vec2_<TCoordType>>::const_iterator it = points.begin();
it != points.end();
++it) {
min = vec2_<TCoordType>::make(std::min(min.x(), it->x()), std::min(min.y(), it->y()));
max = vec2_<TCoordType>::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<vec2_<TCoordType>>& points)
{
m_tree.insert(i, points);
}
/// Find nearest point using R-tree
std::uint32_t nearPoint(
const vec2_<TCoordType>& pos,
const std::vector<vec2_<TCoordType>>& points) const
{
return m_tree.nearest(pos, points).second;
}
private:
typedef kdt::kdtree_t_<TCoordType, NumVerticesInLeaf, InitialStackDepth, StackDepthIncrement> kdtree_t;
kdtree_t m_tree;
};
} // namespace cdt
#endif // header guard

File diff suppressed because it is too large Load Diff

View File

@@ -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 <cassert>
#include <cmath>
#include <limits>
#include <vector>
#include <array>
#include <functional>
#include <random>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
namespace cdt {
/// X- coordinate getter for vec2d_t
template <typename T>
const T& get_x_coord_vec2d(const vec2_<T>& v)
{
return v.x();
}
/// Y-coordinate getter for vec2d_t
template <typename T>
const T& get_y_coord_vec2d(const vec2_<T>& v)
{
return v.y();
}
/// If two 2D vectors are exactly equal
template <typename T>
bool operator==(const vec2_<T>& lhs, const vec2_<T>& 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<std::uint32_t>::max());
/// Constant representing no valid vertex for a triangle
const static std::uint32_t null_vertex(std::numeric_limits<std::uint32_t>::max());
/// 2D bounding box
template <typename T>
struct box2d_t {
vec2_<T> min; ///< min box corner
vec2_<T> max; ///< max box corner
/// Envelop box around a point
void expand_with_point(const vec2_<T>& 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<T> expand_with_points(
TVertexIter first,
TVertexIter last,
TGetVertexCoordX get_x_coord,
TGetVertexCoordY get_y_coord)
{
const T max = std::numeric_limits<T>::max();
box2d_t<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 <typename T>
box2d_t<T> expand_with_points(const std::vector<vec2_<T>>& 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<std::uint32_t, std::uint32_t>& verts() const
{
return m_vertices;
}
private:
std::pair<std::uint32_t, std::uint32_t> 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<std::uint32_t, 3> vertices;
std::array<std::uint32_t, 3> neighbors;
/**
* Factory method
* @note needed for c++03 compatibility (no uniform initialization
* available)
*/
static triangle_t
make(const std::array<std::uint32_t, 3>& vertices, const std::array<std::uint32_t, 3>& 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 <stdexcept>
namespace cdt {
//*****************************************************************************
// box2d_t
//*****************************************************************************
template <typename T>
box2d_t<T> expand_with_points(const std::vector<vec2_<T>>& vertices)
{
return expand_with_points<T>(
vertices.begin(), vertices.end(), get_x_coord_vec2d<T>, get_y_coord_vec2d<T>);
}
//*****************************************************************************
// 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<std::uint32_t>(location - point_to_triangle_location_t::ON_1ST_EDGE);
}
#if 0
/// Orient p against line v1-v2 2D: robust geometric predicate
template <typename T>
T orient2D(const vec2_<T>& p, const vec2_<T>& v1, const vec2_<T>& v2)
{
return orient2d(v1.x(), v1.y(), v2.x(), v2.y(), p.x(), p.y());
}
#endif
/// Classify value of orient2d predicate
template <typename T>
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 <typename T>
point_to_line_location_t::Enum locate_point_wrt_line(
const vec2_<T>& p,
const vec2_<T>& v1,
const vec2_<T>& 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 <typename T>
point_to_triangle_location_t::Enum locate_point_wrt_triangle(
const vec2_<T>& p,
const vec2_<T>& v1,
const vec2_<T>& v2,
const vec2_<T>& 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 <typename T>
bool check_is_in_circumcircle(
const vec2_<T>& p,
const vec2_<T>& v1,
const vec2_<T>& v2,
const vec2_<T>& v3)
{
const double p_[2] = { static_cast<double>(p.x()), static_cast<double>(p.y()) };
const double v1_[2] = { static_cast<double>(v1.x()), static_cast<double>(v1.y()) };
const double v2_[2] = { static_cast<double>(v2.x()), static_cast<double>(v2.y()) };
const double v3_[2] = { static_cast<double>(v3.x()), static_cast<double>(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<std::uint32_t>& aTris, const std::vector<std::uint32_t>& bTris)
{
for (std::vector<std::uint32_t>::const_iterator it = aTris.begin(); it != aTris.end(); ++it)
if (std::find(bTris.begin(), bTris.end(), *it) != bTris.end())
return true;
return false;
}
template <typename T>
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 <typename T>
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 <typename T>
T distance(const vec2_<T>& a, const vec2_<T>& b)
{
return distance(a.x(), a.y(), b.x(), b.y());
}
template <typename T>
T get_square_distance(const vec2_<T>& a, const vec2_<T>& 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<cdt::edge_t> {
/// 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<std::uint32_t>()(key) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}
static std::size_t get_hashed_edge_index(const cdt::edge_t& e)
{
const std::pair<std::uint32_t, std::uint32_t>& 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

View File

@@ -0,0 +1,847 @@
/**
* Copyright (c) 2021-2022 Floyd M. Chitalu.
* All rights reserved.
*
* NOTE: This file is licensed under GPL-3.0-or-later (default).
* A commercial license can be purchased from Floyd M. Chitalu.
*
* License details:
*
* (A) GNU General Public License ("GPL"); a copy of which you should have
* recieved with this file.
* - see also: <http://www.gnu.org/licenses/>
* (B) Commercial license.
* - email: floyd.m.chitalu@gmail.com
*
* The commercial license options is for users that wish to use MCUT in
* their products for comercial purposes but do not wish to release their
* software products under the GPL license.
*
* Author(s) : Floyd M. Chitalu
*/
/**
* @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 <future>
#include <map>
#include <memory>
#include <string>
#include <chrono>
#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 bind_state_impl(
const McContext context,
McFlags stateInfo,
McSize bytes,
const McVoid* pMem);
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 dispatch_planar_section_impl(
McContext context,
McFlags flags,
const McVoid* pSrcMeshVertices,
const uint32_t* pSrcMeshFaceIndices,
const uint32_t* pSrcMeshFaceSizes,
uint32_t numSrcMeshVertices,
uint32_t numSrcMeshFaces,
const McDouble* pNormalVector,
const McDouble sectionOffset,
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<output_mesh_info_t> 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<std::unordered_map<vd_t, vec3>> source_hmesh_new_poly_partition_vertices; // addedFpPartitioningVerticesOnCorrespondingInputSrcMesh
std::shared_ptr<std::unordered_map<vd_t, vec3>> 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<uint32_t> 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<uint32_t> 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<uint32_t> 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<uint32_t> face_adjacent_faces_size_cache;
bool face_adjacent_faces_size_cache_initialized = false;
#endif // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
// non-zero if origin source and cut-mesh where perturbed
vec3 perturbation_vector = vec3(0.0);
};
// 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<void> 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<bool> m_invoked;
} m_callback_info;
// atomic boolean flag indicating whether the task associated with event
// object has completed running
std::atomic<bool> 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<uint32_t> m_responsible_thread_id;
std::atomic<int32_t> m_runtime_exec_status; // API return code associated with respective task (for user to query)
std::atomic<size_t> m_timestamp_submit;
std::atomic<size_t> m_timestamp_start;
std::atomic<size_t> m_timestamp_end;
std::atomic<uint32_t> 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<std::packaged_task<void()>> m_user_API_command_task_emulator;
McContext m_context;
const char* get_cmd_type_str()
{
switch (m_command_type) {
case McCommandType::MC_COMMAND_DISPATCH: {
return "MC_COMMAND_DISPATCH";
} break;
case McCommandType::MC_COMMAND_GET_CONNECTED_COMPONENT_DATA: {
return "MC_COMMAND_GET_CONNECTED_COMPONENT_DATA";
} break;
case McCommandType::MC_COMMAND_GET_CONNECTED_COMPONENTS: {
return "MC_COMMAND_GET_CONNECTED_COMPONENTS";
} break;
case McCommandType::MC_COMMAND_USER: {
return "MC_COMMAND_USER";
} break;
case McCommandType::MC_COMMAND_UKNOWN: {
return "MC_COMMAND_UKNOWN";
} break;
default:
fprintf(stderr, "unknown command type value (%d)\n", (int)m_command_type);
return "UNKNOWN VALUE";
}
}
explicit event_t(McEvent user_handle, McCommandType command_type)
: m_user_handle(user_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(command_type)
, m_user_API_command_task_emulator(nullptr)
, m_context(nullptr)
{
log_msg("[MCUT] Create event (type=" << get_cmd_type_str() <<", handle=" << m_user_handle << ")");
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 (type=" << get_cmd_type_str() << ", handle=" << m_user_handle << ")");
}
inline std::size_t get_time_since_epoch()
{
return std::chrono::duration_cast<std::chrono::nanoseconds>(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<std::mutex> 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<std::mutex> 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<std::shared_ptr<event_t>> g_events;
extern std::atomic<std::uintptr_t> 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 <typename Derived>
void fn_delete_cc(connected_component_t* p)
{
log_msg("[MCUT] Destroy connected component " << p->m_user_handle);
delete dynamic_cast<Derived*>(p);
}
// struct defining the state of a context object
struct context_t {
private:
std::atomic<bool> m_done;
std::vector<thread_safe_queue<function_wrapper>> 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<std::thread> 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<thread_pool> m_compute_threadpool;
#endif
// The state and flag variable current used to configure the next dispatch call
McFlags m_flags = (McFlags)0;
std::atomic<McDouble> m_general_position_enforcement_constant;
std::atomic<McUint32> m_max_num_perturbation_attempts;
std::atomic<McConnectedComponentFaceWindingOrder> m_connected_component_winding_order;
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_general_position_enforcement_constant(1e-4)
, m_max_num_perturbation_attempts(1 << 2),
m_connected_component_winding_order(McConnectedComponentFaceWindingOrder::MC_CONNECTED_COMPONENT_FACE_WINDING_ORDER_AS_GIVEN)
, 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<thread_safe_queue<function_wrapper>>(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<thread_pool>(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;
std::atomic_thread_fence(std::memory_order_acq_rel);
#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;
}
// returns (user controllable) epsilon representing the maximum by which the cut-mesh
// can be perturbed on any axis
McDouble get_general_position_enforcement_constant() const
{
return this->m_general_position_enforcement_constant.load(std::memory_order_acquire);
}
void set_general_position_enforcement_constant(McDouble new_value)
{
this->m_general_position_enforcement_constant.store(new_value, std::memory_order_release);
}
// returns (user controllable) maximum number of times by which the cut-mesh
// can be perturbed before giving (input likely need to be preprocessed)
McUint32 get_general_position_enforcement_attempts() const
{
return this->m_max_num_perturbation_attempts.load(std::memory_order_acquire);
}
void set_general_position_enforcement_attempts(McUint32 new_value)
{
this->m_max_num_perturbation_attempts.store(new_value, std::memory_order_release);
}
McConnectedComponentFaceWindingOrder get_connected_component_winding_order() const
{
return this->m_connected_component_winding_order.load(std::memory_order_acquire);
}
void set_connected_component_winding_order(McConnectedComponentFaceWindingOrder new_value)
{
this->m_connected_component_winding_order.store(new_value, std::memory_order_release);
}
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
thread_pool& get_shared_compute_threadpool()
{
return m_compute_threadpool.get()[0];
}
#endif
template <typename FunctionType>
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_t> event_ptr = std::shared_ptr<event_t>(new event_t(
reinterpret_cast<McEvent>(g_objects_counter.fetch_add(1, std::memory_order_relaxed)),
cmdType));
MCUT_ASSERT(event_ptr != nullptr);
g_events.push_front(event_ptr);
//event_ptr->m_user_handle = reinterpret_cast<McEvent>(g_objects_counter.fetch_add(1, std::memory_order_relaxed));
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<McEvent> 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<McEvent>::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<event_t> parent_task_event_ptr = g_events.find_first_if([=](std::shared_ptr<event_t> e) { return e->m_user_handle == parent_task_event_handle; });
if (parent_task_event_ptr == nullptr) {
throw std::invalid_argument("invalid event in waitlist");
}
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 + 1) % (uint32_t)m_api_threads.size()].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_t> event_weak_ptr(event_ptr);
std::packaged_task<void()> 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_t> 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<std::shared_ptr<connected_component_t>> connected_components;
// McFlags dispatchFlags = (McFlags)0;
// client/user debugging variables
// ------------------------------
// 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<McFlags> dbgCallbackBitfieldSource;
// controller for permmited messages based on the type of message
std::atomic<McFlags> dbgCallbackBitfieldType;
// controller for permmited messages based on the severity of message
std::atomic<McFlags> dbgCallbackBitfieldSeverity;
bool dbgCallbackAllowAsyncCalls = true;
void set_debug_callback_data(pfn_mcDebugOutput_CALLBACK cb, const McVoid* data_ptr)
{
std::lock_guard<std::mutex> lguard(debugCallbackMutex);
debugCallback = cb;
debugCallbackUserParam = data_ptr;
}
struct debug_log_msg_t {
McDebugSource source;
McDebugType type;
McDebugSeverity severity;
std::string str;
};
std::vector<debug_log_msg_t> 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<std::mutex> 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<std::shared_ptr<context_t>> g_contexts;
#endif // #ifndef _FRONTEND_H_

View File

@@ -0,0 +1,640 @@
/**
* Copyright (c) 2021-2022 Floyd M. Chitalu.
* All rights reserved.
*
* NOTE: This file is licensed under GPL-3.0-or-later (default).
* A commercial license can be purchased from Floyd M. Chitalu.
*
* License details:
*
* (A) GNU General Public License ("GPL"); a copy of which you should have
* recieved with this file.
* - see also: <http://www.gnu.org/licenses/>
* (B) Commercial license.
* - email: floyd.m.chitalu@gmail.com
*
* The commercial license options is for users that wish to use MCUT in
* their products for comercial purposes but do not wish to release their
* software products under the GPL license.
*
* Author(s) : Floyd M. Chitalu
*/
#ifndef MCUT_HALFEDGE_MESH_H_
#define MCUT_HALFEDGE_MESH_H_
#include "mcut/internal/math.h"
#include "mcut/internal/utils.h"
#include <algorithm>
#include <limits>
#include <map>
#include <memory>
#include <vector>
#include <cstdint>
template <typename T>
class descriptor_t_ {
public:
typedef unsigned int index_type;
descriptor_t_() { }
virtual ~descriptor_t_() { }
explicit descriptor_t_(index_type i = (std::numeric_limits<index_type>::max)())
: m_value(i)
{
}
operator index_type() const { return m_value; }
void reset() { m_value = (std::numeric_limits<index_type>::max)(); }
bool is_valid() const
{
index_type inf = (std::numeric_limits<index_type>::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_<halfedge_descriptor_t> {
public:
halfedge_descriptor_t()
: descriptor_t_<halfedge_descriptor_t>(std::numeric_limits<index_type>::max())
{
}
explicit halfedge_descriptor_t(descriptor_t_<halfedge_descriptor_t>::index_type idx)
: descriptor_t_<halfedge_descriptor_t>(idx)
{
}
virtual ~halfedge_descriptor_t()
{
}
};
class edge_descriptor_t : public descriptor_t_<edge_descriptor_t> {
public:
edge_descriptor_t()
: descriptor_t_<edge_descriptor_t>((std::numeric_limits<index_type>::max)())
{
}
explicit edge_descriptor_t(descriptor_t_<edge_descriptor_t>::index_type idx)
: descriptor_t_<edge_descriptor_t>(idx)
{
}
virtual ~edge_descriptor_t()
{
}
};
class face_descriptor_t : public descriptor_t_<face_descriptor_t> {
public:
face_descriptor_t()
: descriptor_t_<face_descriptor_t>((std::numeric_limits<index_type>::max)())
{
}
explicit face_descriptor_t(descriptor_t_<face_descriptor_t>::index_type idx)
: descriptor_t_<face_descriptor_t>(idx)
{
}
virtual ~face_descriptor_t()
{
}
};
class vertex_descriptor_t : public descriptor_t_<vertex_descriptor_t> {
public:
vertex_descriptor_t()
: descriptor_t_<vertex_descriptor_t>((std::numeric_limits<index_type>::max)())
{
}
explicit vertex_descriptor_t(descriptor_t_<vertex_descriptor_t>::index_type idx)
: descriptor_t_<vertex_descriptor_t>(idx)
{
}
virtual ~vertex_descriptor_t()
{
}
};
template <typename T>
struct id_ {
typedef T type;
};
template <typename V>
class array_iterator_t;
struct halfedge_data_t : id_<halfedge_descriptor_t> {
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_<edge_descriptor_t> {
halfedge_descriptor_t h; // primary halfedge (even idx)
};
struct face_data_t : id_<face_descriptor_t> {
std::vector<halfedge_descriptor_t> m_halfedges;
};
struct vertex_data_t : id_<vertex_descriptor_t> {
vec3 p; // geometry coordinates
//std::vector<face_descriptor_t> m_faces; // ... incident to vertex // TODO: this is not needed (can be inferred from "m_halfedges")
std::vector<halfedge_descriptor_t> m_halfedges; // ... which point to vertex (note: can be used to infer edges too)
};
typedef std::vector<vertex_data_t> vertex_array_t;
typedef std::vector<edge_data_t> edge_array_t;
typedef std::vector<halfedge_data_t> halfedge_array_t;
typedef std::vector<face_data_t> face_array_t;
typedef array_iterator_t<face_array_t> face_array_iterator_t;
typedef array_iterator_t<vertex_array_t> vertex_array_iterator_t;
typedef array_iterator_t<edge_array_t> edge_array_iterator_t;
typedef array_iterator_t<halfedge_array_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<vertex_descriptor_t>& 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<vertex_descriptor_t> &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 <typename I = int>
I get_removed_elements(id_<I>)
{
return I(); // unused
}
const std::vector<vertex_descriptor_t>& get_removed_elements(id_<array_iterator_t<vertex_array_t>>) const;
const std::vector<edge_descriptor_t>& get_removed_elements(id_<array_iterator_t<edge_array_t>>) const;
const std::vector<halfedge_descriptor_t>& get_removed_elements(id_<array_iterator_t<halfedge_array_t>>) const;
const std::vector<face_descriptor_t>& get_removed_elements(id_<array_iterator_t<face_array_t>>) const;
//
template <typename I = int>
I elements_begin_(id_<I>)
{
return I(); // unused
}
const vertex_array_iterator_t elements_begin_(id_<vertex_array_iterator_t>, bool account_for_removed_elems = true) const;
const edge_array_iterator_t elements_begin_(id_<edge_array_iterator_t>, bool account_for_removed_elems = true) const;
const halfedge_array_iterator_t elements_begin_(id_<halfedge_array_iterator_t>, bool account_for_removed_elems = true) const;
const face_array_iterator_t elements_begin_(id_<face_array_iterator_t>, bool account_for_removed_elems = true) const;
// returns the number of removed mesh elements (vertices, edges, faces or halfedges) between [start, end)
template <typename I>
uint32_t count_removed_elements_in_range(const array_iterator_t<I>& start, const array_iterator_t<I>& 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_<array_iterator_t<I>> {}, false));
uint32_t n = 0;
for (auto elem_descr : get_removed_elements(id_<array_iterator_t<I>> {})) {
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<halfedge_descriptor_t>& get_halfedges_around_vertex(const vertex_descriptor_t v) const;
std::vector<vertex_descriptor_t> get_vertices_around_face(const face_descriptor_t f, uint32_t prepend_offset = 0) const;
void get_vertices_around_face(std::vector<vertex_descriptor_t>& vertex_descriptors, const face_descriptor_t f, uint32_t prepend_offset=0) const;
std::vector<vertex_descriptor_t> get_vertices_around_vertex(const vertex_descriptor_t v) const;
void get_vertices_around_vertex(std::vector<vertex_descriptor_t>& 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<halfedge_descriptor_t>& get_halfedges_around_face(const face_descriptor_t f) const;
const std::vector<face_descriptor_t> get_faces_around_face(const face_descriptor_t f, const std::vector<halfedge_descriptor_t>* halfedges_around_face_ = nullptr) const;
void get_faces_around_face( std::vector<face_descriptor_t>& faces_around_face, const face_descriptor_t f, const std::vector<halfedge_descriptor_t>* halfedges_around_face_ = nullptr) const;
uint32_t get_num_faces_around_face(const face_descriptor_t f, const std::vector<halfedge_descriptor_t>* 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<vertex_descriptor_t>& get_removed_vertices() const;
const std::vector<edge_descriptor_t>& get_removed_edges() const;
const std::vector<halfedge_descriptor_t>& get_removed_halfedges() const;
const std::vector<face_descriptor_t>& get_removed_faces() const;
private:
// member variables
// ----------------
std::vector<vertex_data_t> m_vertices;
std::vector<edge_data_t> m_edges;
std::vector<halfedge_data_t> m_halfedges;
std::vector<face_data_t> 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<face_descriptor_t> m_faces_removed;
std::vector<edge_descriptor_t> m_edges_removed;
std::vector<halfedge_descriptor_t> m_halfedges_removed;
std::vector<vertex_descriptor_t> 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 <typename V = face_array_t>
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<V>& 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<array_iterator_t<V>>(false));
element_descriptor_type raw_descriptor((std::uint32_t)diff); // std::distance(cbegin<array_iterator_t<V>>(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<V>& 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<array_iterator_t<V>>(false); //std::distance(cbegin<array_iterator_t<V>>(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 <typename I = array_iterator_t<V>>
I cend()
{
return cend(id_<I>()); // https://stackoverflow.com/questions/3052579/explicit-specialization-in-non-namespace-scope
}
template <typename I = array_iterator_t<V>>
I cbegin(bool account_for_removed_elems)
{
return cbegin(account_for_removed_elems, id_<I>());
}
private:
// postfix increment (i++)
// NOTE: This overide is private to simplify implementation, and we don't need it
array_iterator_t<V> operator++(int)
{
MCUT_ASSERT(false);
return cend<>();
}
template <typename I = array_iterator_t<V>>
I cend(id_<I>)
{
return I(); // unused stub
}
template <typename I = array_iterator_t<V>>
I cbegin(bool account_for_removed_elems, id_<I>)
{
return I(account_for_removed_elems); // unused stub
}
vertex_array_iterator_t cbegin(bool account_for_removed_elems, id_<vertex_array_iterator_t> = {});
vertex_array_iterator_t cend(id_<vertex_array_iterator_t>);
edge_array_iterator_t cbegin(bool account_for_removed_elems, id_<edge_array_iterator_t> = {});
edge_array_iterator_t cend(id_<edge_array_iterator_t>);
halfedge_array_iterator_t cbegin(bool account_for_removed_elems, id_<halfedge_array_iterator_t> = {});
halfedge_array_iterator_t cend(id_<halfedge_array_iterator_t>);
face_array_iterator_t cbegin(bool account_for_removed_elems, id_<face_array_iterator_t> = {});
face_array_iterator_t cend(id_<face_array_iterator_t>);
}; // 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<hmesh_t::edge_array_t> &iter,
typename std::iterator_traits<hmesh_t::array_iterator_t<hmesh_t::edge_array_t>>::difference_type n);
#endif
template <>
struct hash<vertex_descriptor_t> {
std::size_t operator()(const vertex_descriptor_t& k) const
{
return std::hash<typename vertex_descriptor_t::index_type>()(static_cast<typename vertex_descriptor_t::index_type>(k));
}
};
template <>
struct hash<edge_descriptor_t> {
std::size_t operator()(const edge_descriptor_t& k) const
{
return std::hash<typename edge_descriptor_t::index_type>()(static_cast<typename edge_descriptor_t::index_type>(k));
}
};
template <>
struct hash<halfedge_descriptor_t> {
std::size_t operator()(const halfedge_descriptor_t& k) const
{
return std::hash<typename halfedge_descriptor_t::index_type>()(static_cast<typename halfedge_descriptor_t::index_type>(k));
}
};
template <>
struct hash<face_descriptor_t> {
std::size_t operator()(const face_descriptor_t& k) const
{
return std::hash<typename face_descriptor_t::index_type>()(static_cast<typename face_descriptor_t::index_type>(k));
}
};
}
#endif // #ifndef MCUT_HALFEDGE_MESH_H_

View File

@@ -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: <http://www.gnu.org/licenses/>
* (B) Commercial license.
* - email: floyd.m.chitalu@gmail.com
*
* The commercial license options is for users that wish to use MCUT in
* their products for comercial purposes but do not wish to release their
* software products under the GPL license.
*
* Author(s) : Floyd M. Chitalu
*/
#ifndef MCUT_KERNEL_H
#define MCUT_KERNEL_H
#include <mcut/internal/bvh.h>
#include <mcut/internal/hmesh.h>
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
#include <mcut/internal/tpool.h>
#endif
#include <map>
#include <unordered_map>
#include <vector>
//
// 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<vec3> 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<hmesh_t> src_mesh = nullptr;
/*const*/ std::shared_ptr<hmesh_t> 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<fd_t, std::vector<fd_t>>* ps_face_to_potentially_intersecting_others = nullptr;
#if defined(USE_OIBVH)
const std::vector<bounding_box_t<vec3>>* source_hmesh_face_aabb_array_ptr = nullptr;
const std::vector<bounding_box_t<vec3>>* 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<vd_t> 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<fd_t> face_map;
};
struct output_mesh_info_t {
std::shared_ptr<hmesh_t> mesh;
std::vector<vd_t> 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_t> status;
#else
status_t status = status_t::SUCCESS;
#endif
logger_t logger;
// fragments
std::map<sm_frag_location_t, std::map<cm_patch_location_t, std::vector<std::shared_ptr<output_mesh_info_t>>>> connected_components;
std::map<sm_frag_location_t, std::vector<std::shared_ptr<output_mesh_info_t>>> unsealed_cc; // connected components before hole-filling
// patches
std::map<cm_patch_winding_order_t, std::vector<std::shared_ptr<output_mesh_info_t>>> inside_patches; // .. between neigbouring connected ccsponents (cs-sealing patches)
std::map<cm_patch_winding_order_t, std::vector<std::shared_ptr<output_mesh_info_t>>> 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<output_mesh_info_t> seamed_src_mesh;
std::shared_ptr<output_mesh_info_t> 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<floating_polygon_info_t>>
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<int>& fccmap, const hmesh_t& mesh, std::vector<int>& cc_to_vertex_count,
std::vector<int>& 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

View File

@@ -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: <http://www.gnu.org/licenses/>
* (B) Commercial license.
* - email: floyd.m.chitalu@gmail.com
*
* The commercial license options is for users that wish to use MCUT in
* their products for comercial purposes but do not wish to release their
* software products under the GPL license.
*
* Author(s) : Floyd M. Chitalu
*/
#ifndef MCUT_MATH_H_
#define MCUT_MATH_H_
#include <cmath>
#include <iostream>
#include <limits>
#include <memory>
#include <vector>
#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 <typename T = double>
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_<T>(x, y);
}
static int cardinality()
{
return 2;
}
bool operator==(const vec2_<T>& 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 <typename T = double>
class vec3_ : public vec2_<T> {
public:
vec3_()
: vec2_<T>(0.0, 0.0)
, m_z(0.0)
{
}
vec3_(const T& value)
: vec2_<T>(value, value)
, m_z(value)
{
}
vec3_(const T& x, const T& y, const T& z)
: vec2_<T>(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 <typename T = int>
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<T>((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<T> operator*(const matrix_t<T>& other) const
{
matrix_t<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<T> operator*(const double& s) const
{
matrix_t<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<T> operator/(const double& s) const
{
matrix_t<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<T> operator-(const matrix_t<T>& m) const
{
MCUT_ASSERT(m.rows() == this->rows());
MCUT_ASSERT(m.cols() == this->cols());
matrix_t<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<T> 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 <typename U>
std::ostream& operator<<(std::ostream& os, const matrix_t<U>& 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 <typename T>
const T& min(const T& a, const T& b)
{
return ((b < a) ? b : a);
}
template <typename T>
const T& max(const T& a, const T& b)
{
return ((a < b) ? b : a);
}
extern bool operator==(const vec3& a, const vec3& b);
template <typename T>
vec2_<T> compwise_min(const vec2_<T>& a, const vec2_<T>& b)
{
return vec2_<T>(min(a.x(), b.x()), min(a.y(), b.y()));
}
template <typename T>
vec2_<T> compwise_max(const vec2_<T>& a, const vec2_<T>& b)
{
return vec2_<T>(max(a.x(), b.x()), max(a.y(), b.y()));
}
template <typename T>
vec3_<T> compwise_min(const vec3_<T>& a, const vec3_<T>& b)
{
return vec3_<T>(min(a.x(), b.x()), min(a.y(), b.y()), min(a.z(), b.z()));
}
template <typename T>
vec3_<T> compwise_max(const vec3_<T>& a, const vec3_<T>& b)
{
return vec3_<T>(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 <typename vector_type>
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 <typename vector_type>
matrix_t<typename vector_type::element_type> outer_product(const vector_type& a, const vector_type& b)
{
matrix_t<typename vector_type::element_type> 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 <typename vector_type>
double squared_length(const vector_type& v)
{
return dot_product(v, v);
}
template <typename vector_type>
double length(const vector_type& v)
{
return square_root(squared_length(v));
}
template <typename vector_type>
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<vec3>& 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<vec2>& 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<vec3>& 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<vec2>& out, const std::vector<vec3>& polygon_vertices,
const vec3& polygon_normal, const int polygon_normal_largest_component);
void project_to_2d(std::vector<vec2>& out, const std::vector<vec3>& 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 <typename vector_type>
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<double>::max());
m_maximum = vector_type(-std::numeric_limits<double>::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<vector_type>& 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 <typename T>
inline bool intersect_bounding_boxes(const bounding_box_t<vec3_<T>>& a, const bounding_box_t<vec3_<T>>& b)
{
const vec3_<T>& amin = a.minimum();
const vec3_<T>& amax = a.maximum();
const vec3_<T>& bmin = b.minimum();
const vec3_<T>& 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<vec2>& bbox);
bool point_in_bounding_box(const vec3& point, const bounding_box_t<vec3>& bbox);
template <typename vector_type>
void make_bbox(bounding_box_t<vector_type>& 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_

View File

@@ -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: <http://www.gnu.org/licenses/>
* (B) Commercial license.
* - email: floyd.m.chitalu@gmail.com
*
* The commercial license options is for users that wish to use MCUT in
* their products for comercial purposes but do not wish to release their
* software products under the GPL license.
*
* Author(s) : Floyd M. Chitalu
*/
/**
* @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_t> 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_

View File

@@ -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: <http://www.gnu.org/licenses/>
* (B) Commercial license.
* - email: floyd.m.chitalu@gmail.com
*
* The commercial license options is for users that wish to use MCUT in
* their products for comercial purposes but do not wish to release their
* software products under the GPL license.
*
* Author(s) : Floyd M. Chitalu
*/
#ifndef MCUT_TIMER_H_
#define MCUT_TIMER_H_
#include <chrono>
#include <map>
#include <memory>
#include <stack>
#include <sstream>
#include "mcut/internal/utils.h"
#include "mcut/internal/tpool.h"
//#define PROFILING_BUILD
class mini_timer {
std::chrono::time_point<std::chrono::steady_clock> 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<std::chrono::steady_clock> now = std::chrono::steady_clock::now();
const std::chrono::milliseconds elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(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<std::unique_ptr<mini_timer>> g_thrd_loc_timerstack;
#if defined(PROFILING_BUILD)
#define TIMESTACK_PUSH(name) \
g_thrd_loc_timerstack.push(std::unique_ptr<mini_timer>(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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,258 @@
/**
* Copyright (c) 2021-2022 Floyd M. Chitalu.
* All rights reserved.
*
* NOTE: This file is licensed under GPL-3.0-or-later (default).
* A commercial license can be purchased from Floyd M. Chitalu.
*
* License details:
*
* (A) GNU General Public License ("GPL"); a copy of which you should have
* recieved with this file.
* - see also: <http://www.gnu.org/licenses/>
* (B) Commercial license.
* - email: floyd.m.chitalu@gmail.com
*
* The commercial license options is for users that wish to use MCUT in
* their products for comercial purposes but do not wish to release their
* software products under the GPL license.
*
* Author(s) : Floyd M. Chitalu
*/
#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 <fstream>
#include <iostream>
#include <sstream>
#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 <class T> // 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<ManipFn>(std::flush) || manip == static_cast<ManipFn>(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 <typename T>
struct pair : std::pair<T, T> {
pair(const T a, const T b)
: std::pair<T, T>(a < b ? a : b, a < b ? b : a)
{
}
};
template <typename T>
pair<T> make_pair(const T a, const T b)
{
return pair<T>(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() << std::flush; \
}
// used to marked/label unused function parameters to prevent warnings
#define UNUSED(x) [&x] {}()
#endif // MCUT_UTILS_H_

File diff suppressed because it is too large Load Diff

View File

@@ -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: <http://www.gnu.org/licenses/>
* (B) Commercial license.
* - email: floyd.m.chitalu@gmail.com
*
* The commercial license options is for users that wish to use MCUT in
* their products for comercial purposes but do not wish to release their
* software products under the GPL license.
*
* Author(s) : Floyd M. Chitalu
*/
/**
* @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 <stddef.h> // 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 <stdint.h> // integer types
#endif
#endif // !defined(MC_NO_STDINT_H)
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
#endif