From f3e2a9a131ac561386a0eb881a87f9ec9783e884 Mon Sep 17 00:00:00 2001 From: arlauwer Date: Sat, 30 Sep 2023 16:07:58 +0200 Subject: [PATCH 01/51] Add NestedDensityTreePolicy - changed DensityTreePolicy & TreePolicy consequently - added extra contains & intersects to Box --- SKIRT/core/DensityTreePolicy.cpp | 6 +- SKIRT/core/DensityTreePolicy.hpp | 7 +-- SKIRT/core/NestedDensityTreePolicy.cpp | 52 ++++++++++++++++ SKIRT/core/NestedDensityTreePolicy.hpp | 84 ++++++++++++++++++++++++++ SKIRT/core/SimulationItemRegistry.cpp | 3 +- SKIRT/core/TreePolicy.hpp | 6 ++ SKIRT/utils/Box.hpp | 18 ++++++ 7 files changed, 169 insertions(+), 7 deletions(-) create mode 100644 SKIRT/core/NestedDensityTreePolicy.cpp create mode 100644 SKIRT/core/NestedDensityTreePolicy.hpp mode change 100644 => 100755 SKIRT/core/SimulationItemRegistry.cpp diff --git a/SKIRT/core/DensityTreePolicy.cpp b/SKIRT/core/DensityTreePolicy.cpp index a943dad5..e97d526b 100644 --- a/SKIRT/core/DensityTreePolicy.cpp +++ b/SKIRT/core/DensityTreePolicy.cpp @@ -20,6 +20,8 @@ void DensityTreePolicy::setupSelfBefore() { + TreePolicy::setupSelfBefore(); + // get the random generator and the number of samples (not a big effort, so we do this even if we don't need it) _random = find(); _numSamples = find()->numDensitySamples(); @@ -83,7 +85,7 @@ void DensityTreePolicy::setupSelfBefore() //////////////////////////////////////////////////////////////////// -bool DensityTreePolicy::needsSubdivide(TreeNode* node) +bool DensityTreePolicy::needsSubdivide(TreeNode* node, int level) { // results for the sampled mass or number densities, if applicable double rho = 0.; // dust mass density @@ -215,7 +217,7 @@ vector DensityTreePolicy::constructTree(TreeNode* root) size_t currentChunkSize = min(logEvalChunkSize, numIndices); for (size_t l = firstIndex; l != firstIndex + currentChunkSize; ++l) { - if (needsSubdivide(nodev[lbeg + l])) divide[l] = 1.; + if (needsSubdivide(nodev[lbeg + l], level)) divide[l] = 1.; } log->infoIfElapsed("Evaluation for level " + std::to_string(level) + ": ", currentChunkSize); firstIndex += currentChunkSize; diff --git a/SKIRT/core/DensityTreePolicy.hpp b/SKIRT/core/DensityTreePolicy.hpp index 8f676a22..d2ea2aa3 100644 --- a/SKIRT/core/DensityTreePolicy.hpp +++ b/SKIRT/core/DensityTreePolicy.hpp @@ -123,14 +123,13 @@ class DensityTreePolicy : public TreePolicy, public MaterialWavelengthRangeInter evaluate the configured criteria. */ void setupSelfBefore() override; -private: +public: /** This function returns true if the given node needs to be subdivided according to the criteria configured for this policy, and false otherwise. The minimum and maximum level are not checked, because this function is never called for nodes that don't conform to the level criteria. */ - bool needsSubdivide(TreeNode* node); + virtual bool needsSubdivide(TreeNode* node, int level); -public: /** This function constructs the hierarchical tree and all (interconnected) nodes forming the tree as described for the corresponding pure virtual function in the base class. The implementation for this class loops over the tree subdivision levels (up to the maximum @@ -177,7 +176,7 @@ class DensityTreePolicy : public TreePolicy, public MaterialWavelengthRangeInter bool _hasElectronFraction{false}; bool _hasGasFraction{false}; - // cashed values for each material type (valid if corresponding flag is enabled) + // cached values for each material type (valid if corresponding flag is enabled) double _dustMass{0.}; double _dustKappa{0.}; double _electronNumber{0.}; diff --git a/SKIRT/core/NestedDensityTreePolicy.cpp b/SKIRT/core/NestedDensityTreePolicy.cpp new file mode 100644 index 00000000..1d890f2a --- /dev/null +++ b/SKIRT/core/NestedDensityTreePolicy.cpp @@ -0,0 +1,52 @@ +/*////////////////////////////////////////////////////////////////// +//// The SKIRT project -- advanced radiative transfer //// +//// © Astronomical Observatory, Ghent University //// +///////////////////////////////////////////////////////////////// */ + +#include "NestedDensityTreePolicy.hpp" +#include "FatalError.hpp" +#include "MediumSystem.hpp" +#include "TreeNode.hpp" +#include "TreeSpatialGrid.hpp" + +void NestedDensityTreePolicy::setupSelfBefore() +{ + DensityTreePolicy::setupSelfBefore(); + + _inner = Box(_minXInner, _minYInner, _minZInner, _maxXInner, _maxYInner, _maxZInner); + + auto ms = find(false); + auto grid = ms->find(false); + Box _outer = grid->boundingBox(); + + if (!_outer.contains(_inner)) throw FATALERROR("The nested density tree is not contained in the outer region"); + + _outerMinLevel = _minLevel; + _outerMaxLevel = _maxLevel; +} + +void NestedDensityTreePolicy::setupSelfAfter() +{ + DensityTreePolicy::setupSelfAfter(); + + _minLevel = min(_minLevel, _nestedTree->_minLevel); + _maxLevel = max(_maxLevel, _nestedTree->_maxLevel); +} + +bool NestedDensityTreePolicy::needsSubdivide(TreeNode* node, int level) +{ + if (_inner.intersects(node->extent())) + { + if (level < _nestedTree->_minLevel) return true; + if (level >= _nestedTree->_maxLevel) return false; + + return _nestedTree->needsSubdivide(node, level); + } + else + { + if (level < _outerMinLevel) return true; + if (level >= _outerMaxLevel) return false; + + return DensityTreePolicy::needsSubdivide(node, level); + } +} \ No newline at end of file diff --git a/SKIRT/core/NestedDensityTreePolicy.hpp b/SKIRT/core/NestedDensityTreePolicy.hpp new file mode 100644 index 00000000..78e0f8e3 --- /dev/null +++ b/SKIRT/core/NestedDensityTreePolicy.hpp @@ -0,0 +1,84 @@ +/*////////////////////////////////////////////////////////////////// +//// The SKIRT project -- advanced radiative transfer //// +//// © Astronomical Observatory, Ghent University //// +///////////////////////////////////////////////////////////////// */ + +#ifndef NESTEDDENSITYTREEPOLICY_HPP +#define NESTEDDENSITYTREEPOLICY_HPP + +#include "Box.hpp" +#include "DensityTreePolicy.hpp" + +/** NestedDensityTreePolicy is a tree policy that allows for nesting DensityTreePolicies, enabling + users to define a region containing another (nested) density tree. + + This class inherits from DensityTreePolicy and uses all the inherited properties to classify the + outer region. Additionally, it introduces an additional property called nestedTree, which is + also a DensityTreePolicy. The nestedTree property decides the properties of the inner region. + + If a TreeNode intersects with the inner 'nested' region the node will be subdivided based on the + properties of the nestedTree. This means that even TreeNodes almost fully outside the inner + region can be subdivided if they have a non-zero intersection with the inner region. */ +class NestedDensityTreePolicy : public DensityTreePolicy +{ + ITEM_CONCRETE(NestedDensityTreePolicy, DensityTreePolicy, + "a tree grid construction policy using a nested density tree policy") + ATTRIBUTE_TYPE_DISPLAYED_IF(NestedDensityTreePolicy, "Level2") + + PROPERTY_DOUBLE(minXInner, "the start point of the inner box in the X direction") + ATTRIBUTE_QUANTITY(minXInner, "length") + + PROPERTY_DOUBLE(maxXInner, "the end point of the inner box in the X direction") + ATTRIBUTE_QUANTITY(maxXInner, "length") + + PROPERTY_DOUBLE(minYInner, "the start point of the inner box in the Y direction") + ATTRIBUTE_QUANTITY(minYInner, "length") + + PROPERTY_DOUBLE(maxYInner, "the end point of the inner box in the Y direction") + ATTRIBUTE_QUANTITY(maxYInner, "length") + + PROPERTY_DOUBLE(minZInner, "the start point of the inner box in the Z direction") + ATTRIBUTE_QUANTITY(minZInner, "length") + + PROPERTY_DOUBLE(maxZInner, "the end point of the inner box in the Z direction") + ATTRIBUTE_QUANTITY(maxZInner, "length") + + PROPERTY_ITEM(nestedTree, DensityTreePolicy, "the nested density tree inside the inner region") + + ITEM_END() + + //============= Construction - Setup - Destruction ============= + +protected: + /** This function checks whether the inner region is inside the outer region. It also copies the + * minLevel and maxLevel as these determine the refinement of the outer region. */ + void setupSelfBefore() override; + + /** This function calculates the minLevel and maxLevel values used in the inherited function + constructTree. The minLevel is the minimum value among all the minLevel of all the child + nestedTrees. The maxLevel is the maximum value among all the maxLevel of all the child + nestedTrees. */ + void setupSelfAfter() override; + + //======================== Other Functions ======================= + +public: + /** This function determines whether a given node needs to be subdivided for a given level. If + the node intersects with the inner region, the level must lie between the minLevel and + maxLevel of the inner region. If the node is not within the inner region, it will use the + baseMinLevel and baseMaxLevel as bounds. If the node is within all the refinement bounds + then it will use the needsSubdidive function of the corresponding DensityTreePolicy. */ + bool needsSubdivide(TreeNode* node, int level) override; + + //======================== Data Members ======================== + +private: + // the inner region box + Box _inner; + + // the refinement bounds for the outer region + int _outerMinLevel; + int _outerMaxLevel; +}; + +#endif \ No newline at end of file diff --git a/SKIRT/core/SimulationItemRegistry.cpp b/SKIRT/core/SimulationItemRegistry.cpp old mode 100644 new mode 100755 index 64eab66e..97318734 --- a/SKIRT/core/SimulationItemRegistry.cpp +++ b/SKIRT/core/SimulationItemRegistry.cpp @@ -172,6 +172,7 @@ #include "MollweideProjection.hpp" #include "MonteCarloSimulation.hpp" #include "MultiGaussianExpansionGeometry.hpp" +#include "NestedDensityTreePolicy.hpp" #include "NestedLogWavelengthGrid.hpp" #include "NetzerAngularDistribution.hpp" #include "NoPolarizationProfile.hpp" @@ -373,7 +374,6 @@ SimulationItemRegistry::SimulationItemRegistry(string version, string format) // wavelength distributions ItemRegistry::add(); - ItemRegistry::add(); ItemRegistry::add(); ItemRegistry::add(); ItemRegistry::add(); @@ -484,6 +484,7 @@ SimulationItemRegistry::SimulationItemRegistry(string version, string format) // spatial grid policies ItemRegistry::add(); ItemRegistry::add(); + ItemRegistry::add(); ItemRegistry::add(); // one-dimensional meshes for spatial grids diff --git a/SKIRT/core/TreePolicy.hpp b/SKIRT/core/TreePolicy.hpp index 3383c0c5..92d385f3 100644 --- a/SKIRT/core/TreePolicy.hpp +++ b/SKIRT/core/TreePolicy.hpp @@ -8,6 +8,7 @@ #include "SimulationItem.hpp" class TreeNode; +class NestedDensityTreePolicy; ////////////////////////////////////////////////////////////////////// @@ -58,6 +59,11 @@ class TreePolicy : public SimulationItem neighbors with the largest overlapping border area are listed first, increasing (on average) the probability of locating the correct neighbor early in the list. */ virtual vector constructTree(TreeNode* root) = 0; + + /** + * The NestedDensityTreePolicy needs access to the minLevel and maxLevel. + */ + friend NestedDensityTreePolicy; }; ////////////////////////////////////////////////////////////////////// diff --git a/SKIRT/utils/Box.hpp b/SKIRT/utils/Box.hpp index b8df04f9..9f6f9802 100644 --- a/SKIRT/utils/Box.hpp +++ b/SKIRT/utils/Box.hpp @@ -108,6 +108,14 @@ class Box return x >= _xmin && x <= _xmax && y >= _ymin && y <= _ymax && z >= _zmin && z <= _zmax; } + /** This function returns true if the given box is inside this box, false otherwise. */ + inline bool contains(const Box& box) const + { + return xmin() <= box.xmin() && xmax() >= box.xmax() && + ymin() <= box.ymin() && ymax() >= box.ymax() && + zmin() <= box.zmin() && zmax() >= box.zmax(); + } + /** This function returns the volume \f$(x_\text{max}-x_\text{min}) \times (y_\text{max}-y_\text{min}) \times (z_\text{max}-z_\text{min})\f$ of the box. */ inline double volume() const { return (_xmax - _xmin) * (_ymax - _ymin) * (_zmax - _zmin); } @@ -159,6 +167,16 @@ class Box k = std::max(0, std::min(nz - 1, static_cast(nz * (r.z() - _zmin) / (_zmax - _zmin)))); } + /** This function returns true if the given box and this box have a non-zero intersection, false + * otherwise. */ + inline bool intersects(const Box& box) const + { + if (xmax() < box.xmin() || box.xmax() < xmin()) return false; + if (ymax() < box.ymin() || box.ymax() < ymin()) return false; + if (zmax() < box.zmin() || box.zmax() < zmin()) return false; + return true; + } + /** This function intersects the receiving axis-aligned bounding box with a ray (half-line) defined by the specified starting position \f$\bf{r}\f$ and direction \f$\bf{k}\f$. If the ray intersects the box, the function returns true after storing the near and far From a38ae98bcd40418fa49558e4d682b22d2ea3fa51 Mon Sep 17 00:00:00 2001 From: Peter Camps Date: Tue, 3 Oct 2023 11:44:05 +0200 Subject: [PATCH 02/51] Fix formatting, warning, and minor merge error --- SKIRT/core/DensityTreePolicy.cpp | 2 +- SKIRT/core/SimulationItemRegistry.cpp | 1 + SKIRT/core/TreePolicy.hpp | 4 +--- SKIRT/utils/Box.hpp | 9 ++++----- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/SKIRT/core/DensityTreePolicy.cpp b/SKIRT/core/DensityTreePolicy.cpp index e97d526b..fbe11997 100644 --- a/SKIRT/core/DensityTreePolicy.cpp +++ b/SKIRT/core/DensityTreePolicy.cpp @@ -85,7 +85,7 @@ void DensityTreePolicy::setupSelfBefore() //////////////////////////////////////////////////////////////////// -bool DensityTreePolicy::needsSubdivide(TreeNode* node, int level) +bool DensityTreePolicy::needsSubdivide(TreeNode* node, int /*level*/) { // results for the sampled mass or number densities, if applicable double rho = 0.; // dust mass density diff --git a/SKIRT/core/SimulationItemRegistry.cpp b/SKIRT/core/SimulationItemRegistry.cpp index 97318734..ecbcab06 100755 --- a/SKIRT/core/SimulationItemRegistry.cpp +++ b/SKIRT/core/SimulationItemRegistry.cpp @@ -374,6 +374,7 @@ SimulationItemRegistry::SimulationItemRegistry(string version, string format) // wavelength distributions ItemRegistry::add(); + ItemRegistry::add(); ItemRegistry::add(); ItemRegistry::add(); ItemRegistry::add(); diff --git a/SKIRT/core/TreePolicy.hpp b/SKIRT/core/TreePolicy.hpp index 92d385f3..50a34c2c 100644 --- a/SKIRT/core/TreePolicy.hpp +++ b/SKIRT/core/TreePolicy.hpp @@ -60,9 +60,7 @@ class TreePolicy : public SimulationItem average) the probability of locating the correct neighbor early in the list. */ virtual vector constructTree(TreeNode* root) = 0; - /** - * The NestedDensityTreePolicy needs access to the minLevel and maxLevel. - */ + /** The NestedDensityTreePolicy needs access to the minLevel and maxLevel. */ friend NestedDensityTreePolicy; }; diff --git a/SKIRT/utils/Box.hpp b/SKIRT/utils/Box.hpp index 9f6f9802..8e321491 100644 --- a/SKIRT/utils/Box.hpp +++ b/SKIRT/utils/Box.hpp @@ -111,9 +111,8 @@ class Box /** This function returns true if the given box is inside this box, false otherwise. */ inline bool contains(const Box& box) const { - return xmin() <= box.xmin() && xmax() >= box.xmax() && - ymin() <= box.ymin() && ymax() >= box.ymax() && - zmin() <= box.zmin() && zmax() >= box.zmax(); + return xmin() <= box.xmin() && xmax() >= box.xmax() && ymin() <= box.ymin() && ymax() >= box.ymax() + && zmin() <= box.zmin() && zmax() >= box.zmax(); } /** This function returns the volume \f$(x_\text{max}-x_\text{min}) \times @@ -167,8 +166,8 @@ class Box k = std::max(0, std::min(nz - 1, static_cast(nz * (r.z() - _zmin) / (_zmax - _zmin)))); } - /** This function returns true if the given box and this box have a non-zero intersection, false - * otherwise. */ + /** This function returns true if the given box and this box have a non-zero intersection, + false otherwise. */ inline bool intersects(const Box& box) const { if (xmax() < box.xmin() || box.xmax() < xmin()) return false; From b76f33dbcf35313c1c82def1ebcab8087a84168e Mon Sep 17 00:00:00 2001 From: Peter Camps Date: Wed, 4 Oct 2023 11:38:19 +0200 Subject: [PATCH 03/51] move min/max level test into needsSubdivide() --- SKIRT/core/DensityTreePolicy.cpp | 30 +++++++------------ SKIRT/core/DensityTreePolicy.hpp | 18 +++++------ SKIRT/core/NestedDensityTreePolicy.cpp | 41 ++++++++------------------ SKIRT/core/NestedDensityTreePolicy.hpp | 28 +++++------------- SKIRT/core/TreePolicy.hpp | 4 --- 5 files changed, 38 insertions(+), 83 deletions(-) diff --git a/SKIRT/core/DensityTreePolicy.cpp b/SKIRT/core/DensityTreePolicy.cpp index fbe11997..bdc5cd6b 100644 --- a/SKIRT/core/DensityTreePolicy.cpp +++ b/SKIRT/core/DensityTreePolicy.cpp @@ -85,8 +85,12 @@ void DensityTreePolicy::setupSelfBefore() //////////////////////////////////////////////////////////////////// -bool DensityTreePolicy::needsSubdivide(TreeNode* node, int /*level*/) +bool DensityTreePolicy::needsSubdivide(TreeNode* node) { + // handle minimum and maximum level + if (node->level() < minLevel()) return true; + if (node->level() >= maxLevel()) return false; + // results for the sampled mass or number densities, if applicable double rho = 0.; // dust mass density double rhomin = DBL_MAX; // smallest sample for dust mass density @@ -181,28 +185,13 @@ vector DensityTreePolicy::constructTree(TreeNode* root) // initialize the tree node list with the root node as the first item vector nodev{root}; - // recursively subdivide the root node until the minimum level has been reached + // initialize iteration variables to level 0 int level = 0; // current level size_t lbeg = 0; // node index range for the current level; size_t lend = 1; // at level 0, the node list contains just the root node - while (level != minLevel()) - { - log->info("Subdividing level " + std::to_string(level) + ": " + std::to_string(lend - lbeg) + " nodes"); - log->infoSetElapsed(lend - lbeg); - for (size_t l = lbeg; l != lend; ++l) - { - nodev[l]->subdivide(nodev); - if ((l + 1) % logDivideChunkSize == 0) - log->infoIfElapsed("Subdividing level " + std::to_string(level) + ": ", logDivideChunkSize); - } - // update iteration variables to the next level - level++; - lbeg = lend; - lend = nodev.size(); - } - // recursively subdivide the nodes beyond the minimum level until all nodes satisfy the configured criteria - while (level != maxLevel() && lend != lbeg) + // recursively subdivide nodes until all nodes satisfy the configured criteria + while (lend != lbeg) { size_t numEvalNodes = lend - lbeg; log->info("Subdividing level " + std::to_string(level) + ": " + std::to_string(numEvalNodes) + " nodes"); @@ -217,7 +206,7 @@ vector DensityTreePolicy::constructTree(TreeNode* root) size_t currentChunkSize = min(logEvalChunkSize, numIndices); for (size_t l = firstIndex; l != firstIndex + currentChunkSize; ++l) { - if (needsSubdivide(nodev[lbeg + l], level)) divide[l] = 1.; + if (needsSubdivide(nodev[lbeg + l])) divide[l] = 1.; } log->infoIfElapsed("Evaluation for level " + std::to_string(level) + ": ", currentChunkSize); firstIndex += currentChunkSize; @@ -240,6 +229,7 @@ vector DensityTreePolicy::constructTree(TreeNode* root) log->infoIfElapsed("Subdivision for level " + std::to_string(level) + ": ", logDivideChunkSize); } } + // update iteration variables to the next level level++; lbeg = lend; diff --git a/SKIRT/core/DensityTreePolicy.hpp b/SKIRT/core/DensityTreePolicy.hpp index d2ea2aa3..22db14b5 100644 --- a/SKIRT/core/DensityTreePolicy.hpp +++ b/SKIRT/core/DensityTreePolicy.hpp @@ -125,23 +125,21 @@ class DensityTreePolicy : public TreePolicy, public MaterialWavelengthRangeInter public: /** This function returns true if the given node needs to be subdivided according to the - criteria configured for this policy, and false otherwise. The minimum and maximum level are - not checked, because this function is never called for nodes that don't conform to the - level criteria. */ - virtual bool needsSubdivide(TreeNode* node, int level); + criteria configured for this policy, including minimum and maximum level, and false + otherwise. */ + virtual bool needsSubdivide(TreeNode* node); /** This function constructs the hierarchical tree and all (interconnected) nodes forming the tree as described for the corresponding pure virtual function in the base class. The - implementation for this class loops over the tree subdivision levels (up to the maximum - level configured in the TreePolicy base class). For each level, the function alternates - between evaluating all of the nodes (i.e. determining which nodes need subdivision) and - actually subdividing the nodes that need it. + implementation for this class loops over the tree subdivision levels. For each level, the + function alternates between evaluating all of the nodes (i.e. determining which nodes need + subdivision) and actually subdividing the nodes that need it. These operations are split over two phases because the first one can be parallelized (the only output is a Boolean flag), while the second one cannot (the tree structure is updated in various ways). Parallelizing the first operation is often meaningful, because - determining whether a node needs subdivision can be resource-intensive (for example, it may - require sampling densities in the source distribution). */ + determining whether a node needs subdivision can be resource-intensive. For example, it may + require sampling densities in the source distribution. */ vector constructTree(TreeNode* root) override; //======================== Other Functions ======================= diff --git a/SKIRT/core/NestedDensityTreePolicy.cpp b/SKIRT/core/NestedDensityTreePolicy.cpp index 1d890f2a..63b437d9 100644 --- a/SKIRT/core/NestedDensityTreePolicy.cpp +++ b/SKIRT/core/NestedDensityTreePolicy.cpp @@ -4,10 +4,12 @@ ///////////////////////////////////////////////////////////////// */ #include "NestedDensityTreePolicy.hpp" -#include "FatalError.hpp" +#include "Log.hpp" #include "MediumSystem.hpp" +#include "SpatialGrid.hpp" #include "TreeNode.hpp" -#include "TreeSpatialGrid.hpp" + +//////////////////////////////////////////////////////////////////// void NestedDensityTreePolicy::setupSelfBefore() { @@ -16,37 +18,18 @@ void NestedDensityTreePolicy::setupSelfBefore() _inner = Box(_minXInner, _minYInner, _minZInner, _maxXInner, _maxYInner, _maxZInner); auto ms = find(false); - auto grid = ms->find(false); - Box _outer = grid->boundingBox(); - - if (!_outer.contains(_inner)) throw FATALERROR("The nested density tree is not contained in the outer region"); - - _outerMinLevel = _minLevel; - _outerMaxLevel = _maxLevel; + if (!ms->grid()->boundingBox().contains(_inner)) + find()->warning("The nested density tree policy region is not fully inside the spatial grid domain"); } -void NestedDensityTreePolicy::setupSelfAfter() -{ - DensityTreePolicy::setupSelfAfter(); - - _minLevel = min(_minLevel, _nestedTree->_minLevel); - _maxLevel = max(_maxLevel, _nestedTree->_maxLevel); -} +//////////////////////////////////////////////////////////////////// -bool NestedDensityTreePolicy::needsSubdivide(TreeNode* node, int level) +bool NestedDensityTreePolicy::needsSubdivide(TreeNode* node) { if (_inner.intersects(node->extent())) - { - if (level < _nestedTree->_minLevel) return true; - if (level >= _nestedTree->_maxLevel) return false; - - return _nestedTree->needsSubdivide(node, level); - } + return _nestedTree->needsSubdivide(node); else - { - if (level < _outerMinLevel) return true; - if (level >= _outerMaxLevel) return false; + return DensityTreePolicy::needsSubdivide(node); +} - return DensityTreePolicy::needsSubdivide(node, level); - } -} \ No newline at end of file +//////////////////////////////////////////////////////////////////// diff --git a/SKIRT/core/NestedDensityTreePolicy.hpp b/SKIRT/core/NestedDensityTreePolicy.hpp index 78e0f8e3..cab70084 100644 --- a/SKIRT/core/NestedDensityTreePolicy.hpp +++ b/SKIRT/core/NestedDensityTreePolicy.hpp @@ -10,7 +10,7 @@ #include "DensityTreePolicy.hpp" /** NestedDensityTreePolicy is a tree policy that allows for nesting DensityTreePolicies, enabling - users to define a region containing another (nested) density tree. + users to define a region containing another (nested) density tree. This class inherits from DensityTreePolicy and uses all the inherited properties to classify the outer region. Additionally, it introduces an additional property called nestedTree, which is @@ -50,35 +50,23 @@ class NestedDensityTreePolicy : public DensityTreePolicy //============= Construction - Setup - Destruction ============= protected: - /** This function checks whether the inner region is inside the outer region. It also copies the - * minLevel and maxLevel as these determine the refinement of the outer region. */ + /** This function checks whether the inner region is inside the outer region. */ void setupSelfBefore() override; - /** This function calculates the minLevel and maxLevel values used in the inherited function - constructTree. The minLevel is the minimum value among all the minLevel of all the child - nestedTrees. The maxLevel is the maximum value among all the maxLevel of all the child - nestedTrees. */ - void setupSelfAfter() override; - //======================== Other Functions ======================= public: - /** This function determines whether a given node needs to be subdivided for a given level. If - the node intersects with the inner region, the level must lie between the minLevel and - maxLevel of the inner region. If the node is not within the inner region, it will use the - baseMinLevel and baseMaxLevel as bounds. If the node is within all the refinement bounds - then it will use the needsSubdidive function of the corresponding DensityTreePolicy. */ - bool needsSubdivide(TreeNode* node, int level) override; + /** This function determines whether the given node needs to be subdivided. Depending on + whether the node intersects with the inner region or not, the request is passed to the + needsSubdivide() function of the nested policy or to the needsSubdivide() function of our + base class. */ + bool needsSubdivide(TreeNode* node) override; //======================== Data Members ======================== private: // the inner region box Box _inner; - - // the refinement bounds for the outer region - int _outerMinLevel; - int _outerMaxLevel; }; -#endif \ No newline at end of file +#endif diff --git a/SKIRT/core/TreePolicy.hpp b/SKIRT/core/TreePolicy.hpp index 50a34c2c..3383c0c5 100644 --- a/SKIRT/core/TreePolicy.hpp +++ b/SKIRT/core/TreePolicy.hpp @@ -8,7 +8,6 @@ #include "SimulationItem.hpp" class TreeNode; -class NestedDensityTreePolicy; ////////////////////////////////////////////////////////////////////// @@ -59,9 +58,6 @@ class TreePolicy : public SimulationItem neighbors with the largest overlapping border area are listed first, increasing (on average) the probability of locating the correct neighbor early in the list. */ virtual vector constructTree(TreeNode* root) = 0; - - /** The NestedDensityTreePolicy needs access to the minLevel and maxLevel. */ - friend NestedDensityTreePolicy; }; ////////////////////////////////////////////////////////////////////// From c597e400232737b1d063183ee67e8d7c2056f5ab Mon Sep 17 00:00:00 2001 From: Peter Camps Date: Wed, 4 Oct 2023 16:32:02 +0200 Subject: [PATCH 04/51] streamline property names and add documentation --- SKIRT/core/NestedDensityTreePolicy.cpp | 9 ++-- SKIRT/core/NestedDensityTreePolicy.hpp | 62 ++++++++++++++++---------- 2 files changed, 43 insertions(+), 28 deletions(-) diff --git a/SKIRT/core/NestedDensityTreePolicy.cpp b/SKIRT/core/NestedDensityTreePolicy.cpp index 63b437d9..379758d3 100644 --- a/SKIRT/core/NestedDensityTreePolicy.cpp +++ b/SKIRT/core/NestedDensityTreePolicy.cpp @@ -5,7 +5,6 @@ #include "NestedDensityTreePolicy.hpp" #include "Log.hpp" -#include "MediumSystem.hpp" #include "SpatialGrid.hpp" #include "TreeNode.hpp" @@ -15,10 +14,10 @@ void NestedDensityTreePolicy::setupSelfBefore() { DensityTreePolicy::setupSelfBefore(); - _inner = Box(_minXInner, _minYInner, _minZInner, _maxXInner, _maxYInner, _maxZInner); + _inner = Box(_innerMinX, _innerMinY, _innerMinZ, _innerMaxX, _innerMaxY, _innerMaxZ); - auto ms = find(false); - if (!ms->grid()->boundingBox().contains(_inner)) + auto grid = find(false); + if (!grid->boundingBox().contains(_inner)) find()->warning("The nested density tree policy region is not fully inside the spatial grid domain"); } @@ -27,7 +26,7 @@ void NestedDensityTreePolicy::setupSelfBefore() bool NestedDensityTreePolicy::needsSubdivide(TreeNode* node) { if (_inner.intersects(node->extent())) - return _nestedTree->needsSubdivide(node); + return _innerPolicy->needsSubdivide(node); else return DensityTreePolicy::needsSubdivide(node); } diff --git a/SKIRT/core/NestedDensityTreePolicy.hpp b/SKIRT/core/NestedDensityTreePolicy.hpp index cab70084..3944e272 100644 --- a/SKIRT/core/NestedDensityTreePolicy.hpp +++ b/SKIRT/core/NestedDensityTreePolicy.hpp @@ -9,41 +9,57 @@ #include "Box.hpp" #include "DensityTreePolicy.hpp" -/** NestedDensityTreePolicy is a tree policy that allows for nesting DensityTreePolicies, enabling - users to define a region containing another (nested) density tree. - - This class inherits from DensityTreePolicy and uses all the inherited properties to classify the - outer region. Additionally, it introduces an additional property called nestedTree, which is - also a DensityTreePolicy. The nestedTree property decides the properties of the inner region. - - If a TreeNode intersects with the inner 'nested' region the node will be subdivided based on the - properties of the nestedTree. This means that even TreeNodes almost fully outside the inner - region can be subdivided if they have a non-zero intersection with the inner region. */ +/** NestedDensityTreePolicy is a DensityTreePolicy policy that allows defining separate subdivision + criteria in a given subregion of the spatial grid. This can be used, for example, to specify + a higher resolution in a given region of interest. + + The class inherits from DensityTreePolicy and uses all the inherited properties to classify the + outer region. Additional \em innerMin/innerMax coordinate properties define the bounding box of + the inner region. Finally, the additional property \em innerPolicy, which is also expected to + be a DensityTreePolicy instance, specifies the subdivision criteria for the inner region. + + If a TreeNode intersects with the inner region the node will be subdivided based on the + criteria defined by the \em innerPolicy. This means that even TreeNodes that are almost fully + outside the inner region can be subdivided by those criteria if they have a non-zero + intersection with the inner region. + + It is not meaningful to specify an inner region that extends outside of the spatial grid + bounding box, because none of the tree nodes will intersect those outside areas. Therefore, a + warning is issued if the inner region is not fully inside the spatial grid domain. + + Recursive nesting + + By default, the inner policy is a regular DensityTreePolicy instance. However, because + NestedDensityTreePolicy inherits DensityTreePolicy, it is also possible to again select a + NestedDensityTreePolicy instance as the inner policy, leading to recursive nesting. While this + is not a recommended use case, it would allow specifying recursively inreasing resolution in + nested, successively smaller regions of the spatial domain. */ class NestedDensityTreePolicy : public DensityTreePolicy { ITEM_CONCRETE(NestedDensityTreePolicy, DensityTreePolicy, "a tree grid construction policy using a nested density tree policy") ATTRIBUTE_TYPE_DISPLAYED_IF(NestedDensityTreePolicy, "Level2") - PROPERTY_DOUBLE(minXInner, "the start point of the inner box in the X direction") - ATTRIBUTE_QUANTITY(minXInner, "length") + PROPERTY_DOUBLE(innerMinX, "the start point of the inner box in the X direction") + ATTRIBUTE_QUANTITY(innerMinX, "length") - PROPERTY_DOUBLE(maxXInner, "the end point of the inner box in the X direction") - ATTRIBUTE_QUANTITY(maxXInner, "length") + PROPERTY_DOUBLE(innerMaxX, "the end point of the inner box in the X direction") + ATTRIBUTE_QUANTITY(innerMaxX, "length") - PROPERTY_DOUBLE(minYInner, "the start point of the inner box in the Y direction") - ATTRIBUTE_QUANTITY(minYInner, "length") + PROPERTY_DOUBLE(innerMinY, "the start point of the inner box in the Y direction") + ATTRIBUTE_QUANTITY(innerMinY, "length") - PROPERTY_DOUBLE(maxYInner, "the end point of the inner box in the Y direction") - ATTRIBUTE_QUANTITY(maxYInner, "length") + PROPERTY_DOUBLE(innerMaxY, "the end point of the inner box in the Y direction") + ATTRIBUTE_QUANTITY(innerMaxY, "length") - PROPERTY_DOUBLE(minZInner, "the start point of the inner box in the Z direction") - ATTRIBUTE_QUANTITY(minZInner, "length") + PROPERTY_DOUBLE(innerMinZ, "the start point of the inner box in the Z direction") + ATTRIBUTE_QUANTITY(innerMinZ, "length") - PROPERTY_DOUBLE(maxZInner, "the end point of the inner box in the Z direction") - ATTRIBUTE_QUANTITY(maxZInner, "length") + PROPERTY_DOUBLE(innerMaxZ, "the end point of the inner box in the Z direction") + ATTRIBUTE_QUANTITY(innerMaxZ, "length") - PROPERTY_ITEM(nestedTree, DensityTreePolicy, "the nested density tree inside the inner region") + PROPERTY_ITEM(innerPolicy, DensityTreePolicy, "the density tree policy for the inner region") + ATTRIBUTE_DEFAULT_VALUE(innerPolicy, "DensityTreePolicy") ITEM_END() From 607aa989946696fa4f7d939a943a2eae7384ddd5 Mon Sep 17 00:00:00 2001 From: Peter Camps Date: Wed, 4 Oct 2023 17:48:07 +0200 Subject: [PATCH 05/51] verify that inner box is nonempty --- SKIRT/core/NestedDensityTreePolicy.cpp | 8 ++++++++ SKIRT/core/NestedDensityTreePolicy.hpp | 6 +++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/SKIRT/core/NestedDensityTreePolicy.cpp b/SKIRT/core/NestedDensityTreePolicy.cpp index 379758d3..2ac7cb2b 100644 --- a/SKIRT/core/NestedDensityTreePolicy.cpp +++ b/SKIRT/core/NestedDensityTreePolicy.cpp @@ -4,6 +4,7 @@ ///////////////////////////////////////////////////////////////// */ #include "NestedDensityTreePolicy.hpp" +#include "FatalError.hpp" #include "Log.hpp" #include "SpatialGrid.hpp" #include "TreeNode.hpp" @@ -14,8 +15,15 @@ void NestedDensityTreePolicy::setupSelfBefore() { DensityTreePolicy::setupSelfBefore(); + // verify that the inner box is not empty + if (_innerMaxX <= _innerMinX) throw FATALERROR("The extent of the inner box should be positive in the X direction"); + if (_innerMaxY <= _innerMinY) throw FATALERROR("The extent of the inner box should be positive in the Y direction"); + if (_innerMaxZ <= _innerMinZ) throw FATALERROR("The extent of the inner box should be positive in the Z direction"); + + // copy the coordinates to a Box instance for ease of use _inner = Box(_innerMinX, _innerMinY, _innerMinZ, _innerMaxX, _innerMaxY, _innerMaxZ); + // verify that the inner box is inside the spatial grid auto grid = find(false); if (!grid->boundingBox().contains(_inner)) find()->warning("The nested density tree policy region is not fully inside the spatial grid domain"); diff --git a/SKIRT/core/NestedDensityTreePolicy.hpp b/SKIRT/core/NestedDensityTreePolicy.hpp index 3944e272..f38fffdf 100644 --- a/SKIRT/core/NestedDensityTreePolicy.hpp +++ b/SKIRT/core/NestedDensityTreePolicy.hpp @@ -10,8 +10,8 @@ #include "DensityTreePolicy.hpp" /** NestedDensityTreePolicy is a DensityTreePolicy policy that allows defining separate subdivision - criteria in a given subregion of the spatial grid. This can be used, for example, to specify - a higher resolution in a given region of interest. + criteria in a given subregion of the spatial grid. This can be used, for example, to specify a + higher resolution in a given region of interest. The class inherits from DensityTreePolicy and uses all the inherited properties to classify the outer region. Additional \em innerMin/innerMax coordinate properties define the bounding box of @@ -32,7 +32,7 @@ By default, the inner policy is a regular DensityTreePolicy instance. However, because NestedDensityTreePolicy inherits DensityTreePolicy, it is also possible to again select a NestedDensityTreePolicy instance as the inner policy, leading to recursive nesting. While this - is not a recommended use case, it would allow specifying recursively inreasing resolution in + is not a recommended use case, it would allow specifying recursively increasing resolution in nested, successively smaller regions of the spatial domain. */ class NestedDensityTreePolicy : public DensityTreePolicy { From c5746945cd2215b1e957bd1a1a0e09aa4d79bd65 Mon Sep 17 00:00:00 2001 From: arlauwer Date: Wed, 25 Oct 2023 14:56:56 +0200 Subject: [PATCH 06/51] tetra construction functional --- SKIRT/core/SimulationItemRegistry.cpp | 4 +- SKIRT/core/TetraMeshInterface.hpp | 33 + SKIRT/core/TetraMeshSnapshot.cpp | 1605 +++++++++++++++++++++++++ SKIRT/core/TetraMeshSnapshot.hpp | 492 ++++++++ SKIRT/core/TetraMeshSpatialGrid.cpp | 223 ++++ SKIRT/core/TetraMeshSpatialGrid.hpp | 136 +++ 6 files changed, 2492 insertions(+), 1 deletion(-) create mode 100644 SKIRT/core/TetraMeshInterface.hpp create mode 100644 SKIRT/core/TetraMeshSnapshot.cpp create mode 100644 SKIRT/core/TetraMeshSnapshot.hpp create mode 100644 SKIRT/core/TetraMeshSpatialGrid.cpp create mode 100644 SKIRT/core/TetraMeshSpatialGrid.hpp diff --git a/SKIRT/core/SimulationItemRegistry.cpp b/SKIRT/core/SimulationItemRegistry.cpp index ecbcab06..70c6b72b 100755 --- a/SKIRT/core/SimulationItemRegistry.cpp +++ b/SKIRT/core/SimulationItemRegistry.cpp @@ -248,6 +248,7 @@ #include "TTauriDiskGeometry.hpp" #include "TemperatureProbe.hpp" #include "TemperatureWavelengthCellLibrary.hpp" +#include "TetraMeshSpatialGrid.hpp" #include "ThemisDustMix.hpp" #include "ToddlersSED.hpp" #include "ToddlersSEDFamily.hpp" @@ -437,7 +438,7 @@ SimulationItemRegistry::SimulationItemRegistry(string version, string format) ItemRegistry::add(); ItemRegistry::add(); ItemRegistry::add(); - ItemRegistry::add(); + ItemRegistry::add(); // geometry decorators ItemRegistry::add(); @@ -481,6 +482,7 @@ SimulationItemRegistry::SimulationItemRegistry(string version, string format) ItemRegistry::add(); ItemRegistry::add(); ItemRegistry::add(); + ItemRegistry::add(); // spatial grid policies ItemRegistry::add(); diff --git a/SKIRT/core/TetraMeshInterface.hpp b/SKIRT/core/TetraMeshInterface.hpp new file mode 100644 index 00000000..3e32e188 --- /dev/null +++ b/SKIRT/core/TetraMeshInterface.hpp @@ -0,0 +1,33 @@ +/*////////////////////////////////////////////////////////////////// +//// The SKIRT project -- advanced radiative transfer //// +//// © Astronomical Observatory, Ghent University //// +///////////////////////////////////////////////////////////////// */ + +#ifndef TETRAMESHINTERFACE_HPP +#define TETRAMESHINTERFACE_HPP + +#include "Basics.hpp" +class TetraMeshSnapshot; + +//////////////////////////////////////////////////////////////////// + +/** TetraMeshInterface is a pure interface. It provides access to the Tetra mesh snapshot + maintained by the object that implements the interface. */ +class TetraMeshInterface +{ +protected: + /** The empty constructor for the interface. */ + TetraMeshInterface() {} + +public: + /** The empty destructor for the interface. */ + virtual ~TetraMeshInterface() {} + + /** This function must be implemented in a derived class. It returns a pointer to the Tetra + mesh snapshot maintained by the object that implements the interface. */ + virtual TetraMeshSnapshot* tetraMesh() const = 0; +}; + +///////////////////////////////////////////////////////////////////////////// + +#endif diff --git a/SKIRT/core/TetraMeshSnapshot.cpp b/SKIRT/core/TetraMeshSnapshot.cpp new file mode 100644 index 00000000..0396781e --- /dev/null +++ b/SKIRT/core/TetraMeshSnapshot.cpp @@ -0,0 +1,1605 @@ +/*////////////////////////////////////////////////////////////////// +//// The SKIRT project -- advanced radiative transfer //// +//// © Astronomical Observatory, Ghent University //// +///////////////////////////////////////////////////////////////// */ + +#include "TetraMeshSnapshot.hpp" +#include "EntityCollection.hpp" +#include "FatalError.hpp" +#include "Log.hpp" +#include "NR.hpp" +#include "Parallel.hpp" +#include "ParallelFactory.hpp" +#include "PathSegmentGenerator.hpp" +#include "ProcessManager.hpp" +#include "Random.hpp" +#include "SiteListInterface.hpp" +#include "SpatialGridPath.hpp" +#include "SpatialGridPlotFile.hpp" +#include "StringUtils.hpp" +#include "Table.hpp" +#include "TextInFile.hpp" +#include "Units.hpp" +#include +#include +#include +#include "container.hh" + +//////////////////////////////////////////////////////////////////// + +namespace +{ + // classes used for serializing/deserializing Tetra cell geometry when communicating the results of + // grid construction between multiple processes with the ProcessManager::broadcastAllToAll() function + + // decorates a std::vector with functions to write serialized versions of various data types + class SerializedWrite + { + private: + vector& _data; + + public: + SerializedWrite(vector& data) : _data(data) { _data.clear(); } + void write(double v) { _data.push_back(v); } + void write(Vec v) { _data.insert(_data.end(), {v.x(), v.y(), v.z()}); } + void write(Box v) { _data.insert(_data.end(), {v.xmin(), v.ymin(), v.zmin(), v.xmax(), v.ymax(), v.zmax()}); } + void write(const vector& v) + { + _data.push_back(v.size()); + _data.insert(_data.end(), v.begin(), v.end()); + } + }; + + // decorates a std::vector with functions to read serialized versions of various data types + class SerializedRead + { + private: + const double* _data; + const double* _end; + + public: + SerializedRead(const vector& data) : _data(data.data()), _end(data.data() + data.size()) {} + bool empty() { return _data == _end; } + int readInt() { return *_data++; } + void read(double& v) { v = *_data++; } + void read(Vec& v) + { + v.set(*_data, *(_data + 1), *(_data + 2)); + _data += 3; + } + void read(Box& v) + { + v = Box(*_data, *(_data + 1), *(_data + 2), *(_data + 3), *(_data + 4), *(_data + 5)); + _data += 6; + } + void read(vector& v) + { + int n = *_data++; + v.clear(); + v.reserve(n); + for (int i = 0; i != n; ++i) v.push_back(*_data++); + } + }; +} + +//////////////////////////////////////////////////////////////////// + +// class to hold the information about a Tetra cell that is relevant for calculating paths and densities +class TetraMeshSnapshot::Cell : public Box // enclosing box +{ +public: + Vec _r; // site position + Vec _c; // centroid position + double _volume{0.}; // volume + vector _neighbors; // list of neighbor indices in _cells vector + Array _properties; // user-defined properties, if any + +public: + // constructor stores the specified site position; the other data members are set to zero or empty + Cell(Vec r) : _r(r) {} + + // constructor derives the site position from the first three property values and stores the user properties; + // the other data members are set to zero or empty + Cell(const Array& prop) : _r(prop[0], prop[1], prop[2]), _properties{prop} {} + + // adjusts the site position with the specified offset + void relax(double cx, double cy, double cz) { _r += Vec(cx, cy, cz); } + + // initializes the receiver with information taken from the specified fully computed Tetra cell + void init(voro::voronoicell_neighbor& cell) + { + // copy basic geometric info + double cx, cy, cz; + cell.centroid(cx, cy, cz); + _c = Vec(cx, cy, cz) + _r; + _volume = cell.volume(); + + // get the minimal and maximal coordinates of the box enclosing the cell + vector coords; + cell.vertices(_r.x(), _r.y(), _r.z(), coords); + double xmin = DBL_MAX; + double ymin = DBL_MAX; + double zmin = DBL_MAX; + double xmax = -DBL_MAX; + double ymax = -DBL_MAX; + double zmax = -DBL_MAX; + int n = coords.size(); + for (int i = 0; i < n; i += 3) + { + xmin = min(xmin, coords[i]); + ymin = min(ymin, coords[i + 1]); + zmin = min(zmin, coords[i + 2]); + xmax = max(xmax, coords[i]); + ymax = max(ymax, coords[i + 1]); + zmax = max(zmax, coords[i + 2]); + } + + // set our inherited Box to this bounding box + setExtent(xmin, ymin, zmin, xmax, ymax, zmax); + + // copy a list of neighboring cell/site ids + cell.neighbors(_neighbors); + } + + // clears the information taken from a Tetra cell so it can be reinitialized + void clear() + { + _volume = 0.; + _neighbors.clear(); + } + + // initializes the receiver with the volume calculated from imported information + void init(double volume) { _volume = volume; } + + // returns the cell's site position + Vec position() const { return _r; } + + // returns the x coordinate of the cell's site position + double x() const { return _r.x(); } + + // returns the squared distance from the cell's site to the specified point + double squaredDistanceTo(Vec r) const { return (r - _r).norm2(); } + + // returns the central position in the cell + Vec centroid() const { return _c; } + + // returns the volume of the cell; overriding volume() function of Box bas class + double volume() const { return _volume; } + + // returns a list of neighboring cell/site ids + const vector& neighbors() { return _neighbors; } + + // returns the cell/site user properties, if any + const Array& properties() { return _properties; } + + // writes the Tetra cell geometry to the serialized data buffer, preceded by the specified cell index, + // if the cell geometry has been calculated for this cell; otherwise does nothing + void writeGeometryIfPresent(SerializedWrite& wdata, int m) + { + if (!_neighbors.empty()) + { + wdata.write(m); + wdata.write(extent()); + wdata.write(_c); + wdata.write(_volume); + wdata.write(_neighbors); + } + } + + // reads the Tetra cell geometry from the serialized data buffer + void readGeometry(SerializedRead& rdata) + { + rdata.read(*this); // extent + rdata.read(_c); + rdata.read(_volume); + rdata.read(_neighbors); + } +}; + +class TetraMeshSnapshot::Tetra : public Box +{ +public: + Tetra(const vector& _cells, int i, int j, int k, int l) + { + _vertices[0] = _cells[i]->_r; + _vertices[1] = _cells[j]->_r; + _vertices[2] = _cells[k]->_r; + _vertices[3] = _cells[l]->_r; + + double xmin = DBL_MAX; + double ymin = DBL_MAX; + double zmin = DBL_MAX; + double xmax = -DBL_MAX; + double ymax = -DBL_MAX; + double zmax = -DBL_MAX; + for (int i = 0; i < 4; i += 3) + { + xmin = min(xmin, _vertices[i].x()); + ymin = min(ymin, _vertices[i].y()); + zmin = min(zmin, _vertices[i].z()); + xmax = max(xmax, _vertices[i].x()); + ymax = max(ymax, _vertices[i].y()); + zmax = max(zmax, _vertices[i].z()); + } + setExtent(Box(xmin, ymin, zmin, xmax, ymax, zmax)); + + _vertex_indices[0] = i; + _vertex_indices[1] = j; + _vertex_indices[2] = k; + _vertex_indices[3] = l; + } + + // compute normal for face 3 (in clockwise direction of vertices 012) and dot with vertex 3 + double orient() + { + const Vec e01 = _vertices[1] - _vertices[0]; + const Vec e02 = _vertices[2] - _vertices[0]; + const Vec e03 = _vertices[3] - _vertices[0]; + // this convention makes edges go clockwise around leaving rays from inside the tetrahedron + double orientation = Vec::dot(Vec::cross(e02, e01), e03); + if (orientation < 0) + { + std::swap(_vertices[0], _vertices[1]); + std::swap(_vertex_indices[0], _vertex_indices[1]); + } + return orientation; + } + + // return -1 if no shared face + // return 0-3 for opposite vertex of shared face + int shareFace(const Tetra* other) const + { + int equal_vert = 0; + int opposite = 0 + 1 + 2 + 3; + + for (size_t i = 0; i < 4; i++) + { + const int vi = _vertex_indices[i]; + + for (size_t j = 0; j < 4; j++) + { + const int vj = other->_vertex_indices[i]; + + if (vi == vj) + { + equal_vert++; + opposite -= i; + break; + } + } + } + if (equal_vert == 3) return opposite; + return -1; + } + + bool equals(const Tetra* other) const + { + for (int v : _vertex_indices) + { + bool match = false; + for (int u : other->_vertex_indices) + { + if (u == v) + { + match = true; + break; + } + } + if (!match) return false; + } + return true; + } + + bool SameSide(const Vec& v0, const Vec& v1, const Vec& v2, const Vec& v3, const Vec& pos) const + { + Vec normal = Vec::cross(v1 - v0, v2 - v0); + double dotV4 = Vec::dot(normal, v3 - v0); + double dotP = Vec::dot(normal, pos - v0); + return (dotV4 > 0) == (dotP > 0); + } + + bool inside(const Position& bfr) const + { + // very poor implementation + const Vec& v0 = _vertices[0]; + const Vec& v1 = _vertices[1]; + const Vec& v2 = _vertices[2]; + const Vec& v3 = _vertices[3]; + return SameSide(v0, v1, v2, v3, bfr) && SameSide(v1, v2, v3, v0, bfr) && SameSide(v2, v3, v0, v1, bfr) + && SameSide(v3, v0, v1, v2, bfr); + } + + std::array _vertices; + std::array _neighbors = {-1, -1, -1, -1}; + std::array _vertex_indices; +}; + +class TetraMeshSnapshot::Plucker +{ + Vec U, V; + +public: + Plucker() {} + + Plucker(const Vec& U, const Vec& V) : U(U), V(V) {} + + static inline Plucker createFromDir(const Vec& pos, const Vec& dir) { return Plucker(dir, Vec::cross(dir, pos)); } + + static inline Plucker createFromVertices(const Vec& v1, const Vec& v2) + { + Plucker p; + p.U = v2 - v1; + p.V = Vec::cross(p.U, v1); + return p; + } + + // give plucker coord of all edges of a certain face + static inline void face(std::array& edges, const std::array& vertices, int face) + { + int v1 = (face + 1) % 4; + int v2 = (face + 2) % 4; + int v3 = (face + 3) % 4; + + // if face is even we should swap two edges + if (face % 2 == 0) std::swap(v2, v3); + + edges[0] = Plucker(vertices[v1], vertices[v2]); + edges[1] = Plucker(vertices[v2], vertices[v3]); + edges[2] = Plucker(vertices[v3], vertices[v1]); + } + + // permuted inner product + static inline double dot(const Plucker& a, const Plucker& b) { return Vec::dot(a.U, b.V) + Vec::dot(b.U, a.V); } +}; + +//////////////////////////////////////////////////////////////////// + +namespace +{ + // function to compare two points according to the specified axis (0,1,2) + bool lessthan(Vec p1, Vec p2, int axis) + { + switch (axis) + { + case 0: // split on x + if (p1.x() < p2.x()) return true; + if (p1.x() > p2.x()) return false; + if (p1.y() < p2.y()) return true; + if (p1.y() > p2.y()) return false; + if (p1.z() < p2.z()) return true; + return false; + case 1: // split on y + if (p1.y() < p2.y()) return true; + if (p1.y() > p2.y()) return false; + if (p1.z() < p2.z()) return true; + if (p1.z() > p2.z()) return false; + if (p1.x() < p2.x()) return true; + return false; + case 2: // split on z + if (p1.z() < p2.z()) return true; + if (p1.z() > p2.z()) return false; + if (p1.x() < p2.x()) return true; + if (p1.x() > p2.x()) return false; + if (p1.y() < p2.y()) return true; + return false; + default: // this should never happen + return false; + } + } + + template bool invec(const vector& vec, const T& e) + { + return std::find(vec.begin(), vec.end(), e) != vec.end(); + } + + double det4(double x0, double y0, double z0, double x1, double y1, double z1, double x2, double y2, double z2, + double x3, double y3, double z3) + { + // return x0 * (y1 * (z2 - z3) - y2 * (z1 - z3) + y3 * (z1 - z2)) + // - y0 * (x1 * (z2 - z3) - x2 * (z1 - z3) + x3 * (z1 - z2)) + // + z0 * (x1 * (y2 - y3) - x2 * (y1 - y3) + x3 * (y1 - y2)); + + return x0 * y1 * z2 - x0 * y1 * z3 - x0 * y2 * z1 + x0 * y2 * z3 + x0 * y3 * z1 - x0 * y3 * z2 - x1 * y0 * z2 + + x1 * y0 * z3 + x1 * y2 * z0 - x1 * y2 * z3 - x1 * y3 * z0 + x1 * y3 * z2 + x2 * y0 * z1 - x2 * y0 * z3 + - x2 * y1 * z0 + x2 * y1 * z3 + x2 * y3 * z0 - x2 * y3 * z1 - x3 * y0 * z1 + x3 * y0 * z2 + x3 * y1 * z0 + - x3 * y1 * z2 - x3 * y2 * z0 + x3 * y2 * z1; + } +} + +//////////////////////////////////////////////////////////////////// + +// class to hold a node in the binary search tree (see en.wikipedia.org/wiki/Kd-tree) +class TetraMeshSnapshot::Node +{ +private: + int _m; // index in _cells to the site defining the split at this node + int _axis; // split axis for this node (0,1,2) + Node* _up; // ptr to the parent node + Node* _left; // ptr to the left child node + Node* _right; // ptr to the right child node + + // returns the square of its argument + static double sqr(double x) { return x * x; } + +public: + // constructor stores the specified site index and child pointers (which may be null) + Node(int m, int depth, Node* left, Node* right) : _m(m), _axis(depth % 3), _up(0), _left(left), _right(right) + { + if (_left) _left->setParent(this); + if (_right) _right->setParent(this); + } + + // destructor destroys the children + ~Node() + { + delete _left; + delete _right; + } + + // sets parent pointer (called from parent's constructor) + void setParent(Node* up) { _up = up; } + + // returns the corresponding data member + int m() const { return _m; } + Node* up() const { return _up; } + Node* left() const { return _left; } + Node* right() const { return _right; } + + // returns the apropriate child for the specified query point + Node* child(Vec bfr, const vector& cells) const + { + return lessthan(bfr, cells[_m]->position(), _axis) ? _left : _right; + } + + // returns the other child than the one that would be apropriate for the specified query point + Node* otherChild(Vec bfr, const vector& cells) const + { + return lessthan(bfr, cells[_m]->position(), _axis) ? _right : _left; + } + + // returns the squared distance from the query point to the split plane + double squaredDistanceToSplitPlane(Vec bfr, const vector& cells) const + { + switch (_axis) + { + case 0: // split on x + return sqr(cells[_m]->position().x() - bfr.x()); + case 1: // split on y + return sqr(cells[_m]->position().y() - bfr.y()); + case 2: // split on z + return sqr(cells[_m]->position().z() - bfr.z()); + default: // this should never happen + return 0; + } + } + + // returns the node in this subtree that represents the site nearest to the query point + Node* nearest(Vec bfr, const vector& cells) + { + // recursively descend the tree until a leaf node is reached, going left or right depending on + // whether the specified point is less than or greater than the current node in the split dimension + Node* current = this; + while (Node* child = current->child(bfr, cells)) current = child; + + // unwind the recursion, looking for the nearest node while climbing up + Node* best = current; + double bestSD = cells[best->m()]->squaredDistanceTo(bfr); + while (true) + { + // if the current node is closer than the current best, then it becomes the current best + double currentSD = cells[current->m()]->squaredDistanceTo(bfr); + if (currentSD < bestSD) + { + best = current; + bestSD = currentSD; + } + + // if there could be points on the other side of the splitting plane for the current node + // that are closer to the search point than the current best, then ... + double splitSD = current->squaredDistanceToSplitPlane(bfr, cells); + if (splitSD < bestSD) + { + // move down the other branch of the tree from the current node looking for closer points, + // following the same recursive process as the entire search + Node* other = current->otherChild(bfr, cells); + if (other) + { + Node* otherBest = other->nearest(bfr, cells); + double otherBestSD = cells[otherBest->m()]->squaredDistanceTo(bfr); + if (otherBestSD < bestSD) + { + best = otherBest; + bestSD = otherBestSD; + } + } + } + + // move up to the parent until we meet the top node + if (current == this) break; + current = current->up(); + } + return best; + } +}; + +//////////////////////////////////////////////////////////////////// + +TetraMeshSnapshot::TetraMeshSnapshot() {} + +//////////////////////////////////////////////////////////////////// + +TetraMeshSnapshot::~TetraMeshSnapshot() +{ + for (auto cell : _cells) delete cell; + for (auto tetra : _tetrahedra) delete tetra; + for (auto tree : _blocktrees) delete tree; +} + +//////////////////////////////////////////////////////////////////// + +void TetraMeshSnapshot::readAndClose() +{ + // read the site info into memory + Array prop; + while (infile()->readRow(prop)) _cells.push_back(new Cell(prop)); + + // close the file + Snapshot::readAndClose(); + + // if we are allowed to build a Tetra mesh + if (!_foregoTetraMesh) + { + // calculate the Tetra cells + buildMesh(false); + + // if a mass density policy has been set, calculate masses and densities and build the search data structure + if (hasMassDensityPolicy()) calculateDensityAndMass(); + if (hasMassDensityPolicy() || needGetEntities()) buildSearchPerBlock(); + } + + // if we forego building a Tetra mesh, there is a density policy by definition + else + { + calculateVolume(); + calculateDensityAndMass(); + buildSearchSingle(); + } +} + +//////////////////////////////////////////////////////////////////// + +void TetraMeshSnapshot::setExtent(const Box& extent) +{ + _extent = extent; + _eps = 1e-12 * extent.widths().norm(); +} + +//////////////////////////////////////////////////////////////////// + +void TetraMeshSnapshot::foregoTetraMesh() +{ + _foregoTetraMesh = true; +} + +//////////////////////////////////////////////////////////////////// + +TetraMeshSnapshot::TetraMeshSnapshot(const SimulationItem* item, const Box& extent, string filename, bool relax) +{ + // read the input file + TextInFile in(item, filename, "Tetra sites"); + in.addColumn("position x", "length", "pc"); + in.addColumn("position y", "length", "pc"); + in.addColumn("position z", "length", "pc"); + Array coords; + while (in.readRow(coords)) _cells.push_back(new Cell(Vec(coords[0], coords[1], coords[2]))); + in.close(); + + // calculate the Tetra cells + setContext(item); + setExtent(extent); + buildMesh(relax); + buildSearchPerBlock(); +} + +//////////////////////////////////////////////////////////////////// + +TetraMeshSnapshot::TetraMeshSnapshot(const SimulationItem* item, const Box& extent, SiteListInterface* sli, bool relax) +{ + // prepare the data + int n = sli->numSites(); + _cells.resize(n); + for (int m = 0; m != n; ++m) _cells[m] = new Cell(sli->sitePosition(m)); + + // calculate the Tetra cells + setContext(item); + setExtent(extent); + buildMesh(relax); + buildSearchPerBlock(); +} + +//////////////////////////////////////////////////////////////////// + +TetraMeshSnapshot::TetraMeshSnapshot(const SimulationItem* item, const Box& extent, const vector& sites, + bool relax) +{ + // prepare the data + int n = sites.size(); + _cells.resize(n); + for (int m = 0; m != n; ++m) _cells[m] = new Cell(sites[m]); + + // calculate the Tetra cells + setContext(item); + setExtent(extent); + buildMesh(relax); + buildSearchPerBlock(); +} + +//////////////////////////////////////////////////////////////////// + +namespace +{ + // maximum number of Tetra sites processed between two invocations of infoIfElapsed() + const int logProgressChunkSize = 1000; + + // maximum number of Tetra grid construction iterations + const int maxConstructionIterations = 5; + + // function to erase null pointers from a vector of pointers in one go; returns the new size + template size_t eraseNullPointers(vector& v) + { + // scan to the first null (or to the end) + auto from = v.begin(); + while (from != v.end() && *from) ++from; + + // copy the tail, overwriting any nulls + auto to = from; + while (from != v.end()) + { + if (*from) *to++ = *from; + ++from; + } + v.erase(to, v.end()); + return v.size(); + } +} + +//////////////////////////////////////////////////////////////////// + +void TetraMeshSnapshot::buildMesh(bool relax) +{ + /////////////////////////////////////////////////////////////////////////// + setExtent(Box(-1, -1, -1, 1, 1, 1)); + + vector sites; + // for (size_t i = 0; sites.size() < 15; i++) + // { + // Position r = random()->position(_extent); + // sites.push_back(r); + // } + + // sites.push_back(Vec( 0.2, 0.2, 0)); + // sites.push_back(Vec( 0.2, -0.2, 0)); + sites.push_back(Vec(0.4, 0.4, 0)); + sites.push_back(Vec(0.4, -0.4, 0)); + + sites.push_back(Vec(-0.5, 0, 0)); + sites.push_back(Vec(0, 0, 0.5)); + sites.push_back(Vec(0, 0, -0.5)); + + // prepare the data + int n = sites.size(); + _cells.resize(n); + for (int m = 0; m != n; ++m) _cells[m] = new Cell(sites[m]); + + const double A = 100.; + + /////////////////////////////////////////////////////////////////////////// + + int numCells = _cells.size(); + + // remove sites that lie outside of the domain + int numOutside = 0; + for (int m = 0; m != numCells; ++m) + { + if (!_extent.contains(_cells[m]->position())) + { + delete _cells[m]; + _cells[m] = 0; + numOutside++; + } + } + if (numOutside) numCells = eraseNullPointers(_cells); + + // sort sites in order of increasing x coordinate to accelerate search for nearby sites + std::sort(_cells.begin(), _cells.end(), [](Cell* c1, Cell* c2) { return c1->x() < c2->x(); }); + + // remove sites that lie too nearby another site + int numNearby = 0; + for (int m = 0; m != numCells; ++m) + { + for (int j = m + 1; j != numCells && _cells[j]->x() - _cells[m]->x() < _eps; ++j) + { + if ((_cells[j]->position() - _cells[m]->position()).norm2() < _eps * _eps) + { + delete _cells[m]; + _cells[m] = 0; + numNearby++; + break; + } + } + } + if (numNearby) numCells = eraseNullPointers(_cells); + + // log the number of sites + if (!numOutside && !numNearby) + { + log()->info(" Number of sites: " + std::to_string(numCells)); + } + else + { + if (numOutside) log()->info(" Number of sites outside domain: " + std::to_string(numOutside)); + if (numNearby) log()->info(" Number of sites too nearby others: " + std::to_string(numNearby)); + log()->info(" Number of sites retained: " + std::to_string(numCells)); + } + + // abort if there are no cells to calculate + if (numCells <= 0) return; + + // calculate number of blocks in each direction based on number of cells + _nb = max(3, min(250, static_cast(cbrt(numCells)))); + _nb2 = _nb * _nb; + _nb3 = _nb * _nb * _nb; + + // ========= RELAXATION ========= + + // if requested, perform a single relaxation step + if (relax) + { + // table to hold the calculate relaxation offset for each site + // (initialized to zero so we can communicate the result between parallel processes using sumAll) + Table<2> offsets(numCells, 3); + + // add the retained original sites to a temporary Tetra container, using the cell index m as ID + voro::container vcon(-A, A, -A, A, -A, A, _nb, _nb, _nb, false, false, false, 16); + for (int m = 0; m != numCells; ++m) + { + Vec r = _cells[m]->position(); + vcon.put(m, r.x(), r.y(), r.z()); + } + + // compute the cell in the Tetra tesselation corresponding to each site + // and store the cell's centroid (relative to the site position) as the relaxation offset + log()->info("Relaxing Tetra tessellation with " + std::to_string(numCells) + " cells"); + log()->infoSetElapsed(numCells); + auto parallel = log()->find()->parallelDistributed(); + parallel->call(numCells, [this, &vcon, &offsets](size_t firstIndex, size_t numIndices) { + // allocate a separate cell calculator for each thread to avoid conflicts + voro::voro_compute vcompute(vcon, _nb, _nb, _nb); + // allocate space for the resulting cell info + voro::voronoicell vcell; + + // loop over all cells and work on the ones that have a particle index in our dedicated range + // (we cannot access cells in the container based on cell index m without building an extra data structure) + int numDone = 0; + voro::c_loop_all vloop(vcon); + if (vloop.start()) do + { + size_t m = vloop.pid(); + if (m >= firstIndex && m < firstIndex + numIndices) + { + // compute the cell and store its centroid as relaxation offset + bool ok = vcompute.compute_cell(vcell, vloop.ijk, vloop.q, vloop.i, vloop.j, vloop.k); + if (ok) vcell.centroid(offsets(m, 0), offsets(m, 1), offsets(m, 2)); + + // log message if the minimum time has elapsed + numDone = (numDone + 1) % logProgressChunkSize; + if (numDone == 0) log()->infoIfElapsed("Computed Tetra cells: ", logProgressChunkSize); + } + } while (vloop.inc()); + if (numDone > 0) log()->infoIfElapsed("Computed Tetra cells: ", numDone); + }); + + // communicate the calculated offsets between parallel processes, if needed, and apply them to the cells + ProcessManager::sumToAll(offsets.data()); + for (int m = 0; m != numCells; ++m) _cells[m]->relax(offsets(m, 0), offsets(m, 1), offsets(m, 2)); + } + + // ========= FINAL GRID ========= + + // repeat grid construction until none of the cells have zero volume + int numIterations = 0; + while (true) + { + // add the final sites to a temporary Tetra container, using the cell index m as ID + voro::container vcon(-A, A, -A, A, -A, A, _nb, _nb, _nb, false, false, false, 16); + for (int m = 0; m != numCells; ++m) + { + Vec r = _cells[m]->position(); + vcon.put(m, r.x(), r.y(), r.z()); + } + + // for each site: + // - compute the corresponding cell in the Tetra tesselation + // - extract and copy the relevant information to the cell object with the corresponding index in our vector + log()->info("Constructing Tetra tessellation with " + std::to_string(numCells) + " cells"); + log()->infoSetElapsed(numCells); + // allocate a separate cell calculator for each thread to avoid conflicts + voro::voro_compute vcompute(vcon, _nb, _nb, _nb); + // allocate space for the resulting cell info + voro::voronoicell_neighbor vcell; + std::vector vcells; + + // loop over all cells and work on the ones that have a particle index in our dedicated range + // (we cannot access cells in the container based on cell index m without building an extra data structure) + int numDone = 0; + voro::c_loop_all vloop(vcon); + if (vloop.start()) do + { + size_t m = vloop.pid(); + // compute the cell and copy all relevant information to the cell object that will stay around + bool ok = vcompute.compute_cell(vcell, vloop.ijk, vloop.q, vloop.i, vloop.j, vloop.k); + if (ok) _cells[m]->init(vcell); + + // log message if the minimum time has elapsed + numDone = (numDone + 1) % logProgressChunkSize; + if (numDone == 0) log()->infoIfElapsed("Computed Tetra cells: ", logProgressChunkSize); + } while (vloop.inc()); + if (numDone > 0) log()->infoIfElapsed("Computed Tetra cells: ", numDone); + + //////////////////////////////////////////////////////////////////////////////////////////// + for (int i = 0; i < numCells; i++) + { + Cell* c1 = _cells[i]; + + for (int j : c1->neighbors()) + { + if (j < 0) continue; + + Cell* c2 = _cells[j]; + + for (int k : c2->neighbors()) + { + if (k < 0 || k == i) continue; + + // mutual neighbours + if (!invec(c1->neighbors(), k)) continue; + + for (int l : _cells[k]->neighbors()) + { + if (l < 0 || l == j || l == i) continue; + + // mutual neighbours of both c1 and c2 + if (!invec(c1->neighbors(), l) || !invec(c2->neighbors(), l)) continue; + + // no duplicates in different order // probably use set + Tetra* tetra = new Tetra(_cells, i, j, k, l); + if (inTetrahedra(tetra)) continue; + + // check if tetrahedron is Delaunay + Vec v0 = _cells[i]->_r; + Vec v1 = _cells[j]->_r; + Vec v2 = _cells[k]->_r; + Vec v3 = _cells[l]->_r; + double x0 = v0.x(), y0 = v0.y(), z0 = v0.z(); + double x1 = v1.x(), y1 = v1.y(), z1 = v1.z(); + double x2 = v2.x(), y2 = v2.y(), z2 = v2.z(); + double x3 = v3.x(), y3 = v3.y(), z3 = v3.z(); + + double r0 = x0 * x0 + y0 * y0 + z0 * z0; + double r1 = x1 * x1 + y1 * y1 + z1 * z1; + double r2 = x2 * x2 + y2 * y2 + z2 * z2; + double r3 = x3 * x3 + y3 * y3 + z3 * z3; + + double a = det4(x0, y0, z0, x1, y1, z1, x2, y2, z2, x3, y3, z3); + double Dx = det4(r0, y0, z0, r1, y1, z1, r2, y2, z2, r3, y3, z3); + double Dy = -det4(r0, x0, z0, r1, x1, z1, r2, x2, z2, r3, x3, z3); + double Dz = det4(r0, x0, y0, r1, x1, y1, r2, x2, y2, r3, x3, y3); + + Vec center(Dx / (2 * a), Dy / (2 * a), Dz / (2 * a)); + double R = (center - v0).norm2(); + + bool delaunay = true; + for (int v : tetra->_vertex_indices) + { + Cell* c = _cells[v]; + for (int n : c->neighbors()) + { + if (n < 0 || n == i || n == j || n == k || n == l) continue; + double r = (center - _cells[n]->_r).norm2(); + if (r < R) + { + delaunay = false; + break; + } + } + if (!delaunay) break; + } + if (!delaunay) continue; + + // orient tetrahedron in the same consistent way + tetra->orient(); + + centers.push_back(center); + radii.push_back(sqrt(R)); + _tetrahedra.push_back(tetra); + } + } + } + } + + // find neighbors brute force + for (size_t i = 0; i < _tetrahedra.size(); i++) + { + Tetra* tetra = _tetrahedra[i]; + + for (size_t j = 0; j < _tetrahedra.size(); j++) + { + if (i == j) continue; + + const Tetra* other = _tetrahedra[j]; + + int shared = tetra->shareFace(other); + if (shared != -1) tetra->_neighbors[shared] = j; + } + } + + //////////////////////////////////////////////////////////////////////////////////////////// + + // discover invalid cells with zero volume and/or with neighbors that are not mutual + log()->info("Verifying Tetra tessellation"); + std::set invalid; + for (int m = 0; m < numCells; m++) + { + if (!_cells[m]->volume()) invalid.insert(m); + for (int m1 : _cells[m]->neighbors()) + { + if (m1 >= 0) + { + const vector& neighbors1 = _cells[m1]->neighbors(); + if (std::find(neighbors1.begin(), neighbors1.end(), m) == neighbors1.end()) + { + invalid.insert(m); + invalid.insert(m1); + } + } + } + } + + // break from loop if no invalid cells were found + if (invalid.empty()) break; + + // give up after a given number of iterations + if (++numIterations == maxConstructionIterations) + { + throw FATALERROR("Still " + std::to_string(invalid.size()) + " invalid Tetra cells after " + + std::to_string(maxConstructionIterations) + + " iterations of constructing the tessellation"); + } + + // remove invalid cells and prepare to repeat construction + log()->warning("Removing sites for " + std::to_string(invalid.size()) + + " invalid Tetra cells and reconstructing the tessellation"); + for (auto it = invalid.begin(); it != invalid.end(); ++it) + { + int m = *it; + delete _cells[m]; + _cells[m] = 0; + } + numCells = eraseNullPointers(_cells); + for (int m = 0; m != numCells; ++m) _cells[m]->clear(); + } + + // ========= STATISTICS ========= + + // compile neighbor statistics + int minNeighbors = INT_MAX; + int maxNeighbors = 0; + int64_t totNeighbors = 0; + for (int m = 0; m < numCells; m++) + { + int ns = _cells[m]->neighbors().size(); + totNeighbors += ns; + minNeighbors = min(minNeighbors, ns); + maxNeighbors = max(maxNeighbors, ns); + } + double avgNeighbors = double(totNeighbors) / numCells; + + // log neighbor statistics + log()->info("Done computing Tetra tessellation with " + std::to_string(numCells) + " cells"); + log()->info(" Average number of neighbors per cell: " + StringUtils::toString(avgNeighbors, 'f', 1)); + log()->info(" Minimum number of neighbors per cell: " + std::to_string(minNeighbors)); + log()->info(" Maximum number of neighbors per cell: " + std::to_string(maxNeighbors)); +} + +//////////////////////////////////////////////////////////////////// + +void TetraMeshSnapshot::calculateVolume() +{ + int numCells = _cells.size(); + for (int m = 0; m != numCells; ++m) + { + const Array& prop = _cells[m]->properties(); + double volume = prop[densityIndex()] > 0. ? prop[massIndex()] / prop[densityIndex()] : 0.; + _cells[m]->init(volume); + } +} + +//////////////////////////////////////////////////////////////////// + +void TetraMeshSnapshot::calculateDensityAndMass() +{ + // allocate vectors for mass and density + int numCells = _cells.size(); + _rhov.resize(numCells); + Array Mv(numCells); + + // get the maximum temperature, or zero of there is none + double maxT = useTemperatureCutoff() ? maxTemperature() : 0.; + + // initialize statistics + double totalOriginalMass = 0; + double totalMetallicMass = 0; + double totalEffectiveMass = 0; + + // loop over all sites/cells + int numIgnored = 0; + for (int m = 0; m != numCells; ++m) + { + const Array& prop = _cells[m]->properties(); + + // original mass is zero if temperature is above cutoff or if imported mass/density is not positive + double originalDensity = 0.; + double originalMass = 0.; + if (maxT && prop[temperatureIndex()] > maxT) + { + numIgnored++; + } + else + { + double volume = _cells[m]->volume(); + originalDensity = max(0., densityIndex() >= 0 ? prop[densityIndex()] : prop[massIndex()] / volume); + originalMass = max(0., massIndex() >= 0 ? prop[massIndex()] : prop[densityIndex()] * volume); + } + + double effectiveDensity = originalDensity * (useMetallicity() ? prop[metallicityIndex()] : 1.) * multiplier(); + double metallicMass = originalMass * (useMetallicity() ? prop[metallicityIndex()] : 1.); + double effectiveMass = metallicMass * multiplier(); + + _rhov[m] = effectiveDensity; + Mv[m] = effectiveMass; + + totalOriginalMass += originalMass; + totalMetallicMass += metallicMass; + totalEffectiveMass += effectiveMass; + } + + // log mass statistics + logMassStatistics(numIgnored, totalOriginalMass, totalMetallicMass, totalEffectiveMass); + + // remember the effective mass + _mass = totalEffectiveMass; + + // construct a vector with the normalized cumulative site densities + if (numCells) NR::cdf(_cumrhov, Mv); +} + +//////////////////////////////////////////////////////////////////// + +TetraMeshSnapshot::Node* TetraMeshSnapshot::buildTree(vector::iterator first, vector::iterator last, + int depth) const +{ + auto length = last - first; + if (length > 0) + { + auto median = length >> 1; + std::nth_element(first, first + median, last, [this, depth](int m1, int m2) { + return m1 != m2 && lessthan(_cells[m1]->position(), _cells[m2]->position(), depth % 3); + }); + return new TetraMeshSnapshot::Node(*(first + median), depth, buildTree(first, first + median, depth + 1), + buildTree(first + median + 1, last, depth + 1)); + } + return nullptr; +} + +//////////////////////////////////////////////////////////////////// + +void TetraMeshSnapshot::buildSearchPerBlock() +{ + // abort if there are no cells + int numCells = _cells.size(); + if (!numCells) return; + + log()->info("Building data structures to accelerate searching the Tetra tesselation"); + + // ------------- block lists ------------- + + // initialize a vector of nb x nb x nb lists, each containing the cells overlapping a certain block in the domain + _blocklists.resize(_nb3); + + // add the cell object to the lists for all blocks it may overlap + int i1, j1, k1, i2, j2, k2; + for (int m = 0; m != numCells; ++m) + { + _extent.cellIndices(i1, j1, k1, _cells[m]->rmin() - Vec(_eps, _eps, _eps), _nb, _nb, _nb); + _extent.cellIndices(i2, j2, k2, _cells[m]->rmax() + Vec(_eps, _eps, _eps), _nb, _nb, _nb); + for (int i = i1; i <= i2; i++) + for (int j = j1; j <= j2; j++) + for (int k = k1; k <= k2; k++) _blocklists[i * _nb2 + j * _nb + k].push_back(m); + } + + // compile block list statistics + int minRefsPerBlock = INT_MAX; + int maxRefsPerBlock = 0; + int64_t totalBlockRefs = 0; + for (int b = 0; b < _nb3; b++) + { + int refs = _blocklists[b].size(); + totalBlockRefs += refs; + minRefsPerBlock = min(minRefsPerBlock, refs); + maxRefsPerBlock = max(maxRefsPerBlock, refs); + } + double avgRefsPerBlock = double(totalBlockRefs) / _nb3; + + // log block list statistics + log()->info(" Number of blocks in search grid: " + std::to_string(_nb3) + " (" + std::to_string(_nb) + "^3)"); + log()->info(" Average number of cells per block: " + StringUtils::toString(avgRefsPerBlock, 'f', 1)); + log()->info(" Minimum number of cells per block: " + std::to_string(minRefsPerBlock)); + log()->info(" Maximum number of cells per block: " + std::to_string(maxRefsPerBlock)); + + // ------------- search trees ------------- + + // for each block that contains more than a predefined number of cells, + // construct a search tree on the site locations of the cells + _blocktrees.resize(_nb3); + for (int b = 0; b < _nb3; b++) + { + vector& ids = _blocklists[b]; + if (ids.size() > 9) _blocktrees[b] = buildTree(ids.begin(), ids.end(), 0); + } + + // compile and log search tree statistics + int numTrees = 0; + for (int b = 0; b < _nb3; b++) + if (_blocktrees[b]) numTrees++; + log()->info(" Number of search trees: " + std::to_string(numTrees) + " (" + + StringUtils::toString(100. * numTrees / _nb3, 'f', 1) + "% of blocks)"); +} + +//////////////////////////////////////////////////////////////////// + +void TetraMeshSnapshot::buildSearchSingle() +{ + // log the number of sites + int numCells = _cells.size(); + log()->info(" Number of sites: " + std::to_string(numCells)); + + // abort if there are no cells + if (!numCells) return; + + // construct a single search tree on the site locations of all cells + log()->info("Building data structure to accelerate searching " + std::to_string(numCells) + " Tetra sites"); + _blocktrees.resize(1); + vector ids(numCells); + for (int m = 0; m != numCells; ++m) ids[m] = m; + _blocktrees[0] = buildTree(ids.begin(), ids.end(), 0); +} + +//////////////////////////////////////////////////////////////////// + +bool TetraMeshSnapshot::isPointClosestTo(Vec r, int m, const vector& ids) const +{ + double target = _cells[m]->squaredDistanceTo(r); + for (int id : ids) + { + if (id >= 0 && _cells[id]->squaredDistanceTo(r) < target) return false; + } + return true; +} + +//////////////////////////////////////////////////////////////////// + +bool TetraMeshSnapshot::inTetrahedra(const Tetra* tetra) const +{ + for (const Tetra* t : _tetrahedra) + { + if (tetra->equals(t)) return true; + } + return false; +} + +//////////////////////////////////////////////////////////////////// + +void TetraMeshSnapshot::writeGridPlotFiles(const SimulationItem* probe) const +{ + // create the plot files + // SpatialGridPlotFile plotxy(probe, probe->itemName() + "_grid_xy"); + // SpatialGridPlotFile plotxz(probe, probe->itemName() + "_grid_xz"); + // SpatialGridPlotFile plotyz(probe, probe->itemName() + "_grid_yz"); + SpatialGridPlotFile plotxyz(probe, probe->itemName() + "_grid_xyz"); + + // vector test = {1, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 1}; + // vector test2 = {3, 0, 1, 2, 3, 0, 1, 3, 3, 0, 2, 3, 3, 1, 2, 3}; + // plotxyz.writePolyhedron(test, test2); + // return; + + //////////////////////////////////////////////////////////////////////////////////////////////// + std::ofstream outputFile("input.txt"); + outputFile << "voronois=" << _cells.size() << "\n"; + for (size_t i = 0; i < _cells.size(); i++) + { + outputFile << "voronoi=" << i << "\n"; + Vec& r = _cells[i]->_r; + outputFile << r.x() << ", " << r.y() << ", " << r.z() << "\n"; + + outputFile << _cells[i]->neighbors().size() << " neighbors="; + for (int n : _cells[i]->neighbors()) + { + if (n >= 0) + { + outputFile << " " << n << ","; + } + } + outputFile << "\n"; + } + + outputFile << "tetrahedra=" << _tetrahedra.size() << "\n"; + for (size_t i = 0; i < _tetrahedra.size(); i++) + { + Tetra* tetra = _tetrahedra[i]; + outputFile << "tetrahedron=" << i << "\nvertices="; + for (size_t l = 0; l < 4; l++) + { + outputFile << " " << tetra->_vertex_indices[l] << ","; + } + + outputFile << "\n" << tetra->_neighbors.size() << " neighbors="; + for (size_t j = 0; j < 4; j++) + { + outputFile << " " << tetra->_neighbors[j] << ","; + + double x = std::round(tetra->_vertices[j].x()); + double y = std::round(tetra->_vertices[j].y()); + double z = std::round(tetra->_vertices[j].z()); + tetra->_vertices[j].set(x, y, z); + } + outputFile << "\n"; + } + for (size_t i = 0; i < _tetrahedra.size(); i++) + { + Tetra* tetra = _tetrahedra[i]; + outputFile << "circumsphere=" << i << "\n"; + outputFile << centers[i].x() << "," << centers[i].y() << "," << centers[i].z() << "\n"; + outputFile << radii[i] << "\n"; + } + + // Close the output file + outputFile.close(); + //////////////////////////////////////////////////////////////////////////////////////////////// + + for (size_t i = 0; i < _tetrahedra.size(); i++) + { + const Tetra* tetra = _tetrahedra[i]; + + vector coords; + vector indices = {3, 0, 1, 2, 3, 0, 1, 3, 3, 0, 2, 3, 3, 1, 2, 3}; + + for (size_t j = 0; j < 4; j++) + { + const Vec& v = tetra->_vertices[j]; + coords.push_back(v.x()); + coords.push_back(v.y()); + coords.push_back(v.z()); + } + // write the edges of the cell to the plot files + // if (bounds.zmin() <= 0 && bounds.zmax() >= 0) plotxy.writePolyhedron(coords, indices); + // if (bounds.ymin() <= 0 && bounds.ymax() >= 0) plotxz.writePolyhedron(coords, indices); + // if (bounds.xmin() <= 0 && bounds.xmax() >= 0) plotyz.writePolyhedron(coords, indices); + if (_tetrahedra.size() <= 1000) plotxyz.writePolyhedron(coords, indices); + } +} + +//////////////////////////////////////////////////////////////////// + +Box TetraMeshSnapshot::extent() const +{ + return _extent; +} + +//////////////////////////////////////////////////////////////////// + +int TetraMeshSnapshot::numEntities() const +{ + return _cells.size(); +} + +//////////////////////////////////////////////////////////////////// + +Position TetraMeshSnapshot::position(int m) const +{ + return Position(_cells[m]->position()); +} + +//////////////////////////////////////////////////////////////////// + +Position TetraMeshSnapshot::centroidPosition(int m) const +{ + return Position(_cells[m]->centroid()); +} + +//////////////////////////////////////////////////////////////////// + +double TetraMeshSnapshot::volume(int m) const +{ + return _cells[m]->volume(); +} + +//////////////////////////////////////////////////////////////////// + +Box TetraMeshSnapshot::extent(int m) const +{ + return _cells[m]->extent(); +} + +//////////////////////////////////////////////////////////////////// + +double TetraMeshSnapshot::density(int m) const +{ + return _rhov[m]; +} + +//////////////////////////////////////////////////////////////////// + +double TetraMeshSnapshot::density(Position bfr) const +{ + int m = cellIndex(bfr); + return m >= 0 ? _rhov[m] : 0; +} + +//////////////////////////////////////////////////////////////////// + +double TetraMeshSnapshot::mass() const +{ + return _mass; +} + +//////////////////////////////////////////////////////////////////// + +Position TetraMeshSnapshot::generatePosition(int m) const +{ + // get loop-invariant information about the cell + const Box& box = _cells[m]->extent(); + const vector& neighbors = _cells[m]->neighbors(); + + // generate random points in the enclosing box until one happens to be inside the cell + for (int i = 0; i < 10000; i++) + { + Position r = random()->position(box); + if (isPointClosestTo(r, m, neighbors)) return r; + } + throw FATALERROR("Can't find random position in cell"); +} + +//////////////////////////////////////////////////////////////////// + +Position TetraMeshSnapshot::generatePosition() const +{ + // if there are no sites, return the origin + if (_cells.empty()) return Position(); + + // select a site according to its mass contribution + int m = NR::locateClip(_cumrhov, random()->uniform()); + + return generatePosition(m); +} + +//////////////////////////////////////////////////////////////////// + +int TetraMeshSnapshot::cellIndex(Position bfr) const +{ + int tetras = _tetrahedra.size(); + for (int i = 0; i < tetras; i++) + { + const Tetra* tetra = _tetrahedra[i]; + // speed up by rejecting all points that are not in de bounding box + if (tetra->Box::contains(bfr) && tetra->Tetra::inside(bfr)) return i; + } + return -1; +} + +//////////////////////////////////////////////////////////////////// + +const Array& TetraMeshSnapshot::properties(int m) const +{ + return _cells[m]->properties(); +} + +//////////////////////////////////////////////////////////////////// + +int TetraMeshSnapshot::nearestEntity(Position bfr) const +{ + return _blocktrees.size() ? cellIndex(bfr) : -1; +} + +//////////////////////////////////////////////////////////////////// + +void TetraMeshSnapshot::getEntities(EntityCollection& entities, Position bfr) const +{ + entities.addSingle(cellIndex(bfr)); +} + +//////////////////////////////////////////////////////////////////// + +class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator +{ + const TetraMeshSnapshot* _grid{nullptr}; + int _mr{-1}; + // add entering face index for next + +public: + MySegmentGenerator(const TetraMeshSnapshot* grid) : _grid(grid) {} + + bool next() override + { + switch (state()) + { + case State::Unknown: + { + // try moving the photon packet inside the grid; if this is impossible, return an empty path + if (!moveInside(_grid->extent(), _grid->_eps)) return false; + + // get the index of the cell containing the current position + _mr = _grid->cellIndex(r()); + + // if the photon packet started outside the grid, return the corresponding nonzero-length segment; + // otherwise fall through to determine the first actual segment + if (ds() > 0.) return true; + } + + // intentionally falls through + case State::Inside: + { + // loop in case no exit point was found (which should happen only rarely) + while (true) + { + // get the site position for this cell + Vec pr = _grid->_cells[_mr]->position(); + + // initialize the smallest nonnegative intersection distance and corresponding index + double sq = DBL_MAX; // very large, but not infinity (so that infinite si values are discarded) + const int NO_INDEX = -99; // meaningless cell index + int mq = NO_INDEX; + + // plucker coords + const Plucker ray = Plucker(r(), k()); + + for (int face = 0; face < 4; face++) + { + std::array edges; + Plucker::face(edges, _grid->_tetrahedra[_mr]->_vertices, face); + + int leaveFace = -1; + for (int i = 0; i < 3; i++) + { + double prod = Plucker::dot(ray, edges[i]); + // how to ensure edges are aligned the same way? + // prod < 0 or prod > 0 we don't know + } + } + + // loop over the list of neighbor indices + const vector& mv = _grid->_cells[_mr]->neighbors(); + int n = mv.size(); + for (int i = 0; i < n; i++) + { + int mi = mv[i]; + + // declare the intersection distance for this neighbor (init to a value that will be rejected) + double si = 0; + + // --- intersection with neighboring cell + if (mi >= 0) + { + // get the site position for this neighbor + Vec pi = _grid->_cells[mi]->position(); + + // calculate the (unnormalized) normal on the bisecting plane + Vec n = pi - pr; + + // calculate the denominator of the intersection quotient + double ndotk = Vec::dot(n, k()); + + // if the denominator is negative the intersection distance is negative, + // so don't calculate it + if (ndotk > 0) + { + // calculate a point on the bisecting plane + Vec p = 0.5 * (pi + pr); + + // calculate the intersection distance + si = Vec::dot(n, p - r()) / ndotk; + } + } + + // --- intersection with domain wall + else + { + switch (mi) + { + case -1: si = (_grid->extent().xmin() - rx()) / kx(); break; + case -2: si = (_grid->extent().xmax() - rx()) / kx(); break; + case -3: si = (_grid->extent().ymin() - ry()) / ky(); break; + case -4: si = (_grid->extent().ymax() - ry()) / ky(); break; + case -5: si = (_grid->extent().zmin() - rz()) / kz(); break; + case -6: si = (_grid->extent().zmax() - rz()) / kz(); break; + default: throw FATALERROR("Invalid neighbor ID"); + } + } + + // remember the smallest nonnegative intersection point + if (si > 0 && si < sq) + { + sq = si; + mq = mi; + } + } + + // if no exit point was found, advance the current point by a small distance, + // recalculate the cell index, and return to the start of the loop + if (mq == NO_INDEX) + { + propagater(_grid->_eps); + _mr = _grid->cellIndex(r()); + + // if we're outside the domain, terminate the path without returning a path segment + if (_mr < 0) + { + setState(State::Outside); + return false; + } + } + // otherwise set the current point to the exit point and return the path segment + else + { + propagater(sq + _grid->_eps); + setSegment(_mr, sq); + _mr = mq; + + // if we're outside the domain, terminate the path after returning this path segment + if (_mr < 0) setState(State::Outside); + return true; + } + } + } + + case State::Outside: + { + } + } + return false; + } +}; + +//////////////////////////////////////////////////////////////////// + +void TetraMeshSnapshot::getEntities(EntityCollection& entities, Position bfr, Direction bfk) const +{ + // initialize a path segment generator + MySegmentGenerator generator(this); + generator.start(bfr, bfk); + + // determine and store the path segments in the entity collection + entities.clear(); + while (generator.next()) + { + entities.add(generator.m(), generator.ds()); + } +} + +//////////////////////////////////////////////////////////////////// + +std::unique_ptr TetraMeshSnapshot::createPathSegmentGenerator() const +{ + return std::make_unique(this); +} + +//////////////////////////////////////////////////////////////////// diff --git a/SKIRT/core/TetraMeshSnapshot.hpp b/SKIRT/core/TetraMeshSnapshot.hpp new file mode 100644 index 00000000..81d04655 --- /dev/null +++ b/SKIRT/core/TetraMeshSnapshot.hpp @@ -0,0 +1,492 @@ +/*////////////////////////////////////////////////////////////////// +//// The SKIRT project -- advanced radiative transfer //// +//// © Astronomical Observatory, Ghent University //// +///////////////////////////////////////////////////////////////// */ + +#ifndef TETRAMESHSNAPSHOT_HPP +#define TETRAMESHSNAPSHOT_HPP + +#include "Array.hpp" +#include "Snapshot.hpp" +class PathSegmentGenerator; +class SiteListInterface; +class SpatialGridPath; + +//////////////////////////////////////////////////////////////////// + +/** A TetraMeshSnapshot object represents a Tetra tessellation or \em mesh of a cuboidal + spatial domain (given a list of generating sites) and offers several facilities related to this + mesh. A Tetra mesh partitions the domain in convex polyhedra. Consider a given set of points + in the domain, called \em sites. For each site there will be a corresponding region consisting + of all points in the domain closer to that site than to any other. These regions are called + Tetra \em cells, and together they form the Tetra tessellation of the domain. + + The TetraMeshSnapshot class serves two key purposes: (1) represent snapshot data produced by + a hydrodynamical simulation and imported from a column text file, defining a primary source or + a transfer medium distribution, and (2) implement an unstructured spatial grid that discretizes + the spatial domain as a basis for the radiative transfer simulation itself. + + To support the first use case (imported snapshot data), the TetraMeshSnapshot class is based + on the Snapshot class; it uses the facilities offered there to configure and help read the + snapshot data, and it implements all functions in the general Snapshot public interface. In + addition it offers functionality that is specific to this snapshot type, such as, for example, + the requirement to configure the spatial extent of the domain. In this use case, the client + employs the default constructor and then proceeds to configure the snapshot object as described + in the Snapshot class header. + + To support the second use case (spatial grid for radiative transfer), the class offers + specialty constructors that accept a list of the generating sites from various sources, + including a programmatically generated list or a user-supplied text column file. Note that this + input file includes just the site coordinates, while a snapshot data file would include + additional properties for each site. The specialty constructors automatically complete the + configuration sequence of the object, so that the getters can be used immediately after + construction. + + Once an TetraMeshSnapshot object has been constructed and fully configured, its data members + are no longer modified. Consequently all getters are re-entrant. + + Using the Voro++ library + ------------------------ + + To build the Tetra tessellation, the buildMesh() function in this class uses the code in the + \c voro subfolder of the SKIRT code hierarchy, which is taken from the Voro++ library written + by Chris H. Rycroft (Harvard University/Lawrence Berkeley Laboratory) at + https://github.com/chr1shr/voro (git commit 122531f) with minimal changes to avoid compiler + warnings. + + A distinguishing feature of the Voro++ library is that it carries out cell-based calculations, + computing the Tetra cell for each site individually, rather than computing the Tetra + tessellation as a global network of vertices and edges. It is therefore particularly + well-suited for applications that require cell-based properties such as the cell volume, the + centroid position or the number of faces or vertices. Equally important in the context of + SKIRT, it is easy to distribute the work over parallel execution threads because, after setting + up a common search data structure holding all sites, the calculations for the various cells are + mutually independent. + + The Voro++ approach also has an important drawback. Because cells are handled independently of + each other, floating pointing rounding errors sometimes cause inconsistencies where the results + for one cell do not properly match the corresponding results for a neighboring cell. This most + often happens when the generating sites are very close to each other and/or form certain + hard-to-calculate geometries. These problems manifest themselves either as empty cells or as + asymmetries in the neighbor lists. To handle these situations, the buildMesh() function removes + some sites from the input list as described below. + + Firstly, before actually building the Tetra tessellation, and in addition to removing sites + that lie outside of the spatial domain, the function discards sites that lie closer to any + previously listed site than \f$10^{-12}\f$ times the diagonal of the spatial domain. This + preventative measure essentially removes any "degenerate" sites from the input list. Given the + extremely small distances, it is very unlikely that the physcis of the input model would be + affected by this operation. + + Secondly, after all Tetra cells have been calculated, the function verifies the cell + properties. If any of the cells have a zero volume or if any of the cell neighbors are not + mutual, the involved sites are discarded and the Tetra construction starts anew from scratch + with the reduced list of sites. After a maximum of 5 attempts, the function throws a fatal + error. In practice, this will hopefully never happen. Discarding incorrectly calculated cells + perhaps incurs a slightly higher risk of changing the physcis of the input model. However, in + practice it seems that these issues mostly occur in regions of high site density, so that the + errors should be fairly limited. */ +class TetraMeshSnapshot : public Snapshot +{ + //================= Construction - Destruction ================= + +public: + /** The default constructor initializes the snapshot in an invalid state; see the description + of the required calling sequence in the Snapshot class header. */ + TetraMeshSnapshot(); + + /** The destructor releases any data structures allocated by this class. */ + ~TetraMeshSnapshot(); + + //========== Reading ========== + +public: + /** This function reads the snapshot data from the input file, honoring the options set through + the configuration functions, stores the data for later use, and closes the file by calling + the base class Snapshot::readAndClose() function. Sites located outside of the domain and + sites that are too close to another site are discarded. Sites with an associated + temperature above the cutoff temperature (if one has been configured) are assigned a + density value of zero, so that the corresponding cell has zero mass (regardless of the + imported mass/density properties). + + The function calls the private buildMesh() function to build the Tetra mesh based on the + imported site positions. If the snapshot configuration requires the ability to determine + the density at a given spatial position, the function also calls the private buildSearch() + function to create a data structure that accelerates locating the cell containing a given + point. + + During its operation, the function logs some statistical information about the imported + snapshot and the resulting data structures. */ + void readAndClose() override; + + //========== Configuration ========== + +public: + /** This function sets the extent of the spatial domain for the Tetra mesh. When using the + default constructor, this function must be called during configuration. There is no + default; failing to set the extent of the domain results in undefined behavior. */ + void setExtent(const Box& extent); + + /** This function configures the snapshot to skip construction of the actual Tetra + tessellation and instead use a search tree across all sites. It should be called only if + (1) the snapshot has been configured to import both a mass/number density column \em and a + volume-integrated mass/number column, and (2) the snapshot will not be required to generate + random positions or trace paths. Violating these conditions will result in undefined + behavior. */ + void foregoTetraMesh(); + + //========== Specialty constructors ========== + +public: + /** This constructor reads the site positions from the specified text column file. The input + file must contain three columns specifying the x,y,z coordinates. The default unit is + parsec, which can be overridden by providing column header info in the file. The + constructor completes the configuration for the object (but without importing mass density + information or setting a mass density policy) and calls the private buildMesh() and + buildSearch() functions to create the relevant data structures. + + The \em item argument specifies a simulation item in the hierarchy of the caller (usually + the caller itself) used to retrieve context such as an appropriate logger. The \em extent + argument specifies the extent of the domain as a box lined up with the coordinate axes. + Sites located outside of the domain and sites that are too close to another site are + discarded. The \em filename argument specifies the name of the input file, including + filename extension but excluding path and simulation prefix. If the \em relax argument is + true, the function performs a single relaxation step on the site positions. */ + TetraMeshSnapshot(const SimulationItem* item, const Box& extent, string filename, bool relax); + + /** This constructor obtains the site positions from a SiteListInterface instance. The + constructor completes the configuration for the object (but without importing mass density + information or setting a mass density policy) and calls the private buildMesh() and + buildSearch() functions to create the relevant data structures. + + The \em item argument specifies a simulation item in the hierarchy of the caller (usually + the caller itself) used to retrieve context such as an appropriate logger. The \em extent + argument specifies the extent of the domain as a box lined up with the coordinate axes. + Sites located outside of the domain and sites that are too close to another site are + discarded. The \em sli argument specifies an object that provides the SiteListInterface + interface from which to obtain the site positions. If the \em relax argument is true, the + function performs a single relaxation step on the site positions. */ + TetraMeshSnapshot(const SimulationItem* item, const Box& extent, SiteListInterface* sli, bool relax); + + /** This constructor obtains the site positions from a programmatically prepared list. The + constructor completes the configuration for the object (but without importing mass density + information or setting a mass density policy) and calls the private buildMesh() and + buildSearch() functions to create the relevant data structures. + + The \em item argument specifies a simulation item in the hierarchy of the caller (usually + the caller itself) used to retrieve context such as an appropriate logger. The \em extent + argument specifies the extent of the domain as a box lined up with the coordinate axes. + Sites located outside of the domain and sites that are too close to another site are + discarded. The \em sites argument specifies the list of site positions. If the \em relax + argument is true, the function performs a single relaxation step on the site positions. */ + TetraMeshSnapshot(const SimulationItem* item, const Box& extent, const vector& sites, bool relax); + + //=========== Private construction ========== + +private: + /** Private class to hold the information about a Tetra cell that is relevant for calculating + paths and densities; see the buildMesh() function. */ + class Cell; + + class Tetra; + + class Plucker; + + // temp test + std::vector centers; + std::vector radii; + + /** Private class to hold a node in the internal binary search tree; see the buildTree() + and buildSearch() functions. */ + class Node; + + /** Given a list of generating sites (represented as partially initialized Cell + objects), this private function builds the Tetra tessellation and stores the + corresponding cell information, including any properties relevant for supporting the + interrogation capabilities offered by this class. All other data (such as Tetra cell + vertices, edges and faces) are discarded. In practice, the function adds the sites to a + Voro++ container, computes the Tetra cells one by one, and copies the relevant cell + information (such as the list of neighboring cells) from the Voro++ data structures into + its own. + + If the \em relax argument is true, the function performs a single relaxation step on the + site positions using Lloyd's algorithm (Lloyd 1982; Du, Faber and Gunzburger 1999, SIAM + review 41.4, pp 637-676; Dobbels 2017, master thesis). An intermediate Tetra tessellation + is built using the original site positions, and subsequently each site position is replaced + by the centroid (mass center) of the corresponding cell. The final tessellation is then + constructed with these adjusted site positions, which are distributed more uniformly, + thereby avoiding overly elongated cells in the Tetra tessellation. Relaxation can be + quite time-consuming because the Tetra tessellation must be constructed twice. */ + void buildMesh(bool relax); + + /** This private function calculates the volumes for all cells without using the Tetra mesh. + It assumes that both mass and mass density columns are being imported. */ + void calculateVolume(); + + /** This private function calculates the densities and (cumulative) masses for all cells, and + logs some statistics. The function assumes that the cell volumes have been calculated, + either by building a Tetra tessellation, or by deriving the volume from mass and mass + density columns being imported. */ + void calculateDensityAndMass(); + + /** Private function to recursively build a binary search tree (see + en.wikipedia.org/wiki/Kd-tree) */ + Node* buildTree(vector::iterator first, vector::iterator last, int depth) const; + + /** This private function builds data structures that allow accelerating the operation of the + cellIndex() function. It assumes that the Tetra mesh has already been built. + + The domain is partitioned using a linear cubodial grid into cells that are called \em + blocks. For each block, the function builds and stores a list of all Tetra cells that + possibly overlap that block. Retrieving the list of cells possibly overlapping a given + point in the domain then comes down to locating the block containing the point (which is + trivial since the grid is linear). The current implementation uses a Tetra cell's + enclosing cuboid to test for intersection with a block. Performing a precise intersection + test is \em really slow and the shortened block lists don't substantially accelerate the + cellIndex() function. + + To further reduce the search time within blocks that overlap with a large number of cells, + the function builds a binary search tree on the cell sites for those blocks (see for example + en.wikipedia.org/wiki/Kd-tree). */ + void buildSearchPerBlock(); + + /** This private function builds a data structure that allows accelerating the operation of the + cellIndex() function without using the Tetra mesh. The domain is not partitioned in + blocks. The function builds a single binary search tree on all cell sites (see for example + en.wikipedia.org/wiki/Kd-tree). */ + void buildSearchSingle(); + + /** This private function returns true if the given point is closer to the site with index m + than to the sites with indices ids. */ + bool isPointClosestTo(Vec r, int m, const vector& ids) const; + + bool inTetrahedra(const Tetra* tetra) const; + + //====================== Output ===================== + +public: + /** This function outputs grid plot files as described for the SpatialGridPlotProbe. The + function reconstructs the Tetra tesselation in order to produce the coordinates of the + Tetra cell vertices. */ + void writeGridPlotFiles(const SimulationItem* probe) const; + + //=========== Interrogation ========== + +public: + /** This function returns the extent of the spatial domain as configured through the + setExtent() function. */ + Box extent() const override; + + /** This function returns the number of sites (or, equivalently, cells) in the snapshot. */ + int numEntities() const override; + + /** This function returns the position of the site with index \em m. If the index is out of + range, the behavior is undefined. */ + Position position(int m) const override; + + /** This function returns the centroid of the Tetra cell with index \em m. If the index is + out of range, the behavior is undefined. */ + Position centroidPosition(int m) const; + + /** This function returns the volume of the Tetra cell with index \em m. If the index is out + of range, the behavior is undefined. */ + double volume(int m) const override; + + /** This function returns the bounding box (enclosing cuboid lined up with the coordinate axes) + of the Tetra cell with index \em m. If the index is out of range, the behavior is + undefined. */ + Box extent(int m) const; + + /** This function returns the mass density associated with the cell with index \em m. If no + density policy has been set or no mass information is being imported, or if the index is + out of range, the behavior is undefined. */ + double density(int m) const override; + + /** This function returns the mass density represented by the snapshot at a given point + \f${\bf{r}}\f$, or equivalently, the mass density associated with the cell containing the + given point. If the point is outside the domain, the function returns zero. If no density + policy has been set or no mass information is being imported, or if the search data + structures used by the cellIndex() function were not created during construction, the + behavior is undefined. */ + double density(Position bfr) const override; + + /** This function returns the total mass represented by the snapshot, in other words the sum of + the masses of all cells. If no density policy has been set or no mass information is being + imported, the behavior is undefined. */ + double mass() const override; + + /** This function returns a random position drawn uniformly from the (polyhedron) volume of the + cell with index \em m. If the index is out of range, the behavior is undefined. + + The function generates uniformly distributed random points in the enclosing cuboid until + one happens to be inside the cell. The candidate point is inside the cell if it is closer + to the cell's site position than to any neighbor cell's site positions. */ + Position generatePosition(int m) const override; + + /** This function returns a random position within the spatial domain of the snapshot, drawn + from the mass density distribution represented by the snapshot. The function first selects + a random cell from the discrete probability distribution formed by the respective cell + masses, and then generates a random position uniformly from the volume of that cell. If no + density policy has been set or no mass information is being imported, the behavior is + undefined. */ + Position generatePosition() const override; + + /** This function returns the cell index \f$0\le m \le N_{cells}-1\f$ for the cell containing + the specified point \f${\bf{r}}\f$. If the point is outside the domain, the function + returns -1. By definition of a Tetra tesselation, the closest site position determines + the Tetra cell containing the specified point. + + The function uses the search data structures created by the private BuildSearch() function + to accelerate its operation. It computes the appropriate block index from the coordinates + of the specified point, which provides a list of Tetra cells possibly overlapping the + point. If there is a search tree for this block, the function uses it to locate the nearest + point in \f${\cal{O}}(\log N)\f$ time. Otherwise it calculates the distance from the + specified point to the site positions for each of the possibly overlapping cells, + determining the nearest one in linear time. For a small number of cells this is more + efficient than using the search tree. + + If the search data structures were not created during construction (which happens when + using the default constructor without configuring a mass density policy), invoking the + cellIndex() function causes undefined behavior. */ + int cellIndex(Position bfr) const; + +protected: + /** This function returns a reference to an array containing the imported properties (in column + order) for the cell with index \f$0\le m \le N_\mathrm{ent}-1\f$. If the index is out of + range, the behavior is undefined. */ + const Array& properties(int m) const override; + + /** This function returns the index \f$0\le m \le N_\mathrm{ent}-1\f$ of the cell containing + the specified point \f${\bf{r}}\f$, or -1 if the point is outside the domain, if there + are no cells in the snapshot, or if the search data structures were not created. */ + int nearestEntity(Position bfr) const override; + +public: + /** This function sets the specified entity collection to the cell containing the specified + point \f${\bf{r}}\f$, or to the empty collection if the point is outside the domain or if + there are no cells in the snapshot. If the search data structures were not created, + invoking this function causes undefined behavior. */ + void getEntities(EntityCollection& entities, Position bfr) const override; + + /** This function replaces the contents of the specified entity collection by the set of cells + crossed by the specified path with starting point \f${\bf{r}}\f$ and direction + \f${\bf{k}}\f$. The weight of a cell is given by the length of the path segment inside the + cell. If the path does not cross the spatial domain of the snapshot, the collection will be + empty. If the search data structures were not created, invoking this function causes + undefined behavior. */ + void getEntities(EntityCollection& entities, Position bfr, Direction bfk) const override; + + //====================== Path construction ===================== + +public: + /** This function creates and hands over ownership of a path segment generator appropriate for + the adaptive mesh spatial grid, implemented as a private PathSegmentGenerator subclass. The + algorithm used to construct the path is described below. + + In the first stage, the function checks whether the start point is inside the domain. If + so, the current point is simply initialized to the start point. If not, the function + computes the path segment to the first intersection with one of the domain walls and moves + the current point inside the domain. Finally the function determines the current cell, i.e. + the cell containing the current point. + + In the second stage, the function loops over the algorithm that computes the exit point + from the current cell, i.e. the intersection of the ray formed by the current point and + the path direction with the current cell's boundary. By the nature of Tetra cells, this + algorithm also produces the ID of the neigboring cell without extra cost. If an exit point + is found, the loop adds a segment to the output path, updates the current point and the + current cell, and continues to the next iteration. If the exit is towards a domain wall, + the path is complete and the loop is terminated. If no exit point is found, which shouldn't + happen too often, this must be due to computational inaccuracies. In that case, no path + segment is added, the current point is advanced by a small amount, and the new current cell + is determined by calling the function whichcell(). + + The algorithm that computes the exit point has the following input data: + + + + + + + + +
\f$\bf{r}\f$ %Position of the current point
\f$\bf{k}\f$ %Direction of the ray, normalized
\f$m_r\f$ ID of the cell containing the current point
\f$m_i,\;i=0\ldots n-1\f$ IDs of the neighboring cells (\f$m_i>=0\f$) + or domain walls (\f$m_i<0\f$)
\f$\mathbf{p}(m)\f$ Site position for cell \f$m\f$ (implicit)
\f$x_\text{min},x_\text{max},y_\text{min},y_\text{max},z_\text{min},z_\text{max}\f$Domain boundaries (implicit)
+ where the domain wall IDs are defined as follows: + + + + + + + + +
Domain wall ID Domain wall equation
-1 \f$x=x_\text{min}\f$
-2 \f$x=x_\text{max}\f$
-3 \f$y=y_\text{min}\f$
-4 \f$y=y_\text{max}\f$
-5 \f$z=z_\text{min}\f$
-6 \f$z=z_\text{max}\f$
+ + The line containing the ray can be written as \f$\mathcal{L}(s)=\mathbf{r}+s\,\mathbf{k}\f$ + with \f$s\in\mathbb{R}\f$. The exit point can similarly be written as + \f$\mathbf{q}=\mathbf{r}+s_q\,\mathbf{k}\f$ with \f$s_q>0\f$, and the distance covered within + the cell is given by \f$s_q\f$. The ID of the cell next to the exit point is denoted \f$m_q\f$ + and is easily determined as explained below. + + The algorithm that computes the exit point proceeds as follows: + - Calculate the set of values \f$\{s_i\}\f$ for the intersection points between the line + \f$\mathcal{L}(s)\f$ and the planes defined by the neighbors or walls \f$m_i\f$ + (see below for details on the intersection calculation). + - Select the smallest nonnegative value \f$s_q=\min\{s_i|s_i>0\}\f$ in the set to determine + the exit point. The neighbor or wall ID with the same index \f$i\f$ determines the + corresponding \f$m_q\f$. + - If there is no nonnegative value in the set, no exit point has been found. + + To calculate \f$s_i\f$ for a regular neighbor \f$m_i\geq 0\f$, intersect the + line \f$\mathcal{L}(s)=\mathbf{r}+s\,\mathbf{k}\f$ with the plane bisecting the points + \f$\mathbf{p}(m_i)\f$ and \f$\mathbf{p}(m_r)\f$. An unnormalized vector perpendicular to + this plane is given by \f[\mathbf{n}=\mathbf{p}(m_i)-\mathbf{p}(m_r)\f] + and a point on the plane is given by + \f[\mathbf{p}=\frac{\mathbf{p}(m_i)+\mathbf{p}(m_r)}{2}.\f] The equation + of the plane can then be written as \f[\mathbf{n}\cdot(\mathbf{x}-\mathbf{p})=0.\f] + Substituting \f$\mathbf{x}=\mathbf{r}+s_i\,\mathbf{k}\f$ and solving for \f$s_i\f$ provides + \f[s_i=\frac{\mathbf{n}\cdot(\mathbf{p}-\mathbf{r})}{\mathbf{n}\cdot\mathbf{k}}.\f] + If \f$\mathbf{n}\cdot\mathbf{k}=0\f$ the line and the plane are parallel and there + is no intersection. In that case no \f$s_i\f$ is added to the set of candidate + exit points. + + To calculate \f$s_i\f$ for a wall \f$m_i<0\f$, substitute the appropriate normal and + position vectors for the wall plane in this last formula. For example, for the left wall + with \f$m_i=-1\f$ one has \f$\mathbf{n}=(-1,0,0)\f$ and \f$\mathbf{p}=(x_\text{min},0,0)\f$ + so that \f[s_i=\frac{x_\text{min}-r_x}{k_x}.\f] + */ + std::unique_ptr createPathSegmentGenerator() const; + + //======================== Data Members ======================== + +private: + // data members initialized during configuration + Box _extent; // the spatial domain of the mesh + double _eps{0.}; // small fraction of extent + bool _foregoTetraMesh{false}; // true if using search tree instead of Tetra tessellation + + // data members initialized when processing snapshot input and further completed by BuildMesh() + vector _cells; // cell objects, indexed on m + + vector _tetrahedra; + + // data members initialized when processing snapshot input, but only if a density policy has been set + Array _rhov; // density for each cell (not normalized) + Array _cumrhov; // normalized cumulative density distribution for cells + double _mass{0.}; // total effective mass + + // data members initialized by BuildSearch() + int _nb{0}; // number of blocks in each dimension (limit for indices i,j,k) + int _nb2{0}; // nb*nb + int _nb3{0}; // nb*nb*nb + vector> _blocklists; // list of cell indices per block, indexed on i*_nb2+j*_nb+k + vector _blocktrees; // root node of search tree or null for each block, indexed on i*_nb2+j*_nb+k + + // allow our path segment generator to access our private data members + class MySegmentGenerator; + friend class MySegmentGenerator; +}; + +//////////////////////////////////////////////////////////////////// + +#endif diff --git a/SKIRT/core/TetraMeshSpatialGrid.cpp b/SKIRT/core/TetraMeshSpatialGrid.cpp new file mode 100644 index 00000000..97d4b59b --- /dev/null +++ b/SKIRT/core/TetraMeshSpatialGrid.cpp @@ -0,0 +1,223 @@ +/*////////////////////////////////////////////////////////////////// +//// The SKIRT project -- advanced radiative transfer //// +//// © Astronomical Observatory, Ghent University //// +///////////////////////////////////////////////////////////////// */ + +#include "TetraMeshSpatialGrid.hpp" +#include "FatalError.hpp" +#include "Log.hpp" +#include "MediumSystem.hpp" +#include "NR.hpp" +#include "PathSegmentGenerator.hpp" +#include "Random.hpp" +#include "SiteListInterface.hpp" +#include "TetraMeshInterface.hpp" +#include "TetraMeshSnapshot.hpp" + +////////////////////////////////////////////////////////////////////// + +TetraMeshSpatialGrid::~TetraMeshSpatialGrid() +{ + if (_policy != Policy::ImportedMesh) delete _mesh; +} + +////////////////////////////////////////////////////////////////////// + +namespace +{ + // sample random positions from the given media components with the given relative weigths + vector sampleMedia(const vector& media, const vector& weights, const Box& extent, + int numSites) + { + if (media.empty()) throw FATALERROR("There is no material of the requested type in the medium system"); + + // build the cumulative weight distribution + Array Xv; + NR::cdf(Xv, media.size(), [weights](int h) { return weights[h]; }); + auto random = media[0]->find(); + + // sample + vector rv(numSites); + for (int m = 0; m != numSites;) + { + int h = NR::locateClip(Xv, random->uniform()); + Position p = media[h]->generatePosition(); + if (extent.contains(p)) rv[m++] = p; // discard any points outside of the domain + } + return rv; + } +} + +////////////////////////////////////////////////////////////////////// + +void TetraMeshSpatialGrid::setupSelfBefore() +{ + BoxSpatialGrid::setupSelfBefore(); + + // determine an appropriate set of sites and construct the Tetra mesh + switch (_policy) + { + case Policy::Uniform: + { + auto random = find(); + vector rv(_numSites); + for (int m = 0; m != _numSites; ++m) rv[m] = random->position(extent()); + _mesh = new TetraMeshSnapshot(this, extent(), rv, _relaxSites); + break; + } + case Policy::CentralPeak: + { + auto random = find(); + const int a = 1000; // steepness of the peak; the central 1/a portion is NOT covered + const double rscale = extent().rmax().norm(); + vector rv(_numSites); + for (int m = 1; m != _numSites;) // skip first particle so that it remains (0,0,0) + { + double r = rscale * pow(1. / a, random->uniform()); // random distribution according to 1/x + Direction k = random->direction(); + Position p = Position(r, k); + if (extent().contains(p)) rv[m++] = p; // discard any points outside of the domain + } + _mesh = new TetraMeshSnapshot(this, extent(), rv, _relaxSites); + break; + } + case Policy::DustDensity: + { + // build a list of media that have this material type with corresponding weights + vector media; + vector weights; + auto ms = find(); + for (auto medium : ms->media()) + if (medium->mix()->isDust()) media.push_back(medium); + for (auto medium : media) weights.push_back(medium->mass()); + _mesh = + new TetraMeshSnapshot(this, extent(), sampleMedia(media, weights, extent(), _numSites), _relaxSites); + break; + } + case Policy::ElectronDensity: + { + // build a list of media that have this material type with corresponding weights + vector media; + vector weights; + auto ms = find(); + for (auto medium : ms->media()) + if (medium->mix()->isElectrons()) media.push_back(medium); + for (auto medium : media) weights.push_back(medium->number()); + _mesh = + new TetraMeshSnapshot(this, extent(), sampleMedia(media, weights, extent(), _numSites), _relaxSites); + break; + } + case Policy::GasDensity: + { + // build a list of media that have this material type with corresponding weights + vector media; + vector weights; + auto ms = find(); + for (auto medium : ms->media()) + if (medium->mix()->isGas()) media.push_back(medium); + for (auto medium : media) weights.push_back(medium->number()); + _mesh = + new TetraMeshSnapshot(this, extent(), sampleMedia(media, weights, extent(), _numSites), _relaxSites); + break; + } + case Policy::File: + { + _mesh = new TetraMeshSnapshot(this, extent(), _filename, _relaxSites); + break; + } + case Policy::ImportedSites: + { + auto sli = find()->interface(2); + _mesh = new TetraMeshSnapshot(this, extent(), sli, _relaxSites); + break; + } + case Policy::ImportedMesh: + { + auto ms = find(false); + _mesh = ms->interface(2)->tetraMesh(); + + // if there is a single medium component, calculate the normalization factor imposed by it; + // we need this to directly compute cell densities for the DensityInCellInterface + if (ms->media().size() == 1) _norm = _mesh->mass() > 0 ? ms->media()[0]->number() / _mesh->mass() : 0.; + break; + } + } +} + +////////////////////////////////////////////////////////////////////// + +int TetraMeshSpatialGrid::numCells() const +{ + return _mesh->numEntities(); +} + +////////////////////////////////////////////////////////////////////// + +double TetraMeshSpatialGrid::volume(int m) const +{ + return _mesh->volume(m); +} + +////////////////////////////////////////////////////////////////////// + +double TetraMeshSpatialGrid::diagonal(int m) const +{ + return cbrt(3. * _mesh->volume(m)); +} + +////////////////////////////////////////////////////////////////////// + +int TetraMeshSpatialGrid::cellIndex(Position bfr) const +{ + return _mesh->cellIndex(bfr); +} + +////////////////////////////////////////////////////////////////////// + +Position TetraMeshSpatialGrid::centralPositionInCell(int m) const +{ + return _mesh->centroidPosition(m); +} + +////////////////////////////////////////////////////////////////////// + +Position TetraMeshSpatialGrid::randomPositionInCell(int m) const +{ + return _mesh->generatePosition(m); +} + +////////////////////////////////////////////////////////////////////// + +std::unique_ptr TetraMeshSpatialGrid::createPathSegmentGenerator() const +{ + return _mesh->createPathSegmentGenerator(); +} + +////////////////////////////////////////////////////////////////////// + +void TetraMeshSpatialGrid::writeGridPlotFiles(const SimulationItem* probe) const +{ + _mesh->writeGridPlotFiles(probe); +} + +////////////////////////////////////////////////////////////////////// + +double TetraMeshSpatialGrid::numberDensity(int /*h*/, int m) const +{ + return _norm * _mesh->density(m); +} + +////////////////////////////////////////////////////////////////////// + +bool TetraMeshSpatialGrid::offersInterface(const std::type_info& interfaceTypeInfo) const +{ + if (interfaceTypeInfo == typeid(DensityInCellInterface)) + { + if (_policy != Policy::ImportedMesh) return false; + auto ms = find(false); + return ms && ms->media().size() == 1; + } + return SpatialGrid::offersInterface(interfaceTypeInfo); +} + +////////////////////////////////////////////////////////////////////// diff --git a/SKIRT/core/TetraMeshSpatialGrid.hpp b/SKIRT/core/TetraMeshSpatialGrid.hpp new file mode 100644 index 00000000..06484a4b --- /dev/null +++ b/SKIRT/core/TetraMeshSpatialGrid.hpp @@ -0,0 +1,136 @@ +/*////////////////////////////////////////////////////////////////// +//// The SKIRT project -- advanced radiative transfer //// +//// © Astronomical Observatory, Ghent University //// +///////////////////////////////////////////////////////////////// */ + +#ifndef TETRAMESHSPATIALGRID_HPP +#define TETRAMESHSPATIALGRID_HPP + +#include "BoxSpatialGrid.hpp" +#include "DensityInCellInterface.hpp" +class TetraMeshSnapshot; + +////////////////////////////////////////////////////////////////////// + +/** TetraMeshSpatialGrid is a concrete subclass of the SpatialGrid class. It represents a + three-dimensional grid based on a Tetra tesselation of the cuboidal spatial domain of the + simulation. See the TetraMeshSnapshot class for more information on Tetra tesselations. + + The class offers several options for determining the positions of the sites generating the + Tetra tesselation. A specified number of sites can be distributed randomly over the domain, + either uniformly or with the same overall density distribution as the medium. Alternatively, + the positions can be copied from the sites in the imported distribution(s). + + Furthermore, the user can opt to perform a relaxation step on the site positions to avoid + overly elongated cells. */ +class TetraMeshSpatialGrid : public BoxSpatialGrid, public DensityInCellInterface +{ + /** The enumeration type indicating the policy for determining the positions of the sites. */ + ENUM_DEF(Policy, Uniform, CentralPeak, DustDensity, ElectronDensity, GasDensity, File, ImportedSites, ImportedMesh) + ENUM_VAL(Policy, Uniform, "random from uniform distribution") + ENUM_VAL(Policy, CentralPeak, "random from distribution with a steep central peak") + ENUM_VAL(Policy, DustDensity, "random from dust density distribution") + ENUM_VAL(Policy, ElectronDensity, "random from electron density distribution") + ENUM_VAL(Policy, GasDensity, "random from gas density distribution") + ENUM_VAL(Policy, File, "loaded from text column data file") + ENUM_VAL(Policy, ImportedSites, "positions of particles, sites or cells in imported distribution") + ENUM_VAL(Policy, ImportedMesh, "employ imported Tetra mesh in medium system") + ENUM_END() + + ITEM_CONCRETE(TetraMeshSpatialGrid, BoxSpatialGrid, "a Tetra tessellation-based spatial grid") + ATTRIBUTE_TYPE_DISPLAYED_IF(TetraMeshSpatialGrid, "Level2") + + PROPERTY_ENUM(policy, Policy, "the policy for determining the positions of the sites") + ATTRIBUTE_DEFAULT_VALUE(policy, "DustDensity") + + PROPERTY_INT(numSites, "the number of random sites (or cells in the grid)") + ATTRIBUTE_MIN_VALUE(numSites, "5") + ATTRIBUTE_DEFAULT_VALUE(numSites, "500") + ATTRIBUTE_RELEVANT_IF(numSites, "policyUniform|policyCentralPeak|policyDustDensity|" + "policyElectronDensity|policyGasDensity") + + PROPERTY_STRING(filename, "the name of the file containing the site positions") + ATTRIBUTE_RELEVANT_IF(filename, "policyFile") + + PROPERTY_BOOL(relaxSites, "perform site relaxation to avoid overly elongated cells") + ATTRIBUTE_DEFAULT_VALUE(relaxSites, "false") + ATTRIBUTE_RELEVANT_IF(relaxSites, "!policyImportedMesh") + + ITEM_END() + + //============= Construction - Setup - Destruction ============= + +public: + /** The destructor releases the Tetra mesh if this object owns it. */ + ~TetraMeshSpatialGrid(); + +protected: + /** This function verifies that the attributes have been appropriately set, generates or + retrieves the site positions for constructing the Tetra tessellation according to the + configured policy, and finally constructs the Tetra tessellation through an instance of + the TetraMeshSnapshot class. */ + void setupSelfBefore() override; + + //======================== Other Functions ======================= + +public: + /** This function returns the number of cells in the grid. */ + int numCells() const override; + + /** This function returns the volume of the cell with index \f$m\f$. */ + double volume(int m) const override; + + /** This function returns the approximate diagonal of the cell with index \f$m\f$. For a + Tetra grid, it returns \f$(3V)^(1/3)\f$ where \f$V\f$ is the volume of the cell. This + corresponds to the correct diagonal only for cubical cells. */ + double diagonal(int m) const override; + + /** This function returns the index of the cell that contains the position \f${\bf{r}}\f$. */ + int cellIndex(Position bfr) const override; + + /** This function returns the central location of the cell with index \f$m\f$. In this class + the function returns the centroid of the Tetra cell. */ + Position centralPositionInCell(int m) const override; + + /** This function returns a random location from the cell with index \f$m\f$. */ + Position randomPositionInCell(int m) const override; + + /** This function creates and hands over ownership of a path segment generator (an instance of + a PathSegmentGenerator subclass) appropriate for this spatial grid type. For the Tetra + mesh grid, the path segment generator is actually implemented in the TetraMeshSnapshot + class. */ + std::unique_ptr createPathSegmentGenerator() const override; + + /** This function outputs the grid plot files; it is provided here because the regular + mechanism does not apply. The function reconstructs the Tetra tesselation in order to + produce the coordinates of the Tetra cell vertices. */ + void writeGridPlotFiles(const SimulationItem* probe) const override; + + /** This function implements the DensityInCellInterface interface. It returns the number + density for medium component \f$h\f$ in the grid cell with index \f$m\f$. For a Tetra + mesh grid, this interface can be offered only if the "ImportedMesh" policy has been + configured, and the medium system consists of a single component, which then, by + definition, supplies the Tetra mesh. Thus, the component index \f$h\f$ passed to this + function should always be zero; in fact, its value is actually ignored. */ + double numberDensity(int h, int m) const override; + +protected: + /** This function is used by the interface() function to ensure that the receiving item can + actually offer the specified interface. If the requested interface is the + DensityInCellInterface, the implementation in this class returns true if the "ImportedMesh" + policy has been configured and the medium system consists of a single component, and false + otherwise. For other requested interfaces, the function invokes its counterpart in the base + class. */ + bool offersInterface(const std::type_info& interfaceTypeInfo) const override; + + //======================== Data Members ======================== + +private: + // data members initialized during setup + TetraMeshSnapshot* _mesh{nullptr}; // Tetra mesh snapshot created here or obtained from medium component + double _norm{0.}; // in the latter case, normalization factor obtained from medium component +}; + +////////////////////////////////////////////////////////////////////// + +#endif From 1ede7f188816682f510c2d39c503c946aa9d6eea Mon Sep 17 00:00:00 2001 From: arlauwer Date: Tue, 31 Oct 2023 19:33:18 +0100 Subject: [PATCH 07/51] grid construction fully working cleaned up unused stuff from voronoi started work on PathSegmentGenerator --- SKIRT/core/TetraMeshSnapshot.cpp | 1087 ++++++++------------------ SKIRT/core/TetraMeshSnapshot.hpp | 16 +- SKIRT/core/TetraMeshSpatialGrid.cpp | 2 +- SKIRT/utils/PathSegmentGenerator.hpp | 4 + 4 files changed, 348 insertions(+), 761 deletions(-) diff --git a/SKIRT/core/TetraMeshSnapshot.cpp b/SKIRT/core/TetraMeshSnapshot.cpp index 0396781e..b1b76978 100644 --- a/SKIRT/core/TetraMeshSnapshot.cpp +++ b/SKIRT/core/TetraMeshSnapshot.cpp @@ -27,70 +27,11 @@ //////////////////////////////////////////////////////////////////// -namespace -{ - // classes used for serializing/deserializing Tetra cell geometry when communicating the results of - // grid construction between multiple processes with the ProcessManager::broadcastAllToAll() function - - // decorates a std::vector with functions to write serialized versions of various data types - class SerializedWrite - { - private: - vector& _data; - - public: - SerializedWrite(vector& data) : _data(data) { _data.clear(); } - void write(double v) { _data.push_back(v); } - void write(Vec v) { _data.insert(_data.end(), {v.x(), v.y(), v.z()}); } - void write(Box v) { _data.insert(_data.end(), {v.xmin(), v.ymin(), v.zmin(), v.xmax(), v.ymax(), v.zmax()}); } - void write(const vector& v) - { - _data.push_back(v.size()); - _data.insert(_data.end(), v.begin(), v.end()); - } - }; - - // decorates a std::vector with functions to read serialized versions of various data types - class SerializedRead - { - private: - const double* _data; - const double* _end; - - public: - SerializedRead(const vector& data) : _data(data.data()), _end(data.data() + data.size()) {} - bool empty() { return _data == _end; } - int readInt() { return *_data++; } - void read(double& v) { v = *_data++; } - void read(Vec& v) - { - v.set(*_data, *(_data + 1), *(_data + 2)); - _data += 3; - } - void read(Box& v) - { - v = Box(*_data, *(_data + 1), *(_data + 2), *(_data + 3), *(_data + 4), *(_data + 5)); - _data += 6; - } - void read(vector& v) - { - int n = *_data++; - v.clear(); - v.reserve(n); - for (int i = 0; i != n; ++i) v.push_back(*_data++); - } - }; -} - -//////////////////////////////////////////////////////////////////// - // class to hold the information about a Tetra cell that is relevant for calculating paths and densities -class TetraMeshSnapshot::Cell : public Box // enclosing box +class TetraMeshSnapshot::Cell { public: Vec _r; // site position - Vec _c; // centroid position - double _volume{0.}; // volume vector _neighbors; // list of neighbor indices in _cells vector Array _properties; // user-defined properties, if any @@ -108,48 +49,12 @@ class TetraMeshSnapshot::Cell : public Box // enclosing box // initializes the receiver with information taken from the specified fully computed Tetra cell void init(voro::voronoicell_neighbor& cell) { - // copy basic geometric info - double cx, cy, cz; - cell.centroid(cx, cy, cz); - _c = Vec(cx, cy, cz) + _r; - _volume = cell.volume(); - - // get the minimal and maximal coordinates of the box enclosing the cell - vector coords; - cell.vertices(_r.x(), _r.y(), _r.z(), coords); - double xmin = DBL_MAX; - double ymin = DBL_MAX; - double zmin = DBL_MAX; - double xmax = -DBL_MAX; - double ymax = -DBL_MAX; - double zmax = -DBL_MAX; - int n = coords.size(); - for (int i = 0; i < n; i += 3) - { - xmin = min(xmin, coords[i]); - ymin = min(ymin, coords[i + 1]); - zmin = min(zmin, coords[i + 2]); - xmax = max(xmax, coords[i]); - ymax = max(ymax, coords[i + 1]); - zmax = max(zmax, coords[i + 2]); - } - - // set our inherited Box to this bounding box - setExtent(xmin, ymin, zmin, xmax, ymax, zmax); - // copy a list of neighboring cell/site ids cell.neighbors(_neighbors); } // clears the information taken from a Tetra cell so it can be reinitialized - void clear() - { - _volume = 0.; - _neighbors.clear(); - } - - // initializes the receiver with the volume calculated from imported information - void init(double volume) { _volume = volume; } + void clear() { _neighbors.clear(); } // returns the cell's site position Vec position() const { return _r; } @@ -160,44 +65,22 @@ class TetraMeshSnapshot::Cell : public Box // enclosing box // returns the squared distance from the cell's site to the specified point double squaredDistanceTo(Vec r) const { return (r - _r).norm2(); } - // returns the central position in the cell - Vec centroid() const { return _c; } - - // returns the volume of the cell; overriding volume() function of Box bas class - double volume() const { return _volume; } - // returns a list of neighboring cell/site ids const vector& neighbors() { return _neighbors; } // returns the cell/site user properties, if any const Array& properties() { return _properties; } - - // writes the Tetra cell geometry to the serialized data buffer, preceded by the specified cell index, - // if the cell geometry has been calculated for this cell; otherwise does nothing - void writeGeometryIfPresent(SerializedWrite& wdata, int m) - { - if (!_neighbors.empty()) - { - wdata.write(m); - wdata.write(extent()); - wdata.write(_c); - wdata.write(_volume); - wdata.write(_neighbors); - } - } - - // reads the Tetra cell geometry from the serialized data buffer - void readGeometry(SerializedRead& rdata) - { - rdata.read(*this); // extent - rdata.read(_c); - rdata.read(_volume); - rdata.read(_neighbors); - } }; class TetraMeshSnapshot::Tetra : public Box { +public: + double _volume; + std::array _vertices; + std::array _neighbors = {-1, -1, -1, -1}; + std::array _vertex_indices; + Array _properties; // user-defined properties, if any + public: Tetra(const vector& _cells, int i, int j, int k, int l) { @@ -212,14 +95,14 @@ class TetraMeshSnapshot::Tetra : public Box double xmax = -DBL_MAX; double ymax = -DBL_MAX; double zmax = -DBL_MAX; - for (int i = 0; i < 4; i += 3) + for (const Vec& vertex : _vertices) { - xmin = min(xmin, _vertices[i].x()); - ymin = min(ymin, _vertices[i].y()); - zmin = min(zmin, _vertices[i].z()); - xmax = max(xmax, _vertices[i].x()); - ymax = max(ymax, _vertices[i].y()); - zmax = max(zmax, _vertices[i].z()); + xmin = min(xmin, vertex.x()); + ymin = min(ymin, vertex.y()); + zmin = min(zmin, vertex.z()); + xmax = max(xmax, vertex.x()); + ymax = max(ymax, vertex.y()); + zmax = max(zmax, vertex.z()); } setExtent(Box(xmin, ymin, zmin, xmax, ymax, zmax)); @@ -227,6 +110,8 @@ class TetraMeshSnapshot::Tetra : public Box _vertex_indices[1] = j; _vertex_indices[2] = k; _vertex_indices[3] = l; + + _volume = Box::volume(); //temp } // compute normal for face 3 (in clockwise direction of vertices 012) and dot with vertex 3 @@ -309,20 +194,29 @@ class TetraMeshSnapshot::Tetra : public Box && SameSide(v3, v0, v1, v2, bfr); } - std::array _vertices; - std::array _neighbors = {-1, -1, -1, -1}; - std::array _vertex_indices; + Position position() const + { + Position pos; + for (int i = 0; i < 4; i++) pos += _vertices[i]; + pos /= 4; + return pos; + } + + double volume() const { return _volume; } + + const Array& properties() { return _properties; } }; class TetraMeshSnapshot::Plucker { +private: Vec U, V; + Plucker(const Vec& U, const Vec& V) : U(U), V(V) {} + public: Plucker() {} - Plucker(const Vec& U, const Vec& V) : U(U), V(V) {} - static inline Plucker createFromDir(const Vec& pos, const Vec& dir) { return Plucker(dir, Vec::cross(dir, pos)); } static inline Plucker createFromVertices(const Vec& v1, const Vec& v2) @@ -333,8 +227,20 @@ class TetraMeshSnapshot::Plucker return p; } - // give plucker coord of all edges of a certain face - static inline void face(std::array& edges, const std::array& vertices, int face) + // permuted inner product + static inline double dot(const Plucker& a, const Plucker& b) { return Vec::dot(a.U, b.V) + Vec::dot(b.U, a.V); } +}; + +class TetraMeshSnapshot::Face +{ +public: + std::array _edges; + std::array _vertex_indices; + +public: + Face() {} + + Face(const Tetra* tetra, int face) { int v1 = (face + 1) % 4; int v2 = (face + 2) % 4; @@ -343,50 +249,34 @@ class TetraMeshSnapshot::Plucker // if face is even we should swap two edges if (face % 2 == 0) std::swap(v2, v3); - edges[0] = Plucker(vertices[v1], vertices[v2]); - edges[1] = Plucker(vertices[v2], vertices[v3]); - edges[2] = Plucker(vertices[v3], vertices[v1]); + _edges[0] = Plucker::createFromVertices(tetra->_vertices[v1], tetra->_vertices[v2]); + _edges[1] = Plucker::createFromVertices(tetra->_vertices[v2], tetra->_vertices[v3]); + _edges[2] = Plucker::createFromVertices(tetra->_vertices[v3], tetra->_vertices[v1]); + _vertex_indices[0] = v1; + _vertex_indices[1] = v2; + _vertex_indices[2] = v3; } - // permuted inner product - static inline double dot(const Plucker& a, const Plucker& b) { return Vec::dot(a.U, b.V) + Vec::dot(b.U, a.V); } + // leaving = true: returns true if the ray is leaving + // leaving = false: returns true if the ray is entering + inline bool intersects(std::array& w, const Plucker& ray, bool leaving = true) + { + for (int i = 0; i < 3; i++) + { + double prod = Plucker::dot(ray, _edges[i]); + if (leaving == prod < 0) return false; // weird if prod == 0 + w[i] = prod; + } + double W = w[0] + w[1] + w[2]; + w = {w[0] / W, w[1] / W, w[2] / W}; + return true; + } }; //////////////////////////////////////////////////////////////////// namespace { - // function to compare two points according to the specified axis (0,1,2) - bool lessthan(Vec p1, Vec p2, int axis) - { - switch (axis) - { - case 0: // split on x - if (p1.x() < p2.x()) return true; - if (p1.x() > p2.x()) return false; - if (p1.y() < p2.y()) return true; - if (p1.y() > p2.y()) return false; - if (p1.z() < p2.z()) return true; - return false; - case 1: // split on y - if (p1.y() < p2.y()) return true; - if (p1.y() > p2.y()) return false; - if (p1.z() < p2.z()) return true; - if (p1.z() > p2.z()) return false; - if (p1.x() < p2.x()) return true; - return false; - case 2: // split on z - if (p1.z() < p2.z()) return true; - if (p1.z() > p2.z()) return false; - if (p1.x() < p2.x()) return true; - if (p1.x() > p2.x()) return false; - if (p1.y() < p2.y()) return true; - return false; - default: // this should never happen - return false; - } - } - template bool invec(const vector& vec, const T& e) { return std::find(vec.begin(), vec.end(), e) != vec.end(); @@ -395,10 +285,6 @@ namespace double det4(double x0, double y0, double z0, double x1, double y1, double z1, double x2, double y2, double z2, double x3, double y3, double z3) { - // return x0 * (y1 * (z2 - z3) - y2 * (z1 - z3) + y3 * (z1 - z2)) - // - y0 * (x1 * (z2 - z3) - x2 * (z1 - z3) + x3 * (z1 - z2)) - // + z0 * (x1 * (y2 - y3) - x2 * (y1 - y3) + x3 * (y1 - y2)); - return x0 * y1 * z2 - x0 * y1 * z3 - x0 * y2 * z1 + x0 * y2 * z3 + x0 * y3 * z1 - x0 * y3 * z2 - x1 * y0 * z2 + x1 * y0 * z3 + x1 * y2 * z0 - x1 * y2 * z3 - x1 * y3 * z0 + x1 * y3 * z2 + x2 * y0 * z1 - x2 * y0 * z3 - x2 * y1 * z0 + x2 * y1 * z3 + x2 * y3 * z0 - x2 * y3 * z1 - x3 * y0 * z1 + x3 * y0 * z2 + x3 * y1 * z0 @@ -408,122 +294,6 @@ namespace //////////////////////////////////////////////////////////////////// -// class to hold a node in the binary search tree (see en.wikipedia.org/wiki/Kd-tree) -class TetraMeshSnapshot::Node -{ -private: - int _m; // index in _cells to the site defining the split at this node - int _axis; // split axis for this node (0,1,2) - Node* _up; // ptr to the parent node - Node* _left; // ptr to the left child node - Node* _right; // ptr to the right child node - - // returns the square of its argument - static double sqr(double x) { return x * x; } - -public: - // constructor stores the specified site index and child pointers (which may be null) - Node(int m, int depth, Node* left, Node* right) : _m(m), _axis(depth % 3), _up(0), _left(left), _right(right) - { - if (_left) _left->setParent(this); - if (_right) _right->setParent(this); - } - - // destructor destroys the children - ~Node() - { - delete _left; - delete _right; - } - - // sets parent pointer (called from parent's constructor) - void setParent(Node* up) { _up = up; } - - // returns the corresponding data member - int m() const { return _m; } - Node* up() const { return _up; } - Node* left() const { return _left; } - Node* right() const { return _right; } - - // returns the apropriate child for the specified query point - Node* child(Vec bfr, const vector& cells) const - { - return lessthan(bfr, cells[_m]->position(), _axis) ? _left : _right; - } - - // returns the other child than the one that would be apropriate for the specified query point - Node* otherChild(Vec bfr, const vector& cells) const - { - return lessthan(bfr, cells[_m]->position(), _axis) ? _right : _left; - } - - // returns the squared distance from the query point to the split plane - double squaredDistanceToSplitPlane(Vec bfr, const vector& cells) const - { - switch (_axis) - { - case 0: // split on x - return sqr(cells[_m]->position().x() - bfr.x()); - case 1: // split on y - return sqr(cells[_m]->position().y() - bfr.y()); - case 2: // split on z - return sqr(cells[_m]->position().z() - bfr.z()); - default: // this should never happen - return 0; - } - } - - // returns the node in this subtree that represents the site nearest to the query point - Node* nearest(Vec bfr, const vector& cells) - { - // recursively descend the tree until a leaf node is reached, going left or right depending on - // whether the specified point is less than or greater than the current node in the split dimension - Node* current = this; - while (Node* child = current->child(bfr, cells)) current = child; - - // unwind the recursion, looking for the nearest node while climbing up - Node* best = current; - double bestSD = cells[best->m()]->squaredDistanceTo(bfr); - while (true) - { - // if the current node is closer than the current best, then it becomes the current best - double currentSD = cells[current->m()]->squaredDistanceTo(bfr); - if (currentSD < bestSD) - { - best = current; - bestSD = currentSD; - } - - // if there could be points on the other side of the splitting plane for the current node - // that are closer to the search point than the current best, then ... - double splitSD = current->squaredDistanceToSplitPlane(bfr, cells); - if (splitSD < bestSD) - { - // move down the other branch of the tree from the current node looking for closer points, - // following the same recursive process as the entire search - Node* other = current->otherChild(bfr, cells); - if (other) - { - Node* otherBest = other->nearest(bfr, cells); - double otherBestSD = cells[otherBest->m()]->squaredDistanceTo(bfr); - if (otherBestSD < bestSD) - { - best = otherBest; - bestSD = otherBestSD; - } - } - } - - // move up to the parent until we meet the top node - if (current == this) break; - current = current->up(); - } - return best; - } -}; - -//////////////////////////////////////////////////////////////////// - TetraMeshSnapshot::TetraMeshSnapshot() {} //////////////////////////////////////////////////////////////////// @@ -537,37 +307,6 @@ TetraMeshSnapshot::~TetraMeshSnapshot() //////////////////////////////////////////////////////////////////// -void TetraMeshSnapshot::readAndClose() -{ - // read the site info into memory - Array prop; - while (infile()->readRow(prop)) _cells.push_back(new Cell(prop)); - - // close the file - Snapshot::readAndClose(); - - // if we are allowed to build a Tetra mesh - if (!_foregoTetraMesh) - { - // calculate the Tetra cells - buildMesh(false); - - // if a mass density policy has been set, calculate masses and densities and build the search data structure - if (hasMassDensityPolicy()) calculateDensityAndMass(); - if (hasMassDensityPolicy() || needGetEntities()) buildSearchPerBlock(); - } - - // if we forego building a Tetra mesh, there is a density policy by definition - else - { - calculateVolume(); - calculateDensityAndMass(); - buildSearchSingle(); - } -} - -//////////////////////////////////////////////////////////////////// - void TetraMeshSnapshot::setExtent(const Box& extent) { _extent = extent; @@ -576,13 +315,6 @@ void TetraMeshSnapshot::setExtent(const Box& extent) //////////////////////////////////////////////////////////////////// -void TetraMeshSnapshot::foregoTetraMesh() -{ - _foregoTetraMesh = true; -} - -//////////////////////////////////////////////////////////////////// - TetraMeshSnapshot::TetraMeshSnapshot(const SimulationItem* item, const Box& extent, string filename, bool relax) { // read the input file @@ -598,7 +330,7 @@ TetraMeshSnapshot::TetraMeshSnapshot(const SimulationItem* item, const Box& exte setContext(item); setExtent(extent); buildMesh(relax); - buildSearchPerBlock(); + // buildSearchPerBlock(); } //////////////////////////////////////////////////////////////////// @@ -614,7 +346,7 @@ TetraMeshSnapshot::TetraMeshSnapshot(const SimulationItem* item, const Box& exte setContext(item); setExtent(extent); buildMesh(relax); - buildSearchPerBlock(); + // buildSearchPerBlock(); } //////////////////////////////////////////////////////////////////// @@ -631,7 +363,7 @@ TetraMeshSnapshot::TetraMeshSnapshot(const SimulationItem* item, const Box& exte setContext(item); setExtent(extent); buildMesh(relax); - buildSearchPerBlock(); + // buildSearchPerBlock(); } //////////////////////////////////////////////////////////////////// @@ -669,29 +401,26 @@ void TetraMeshSnapshot::buildMesh(bool relax) { /////////////////////////////////////////////////////////////////////////// setExtent(Box(-1, -1, -1, 1, 1, 1)); - vector sites; - // for (size_t i = 0; sites.size() < 15; i++) - // { - // Position r = random()->position(_extent); - // sites.push_back(r); - // } + for (size_t i = 0; i < 7; i++) + { + sites.push_back(random()->position(_extent)); + } - // sites.push_back(Vec( 0.2, 0.2, 0)); - // sites.push_back(Vec( 0.2, -0.2, 0)); - sites.push_back(Vec(0.4, 0.4, 0)); - sites.push_back(Vec(0.4, -0.4, 0)); + // sites.push_back(Vec(0.2, 0.2, 0)); + // sites.push_back(Vec(0.2, -0.2, 0)); + // sites.push_back(Vec(0.4, 0.4, 0)); + // sites.push_back(Vec(0.4, -0.4, 0)); - sites.push_back(Vec(-0.5, 0, 0)); - sites.push_back(Vec(0, 0, 0.5)); - sites.push_back(Vec(0, 0, -0.5)); + // sites.push_back(Vec(-0.5, 0, 0)); + // sites.push_back(Vec(0, 0, 0.5)); + // sites.push_back(Vec(0, 0, -0.5)); - // prepare the data int n = sites.size(); _cells.resize(n); for (int m = 0; m != n; ++m) _cells[m] = new Cell(sites[m]); - const double A = 100.; + const double A = extent().rmax().norm() * 1000; /////////////////////////////////////////////////////////////////////////// @@ -759,17 +488,18 @@ void TetraMeshSnapshot::buildMesh(bool relax) // (initialized to zero so we can communicate the result between parallel processes using sumAll) Table<2> offsets(numCells, 3); - // add the retained original sites to a temporary Tetra container, using the cell index m as ID - voro::container vcon(-A, A, -A, A, -A, A, _nb, _nb, _nb, false, false, false, 16); + // add the retained original sites to a temporary Voronoi container, using the cell index m as ID + voro::container vcon(_extent.xmin(), _extent.xmax(), _extent.ymin(), _extent.ymax(), _extent.zmin(), + _extent.zmax(), _nb, _nb, _nb, false, false, false, 16); for (int m = 0; m != numCells; ++m) { Vec r = _cells[m]->position(); vcon.put(m, r.x(), r.y(), r.z()); } - // compute the cell in the Tetra tesselation corresponding to each site + // compute the cell in the Voronoi tesselation corresponding to each site // and store the cell's centroid (relative to the site position) as the relaxation offset - log()->info("Relaxing Tetra tessellation with " + std::to_string(numCells) + " cells"); + log()->info("Relaxing Voronoi tessellation with " + std::to_string(numCells) + " cells"); log()->infoSetElapsed(numCells); auto parallel = log()->find()->parallelDistributed(); parallel->call(numCells, [this, &vcon, &offsets](size_t firstIndex, size_t numIndices) { @@ -793,10 +523,10 @@ void TetraMeshSnapshot::buildMesh(bool relax) // log message if the minimum time has elapsed numDone = (numDone + 1) % logProgressChunkSize; - if (numDone == 0) log()->infoIfElapsed("Computed Tetra cells: ", logProgressChunkSize); + if (numDone == 0) log()->infoIfElapsed("Computed Voronoi cells: ", logProgressChunkSize); } } while (vloop.inc()); - if (numDone > 0) log()->infoIfElapsed("Computed Tetra cells: ", numDone); + if (numDone > 0) log()->infoIfElapsed("Computed Voronoi cells: ", numDone); }); // communicate the calculated offsets between parallel processes, if needed, and apply them to the cells @@ -806,222 +536,146 @@ void TetraMeshSnapshot::buildMesh(bool relax) // ========= FINAL GRID ========= - // repeat grid construction until none of the cells have zero volume - int numIterations = 0; - while (true) + // add the final sites to a temporary Tetra container, using the cell index m as ID + voro::container vcon(-A, A, -A, A, -A, A, _nb, _nb, _nb, false, false, false, 16); + for (int m = 0; m != numCells; ++m) { - // add the final sites to a temporary Tetra container, using the cell index m as ID - voro::container vcon(-A, A, -A, A, -A, A, _nb, _nb, _nb, false, false, false, 16); - for (int m = 0; m != numCells; ++m) + Vec r = _cells[m]->position(); + vcon.put(m, r.x(), r.y(), r.z()); + } + + // for each site: + // - compute the corresponding cell in the Tetra tesselation + // - extract and copy the relevant information to the cell object with the corresponding index in our vector + log()->info("Constructing Tetra tessellation with " + std::to_string(numCells) + " cells"); + log()->infoSetElapsed(numCells); + // allocate a separate cell calculator for each thread to avoid conflicts + voro::voro_compute vcompute(vcon, _nb, _nb, _nb); + // allocate space for the resulting cell info + voro::voronoicell_neighbor vcell; + std::vector vcells; + + // loop over all cells and work on the ones that have a particle index in our dedicated range + // (we cannot access cells in the container based on cell index m without building an extra data structure) + int numDone = 0; + voro::c_loop_all vloop(vcon); + if (vloop.start()) do { - Vec r = _cells[m]->position(); - vcon.put(m, r.x(), r.y(), r.z()); - } + size_t m = vloop.pid(); + // compute the cell and copy all relevant information to the cell object that will stay around + bool ok = vcompute.compute_cell(vcell, vloop.ijk, vloop.q, vloop.i, vloop.j, vloop.k); + if (ok) _cells[m]->init(vcell); + + // log message if the minimum time has elapsed + numDone = (numDone + 1) % logProgressChunkSize; + if (numDone == 0) log()->infoIfElapsed("Computed Tetra cells: ", logProgressChunkSize); + } while (vloop.inc()); + if (numDone > 0) log()->infoIfElapsed("Computed Tetra cells: ", numDone); + + //////////////////////////////////////////////////////////////////////////////////////////// + for (int i = 0; i < numCells; i++) + { + Cell* c1 = _cells[i]; - // for each site: - // - compute the corresponding cell in the Tetra tesselation - // - extract and copy the relevant information to the cell object with the corresponding index in our vector - log()->info("Constructing Tetra tessellation with " + std::to_string(numCells) + " cells"); - log()->infoSetElapsed(numCells); - // allocate a separate cell calculator for each thread to avoid conflicts - voro::voro_compute vcompute(vcon, _nb, _nb, _nb); - // allocate space for the resulting cell info - voro::voronoicell_neighbor vcell; - std::vector vcells; - - // loop over all cells and work on the ones that have a particle index in our dedicated range - // (we cannot access cells in the container based on cell index m without building an extra data structure) - int numDone = 0; - voro::c_loop_all vloop(vcon); - if (vloop.start()) do - { - size_t m = vloop.pid(); - // compute the cell and copy all relevant information to the cell object that will stay around - bool ok = vcompute.compute_cell(vcell, vloop.ijk, vloop.q, vloop.i, vloop.j, vloop.k); - if (ok) _cells[m]->init(vcell); - - // log message if the minimum time has elapsed - numDone = (numDone + 1) % logProgressChunkSize; - if (numDone == 0) log()->infoIfElapsed("Computed Tetra cells: ", logProgressChunkSize); - } while (vloop.inc()); - if (numDone > 0) log()->infoIfElapsed("Computed Tetra cells: ", numDone); - - //////////////////////////////////////////////////////////////////////////////////////////// - for (int i = 0; i < numCells; i++) + for (int j : c1->neighbors()) { - Cell* c1 = _cells[i]; + if (j < 0) continue; + + Cell* c2 = _cells[j]; - for (int j : c1->neighbors()) + for (int k : c2->neighbors()) { - if (j < 0) continue; + if (k < 0 || k == i) continue; - Cell* c2 = _cells[j]; + // mutual neighbours + if (!invec(c1->neighbors(), k)) continue; - for (int k : c2->neighbors()) + for (int l : _cells[k]->neighbors()) { - if (k < 0 || k == i) continue; - - // mutual neighbours - if (!invec(c1->neighbors(), k)) continue; - - for (int l : _cells[k]->neighbors()) + if (l < 0 || l == j || l == i) continue; + + // mutual neighbours of both c1 and c2 + if (!invec(c1->neighbors(), l) || !invec(c2->neighbors(), l)) continue; + + // no duplicates in different order // probably use set + Tetra* tetra = new Tetra(_cells, i, j, k, l); + if (inTetrahedra(tetra)) continue; + + // check if tetrahedron is Delaunay + Vec v0 = _cells[i]->_r; + Vec v1 = _cells[j]->_r; + Vec v2 = _cells[k]->_r; + Vec v3 = _cells[l]->_r; + double x0 = v0.x(), y0 = v0.y(), z0 = v0.z(); + double x1 = v1.x(), y1 = v1.y(), z1 = v1.z(); + double x2 = v2.x(), y2 = v2.y(), z2 = v2.z(); + double x3 = v3.x(), y3 = v3.y(), z3 = v3.z(); + + double r0 = v0.norm2(); + double r1 = v1.norm2(); + double r2 = v2.norm2(); + double r3 = v3.norm2(); + + double a = det4(x0, y0, z0, x1, y1, z1, x2, y2, z2, x3, y3, z3); + double Dx = det4(r0, y0, z0, r1, y1, z1, r2, y2, z2, r3, y3, z3); + double Dy = -det4(r0, x0, z0, r1, x1, z1, r2, x2, z2, r3, x3, z3); + double Dz = det4(r0, x0, y0, r1, x1, y1, r2, x2, y2, r3, x3, y3); + + Vec center(Dx / (2 * a), Dy / (2 * a), Dz / (2 * a)); + double R = (center - v0).norm2(); + + bool delaunay = true; + for (int v : tetra->_vertex_indices) { - if (l < 0 || l == j || l == i) continue; - - // mutual neighbours of both c1 and c2 - if (!invec(c1->neighbors(), l) || !invec(c2->neighbors(), l)) continue; - - // no duplicates in different order // probably use set - Tetra* tetra = new Tetra(_cells, i, j, k, l); - if (inTetrahedra(tetra)) continue; - - // check if tetrahedron is Delaunay - Vec v0 = _cells[i]->_r; - Vec v1 = _cells[j]->_r; - Vec v2 = _cells[k]->_r; - Vec v3 = _cells[l]->_r; - double x0 = v0.x(), y0 = v0.y(), z0 = v0.z(); - double x1 = v1.x(), y1 = v1.y(), z1 = v1.z(); - double x2 = v2.x(), y2 = v2.y(), z2 = v2.z(); - double x3 = v3.x(), y3 = v3.y(), z3 = v3.z(); - - double r0 = x0 * x0 + y0 * y0 + z0 * z0; - double r1 = x1 * x1 + y1 * y1 + z1 * z1; - double r2 = x2 * x2 + y2 * y2 + z2 * z2; - double r3 = x3 * x3 + y3 * y3 + z3 * z3; - - double a = det4(x0, y0, z0, x1, y1, z1, x2, y2, z2, x3, y3, z3); - double Dx = det4(r0, y0, z0, r1, y1, z1, r2, y2, z2, r3, y3, z3); - double Dy = -det4(r0, x0, z0, r1, x1, z1, r2, x2, z2, r3, x3, z3); - double Dz = det4(r0, x0, y0, r1, x1, y1, r2, x2, y2, r3, x3, y3); - - Vec center(Dx / (2 * a), Dy / (2 * a), Dz / (2 * a)); - double R = (center - v0).norm2(); - - bool delaunay = true; - for (int v : tetra->_vertex_indices) + Cell* c = _cells[v]; + for (int n : c->neighbors()) { - Cell* c = _cells[v]; - for (int n : c->neighbors()) + if (n < 0 || n == i || n == j || n == k || n == l) continue; + double r = (center - _cells[n]->_r).norm2(); + if (r < R) { - if (n < 0 || n == i || n == j || n == k || n == l) continue; - double r = (center - _cells[n]->_r).norm2(); - if (r < R) - { - delaunay = false; - break; - } + delaunay = false; + break; } - if (!delaunay) break; } - if (!delaunay) continue; - - // orient tetrahedron in the same consistent way - tetra->orient(); - - centers.push_back(center); - radii.push_back(sqrt(R)); - _tetrahedra.push_back(tetra); + if (!delaunay) break; } - } - } - } - - // find neighbors brute force - for (size_t i = 0; i < _tetrahedra.size(); i++) - { - Tetra* tetra = _tetrahedra[i]; - - for (size_t j = 0; j < _tetrahedra.size(); j++) - { - if (i == j) continue; + if (!delaunay) continue; - const Tetra* other = _tetrahedra[j]; + // orient tetrahedron in the same consistent way + tetra->orient(); - int shared = tetra->shareFace(other); - if (shared != -1) tetra->_neighbors[shared] = j; - } - } - - //////////////////////////////////////////////////////////////////////////////////////////// - - // discover invalid cells with zero volume and/or with neighbors that are not mutual - log()->info("Verifying Tetra tessellation"); - std::set invalid; - for (int m = 0; m < numCells; m++) - { - if (!_cells[m]->volume()) invalid.insert(m); - for (int m1 : _cells[m]->neighbors()) - { - if (m1 >= 0) - { - const vector& neighbors1 = _cells[m1]->neighbors(); - if (std::find(neighbors1.begin(), neighbors1.end(), m) == neighbors1.end()) - { - invalid.insert(m); - invalid.insert(m1); - } + centers.push_back(center); + radii.push_back(sqrt(R)); + _tetrahedra.push_back(tetra); } } } + } - // break from loop if no invalid cells were found - if (invalid.empty()) break; - - // give up after a given number of iterations - if (++numIterations == maxConstructionIterations) - { - throw FATALERROR("Still " + std::to_string(invalid.size()) + " invalid Tetra cells after " - + std::to_string(maxConstructionIterations) - + " iterations of constructing the tessellation"); - } + // find neighbors brute force + for (size_t i = 0; i < _tetrahedra.size(); i++) + { + Tetra* tetra = _tetrahedra[i]; - // remove invalid cells and prepare to repeat construction - log()->warning("Removing sites for " + std::to_string(invalid.size()) - + " invalid Tetra cells and reconstructing the tessellation"); - for (auto it = invalid.begin(); it != invalid.end(); ++it) + for (size_t j = 0; j < _tetrahedra.size(); j++) { - int m = *it; - delete _cells[m]; - _cells[m] = 0; - } - numCells = eraseNullPointers(_cells); - for (int m = 0; m != numCells; ++m) _cells[m]->clear(); - } + if (i == j) continue; - // ========= STATISTICS ========= + const Tetra* other = _tetrahedra[j]; - // compile neighbor statistics - int minNeighbors = INT_MAX; - int maxNeighbors = 0; - int64_t totNeighbors = 0; - for (int m = 0; m < numCells; m++) - { - int ns = _cells[m]->neighbors().size(); - totNeighbors += ns; - minNeighbors = min(minNeighbors, ns); - maxNeighbors = max(maxNeighbors, ns); + int shared = tetra->shareFace(other); + if (shared != -1) tetra->_neighbors[shared] = j; + } } - double avgNeighbors = double(totNeighbors) / numCells; - - // log neighbor statistics - log()->info("Done computing Tetra tessellation with " + std::to_string(numCells) + " cells"); - log()->info(" Average number of neighbors per cell: " + StringUtils::toString(avgNeighbors, 'f', 1)); - log()->info(" Minimum number of neighbors per cell: " + std::to_string(minNeighbors)); - log()->info(" Maximum number of neighbors per cell: " + std::to_string(maxNeighbors)); + //////////////////////////////////////////////////////////////////////////////////////////// } //////////////////////////////////////////////////////////////////// void TetraMeshSnapshot::calculateVolume() { - int numCells = _cells.size(); - for (int m = 0; m != numCells; ++m) - { - const Array& prop = _cells[m]->properties(); - double volume = prop[densityIndex()] > 0. ? prop[massIndex()] / prop[densityIndex()] : 0.; - _cells[m]->init(volume); - } + //WIP } //////////////////////////////////////////////////////////////////// @@ -1029,7 +683,7 @@ void TetraMeshSnapshot::calculateVolume() void TetraMeshSnapshot::calculateDensityAndMass() { // allocate vectors for mass and density - int numCells = _cells.size(); + int numCells = _tetrahedra.size(); _rhov.resize(numCells); Array Mv(numCells); @@ -1045,7 +699,7 @@ void TetraMeshSnapshot::calculateDensityAndMass() int numIgnored = 0; for (int m = 0; m != numCells; ++m) { - const Array& prop = _cells[m]->properties(); + const Array& prop = _tetrahedra[m]->properties(); // original mass is zero if temperature is above cutoff or if imported mass/density is not positive double originalDensity = 0.; @@ -1056,7 +710,7 @@ void TetraMeshSnapshot::calculateDensityAndMass() } else { - double volume = _cells[m]->volume(); + double volume = _tetrahedra[m]->volume(); originalDensity = max(0., densityIndex() >= 0 ? prop[densityIndex()] : prop[massIndex()] / volume); originalMass = max(0., massIndex() >= 0 ? prop[massIndex()] : prop[densityIndex()] * volume); } @@ -1085,119 +739,6 @@ void TetraMeshSnapshot::calculateDensityAndMass() //////////////////////////////////////////////////////////////////// -TetraMeshSnapshot::Node* TetraMeshSnapshot::buildTree(vector::iterator first, vector::iterator last, - int depth) const -{ - auto length = last - first; - if (length > 0) - { - auto median = length >> 1; - std::nth_element(first, first + median, last, [this, depth](int m1, int m2) { - return m1 != m2 && lessthan(_cells[m1]->position(), _cells[m2]->position(), depth % 3); - }); - return new TetraMeshSnapshot::Node(*(first + median), depth, buildTree(first, first + median, depth + 1), - buildTree(first + median + 1, last, depth + 1)); - } - return nullptr; -} - -//////////////////////////////////////////////////////////////////// - -void TetraMeshSnapshot::buildSearchPerBlock() -{ - // abort if there are no cells - int numCells = _cells.size(); - if (!numCells) return; - - log()->info("Building data structures to accelerate searching the Tetra tesselation"); - - // ------------- block lists ------------- - - // initialize a vector of nb x nb x nb lists, each containing the cells overlapping a certain block in the domain - _blocklists.resize(_nb3); - - // add the cell object to the lists for all blocks it may overlap - int i1, j1, k1, i2, j2, k2; - for (int m = 0; m != numCells; ++m) - { - _extent.cellIndices(i1, j1, k1, _cells[m]->rmin() - Vec(_eps, _eps, _eps), _nb, _nb, _nb); - _extent.cellIndices(i2, j2, k2, _cells[m]->rmax() + Vec(_eps, _eps, _eps), _nb, _nb, _nb); - for (int i = i1; i <= i2; i++) - for (int j = j1; j <= j2; j++) - for (int k = k1; k <= k2; k++) _blocklists[i * _nb2 + j * _nb + k].push_back(m); - } - - // compile block list statistics - int minRefsPerBlock = INT_MAX; - int maxRefsPerBlock = 0; - int64_t totalBlockRefs = 0; - for (int b = 0; b < _nb3; b++) - { - int refs = _blocklists[b].size(); - totalBlockRefs += refs; - minRefsPerBlock = min(minRefsPerBlock, refs); - maxRefsPerBlock = max(maxRefsPerBlock, refs); - } - double avgRefsPerBlock = double(totalBlockRefs) / _nb3; - - // log block list statistics - log()->info(" Number of blocks in search grid: " + std::to_string(_nb3) + " (" + std::to_string(_nb) + "^3)"); - log()->info(" Average number of cells per block: " + StringUtils::toString(avgRefsPerBlock, 'f', 1)); - log()->info(" Minimum number of cells per block: " + std::to_string(minRefsPerBlock)); - log()->info(" Maximum number of cells per block: " + std::to_string(maxRefsPerBlock)); - - // ------------- search trees ------------- - - // for each block that contains more than a predefined number of cells, - // construct a search tree on the site locations of the cells - _blocktrees.resize(_nb3); - for (int b = 0; b < _nb3; b++) - { - vector& ids = _blocklists[b]; - if (ids.size() > 9) _blocktrees[b] = buildTree(ids.begin(), ids.end(), 0); - } - - // compile and log search tree statistics - int numTrees = 0; - for (int b = 0; b < _nb3; b++) - if (_blocktrees[b]) numTrees++; - log()->info(" Number of search trees: " + std::to_string(numTrees) + " (" - + StringUtils::toString(100. * numTrees / _nb3, 'f', 1) + "% of blocks)"); -} - -//////////////////////////////////////////////////////////////////// - -void TetraMeshSnapshot::buildSearchSingle() -{ - // log the number of sites - int numCells = _cells.size(); - log()->info(" Number of sites: " + std::to_string(numCells)); - - // abort if there are no cells - if (!numCells) return; - - // construct a single search tree on the site locations of all cells - log()->info("Building data structure to accelerate searching " + std::to_string(numCells) + " Tetra sites"); - _blocktrees.resize(1); - vector ids(numCells); - for (int m = 0; m != numCells; ++m) ids[m] = m; - _blocktrees[0] = buildTree(ids.begin(), ids.end(), 0); -} - -//////////////////////////////////////////////////////////////////// - -bool TetraMeshSnapshot::isPointClosestTo(Vec r, int m, const vector& ids) const -{ - double target = _cells[m]->squaredDistanceTo(r); - for (int id : ids) - { - if (id >= 0 && _cells[id]->squaredDistanceTo(r) < target) return false; - } - return true; -} - -//////////////////////////////////////////////////////////////////// - bool TetraMeshSnapshot::inTetrahedra(const Tetra* tetra) const { for (const Tetra* t : _tetrahedra) @@ -1234,10 +775,7 @@ void TetraMeshSnapshot::writeGridPlotFiles(const SimulationItem* probe) const outputFile << _cells[i]->neighbors().size() << " neighbors="; for (int n : _cells[i]->neighbors()) { - if (n >= 0) - { - outputFile << " " << n << ","; - } + if (n >= 0) outputFile << " " << n << ","; } outputFile << "\n"; } @@ -1245,7 +783,7 @@ void TetraMeshSnapshot::writeGridPlotFiles(const SimulationItem* probe) const outputFile << "tetrahedra=" << _tetrahedra.size() << "\n"; for (size_t i = 0; i < _tetrahedra.size(); i++) { - Tetra* tetra = _tetrahedra[i]; + const Tetra* tetra = _tetrahedra[i]; outputFile << "tetrahedron=" << i << "\nvertices="; for (size_t l = 0; l < 4; l++) { @@ -1256,17 +794,11 @@ void TetraMeshSnapshot::writeGridPlotFiles(const SimulationItem* probe) const for (size_t j = 0; j < 4; j++) { outputFile << " " << tetra->_neighbors[j] << ","; - - double x = std::round(tetra->_vertices[j].x()); - double y = std::round(tetra->_vertices[j].y()); - double z = std::round(tetra->_vertices[j].z()); - tetra->_vertices[j].set(x, y, z); } outputFile << "\n"; } for (size_t i = 0; i < _tetrahedra.size(); i++) { - Tetra* tetra = _tetrahedra[i]; outputFile << "circumsphere=" << i << "\n"; outputFile << centers[i].x() << "," << centers[i].y() << "," << centers[i].z() << "\n"; outputFile << radii[i] << "\n"; @@ -1309,35 +841,28 @@ Box TetraMeshSnapshot::extent() const int TetraMeshSnapshot::numEntities() const { - return _cells.size(); + return _tetrahedra.size(); } //////////////////////////////////////////////////////////////////// Position TetraMeshSnapshot::position(int m) const { - return Position(_cells[m]->position()); -} - -//////////////////////////////////////////////////////////////////// - -Position TetraMeshSnapshot::centroidPosition(int m) const -{ - return Position(_cells[m]->centroid()); + return _tetrahedra[m]->position(); } //////////////////////////////////////////////////////////////////// double TetraMeshSnapshot::volume(int m) const { - return _cells[m]->volume(); + return _tetrahedra[m]->volume(); } //////////////////////////////////////////////////////////////////// Box TetraMeshSnapshot::extent(int m) const { - return _cells[m]->extent(); + return _tetrahedra[m]->extent(); } //////////////////////////////////////////////////////////////////// @@ -1367,16 +892,17 @@ double TetraMeshSnapshot::mass() const Position TetraMeshSnapshot::generatePosition(int m) const { // get loop-invariant information about the cell - const Box& box = _cells[m]->extent(); - const vector& neighbors = _cells[m]->neighbors(); + const Box& box = _tetrahedra[m]->extent(); // generate random points in the enclosing box until one happens to be inside the cell for (int i = 0; i < 10000; i++) { Position r = random()->position(box); - if (isPointClosestTo(r, m, neighbors)) return r; + if (_tetrahedra[m]->inside(r)) return r; } - throw FATALERROR("Can't find random position in cell"); + log()->error("can't find random poisition in tetrahedron"); + return Position(); + // throw FATALERROR("Can't find random position in cell"); } //////////////////////////////////////////////////////////////////// @@ -1384,7 +910,7 @@ Position TetraMeshSnapshot::generatePosition(int m) const Position TetraMeshSnapshot::generatePosition() const { // if there are no sites, return the origin - if (_cells.empty()) return Position(); + if (_tetrahedra.empty()) return Position(); // select a site according to its mass contribution int m = NR::locateClip(_cumrhov, random()->uniform()); @@ -1433,26 +959,55 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator { const TetraMeshSnapshot* _grid{nullptr}; int _mr{-1}; - // add entering face index for next + // true if the path is was ever inside the convex hull + bool wasInside = false; public: MySegmentGenerator(const TetraMeshSnapshot* grid) : _grid(grid) {} + std::ofstream out; + + void startwriting() + { + if (!out.is_open()) out.open("photon.txt"); + } + + void stopwriting() { out.close(); } + + void write(const Vec& exit, double ds) + { + out << "photon=" << _mr << "\n"; + out << "ds=" << ds << "\n"; + out << "r=" << r().x() << "," << r().y() << "," << r().z() << "\n"; + out << "exit=" << exit.x() << "," << exit.y() << "," << exit.z() << "\n"; + out << "k=" << k().x() << "," << k().y() << "," << k().z() << "\n"; + // out.flush(); // for debug + } + bool next() override { switch (state()) { case State::Unknown: { + startwriting(); + // try moving the photon packet inside the grid; if this is impossible, return an empty path if (!moveInside(_grid->extent(), _grid->_eps)) return false; // get the index of the cell containing the current position _mr = _grid->cellIndex(r()); + if (_mr == -1) + setState(State::Outside); + else + wasInside = true; + // if the photon packet started outside the grid, return the corresponding nonzero-length segment; // otherwise fall through to determine the first actual segment if (ds() > 0.) return true; + + write(Vec(), 0); } // intentionally falls through @@ -1461,88 +1016,43 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator // loop in case no exit point was found (which should happen only rarely) while (true) { - // get the site position for this cell - Vec pr = _grid->_cells[_mr]->position(); - // initialize the smallest nonnegative intersection distance and corresponding index double sq = DBL_MAX; // very large, but not infinity (so that infinite si values are discarded) const int NO_INDEX = -99; // meaningless cell index int mq = NO_INDEX; // plucker coords - const Plucker ray = Plucker(r(), k()); - - for (int face = 0; face < 4; face++) + const Plucker ray = Plucker::createFromDir(r(), k()); + const Tetra* tetra = _grid->_tetrahedra[_mr]; + + int leavingFace = -1; + int leaving_faces = 0; + std::array w; + Face face; + for (int f = 0; f < 4; f++) { - std::array edges; - Plucker::face(edges, _grid->_tetrahedra[_mr]->_vertices, face); + face = Face(tetra, f); - int leaveFace = -1; - for (int i = 0; i < 3; i++) + if (face.intersects(w, ray, true)) { - double prod = Plucker::dot(ray, edges[i]); - // how to ensure edges are aligned the same way? - // prod < 0 or prod > 0 we don't know + leaving_faces++; + leavingFace = f; + bool allZero = std::all_of(w.begin(), w.end(), [](double val) { return val == 0.0; }); + if (allZero) _grid->log()->error("TetraMeshSnapshot: all plucker prods == 0"); + // break; } } + if (leaving_faces != 1) _grid->log()->error("TetraMeshSnapshot: too many leaving faces!"); - // loop over the list of neighbor indices - const vector& mv = _grid->_cells[_mr]->neighbors(); - int n = mv.size(); - for (int i = 0; i < n; i++) + Vec exit; + for (int i = 0; i < 3; i++) { - int mi = mv[i]; - - // declare the intersection distance for this neighbor (init to a value that will be rejected) - double si = 0; - - // --- intersection with neighboring cell - if (mi >= 0) - { - // get the site position for this neighbor - Vec pi = _grid->_cells[mi]->position(); - - // calculate the (unnormalized) normal on the bisecting plane - Vec n = pi - pr; - - // calculate the denominator of the intersection quotient - double ndotk = Vec::dot(n, k()); - - // if the denominator is negative the intersection distance is negative, - // so don't calculate it - if (ndotk > 0) - { - // calculate a point on the bisecting plane - Vec p = 0.5 * (pi + pr); - - // calculate the intersection distance - si = Vec::dot(n, p - r()) / ndotk; - } - } - - // --- intersection with domain wall - else - { - switch (mi) - { - case -1: si = (_grid->extent().xmin() - rx()) / kx(); break; - case -2: si = (_grid->extent().xmax() - rx()) / kx(); break; - case -3: si = (_grid->extent().ymin() - ry()) / ky(); break; - case -4: si = (_grid->extent().ymax() - ry()) / ky(); break; - case -5: si = (_grid->extent().zmin() - rz()) / kz(); break; - case -6: si = (_grid->extent().zmax() - rz()) / kz(); break; - default: throw FATALERROR("Invalid neighbor ID"); - } - } - - // remember the smallest nonnegative intersection point - if (si > 0 && si < sq) - { - sq = si; - mq = mi; - } + exit += tetra->_vertices[face._vertex_indices[i]] * w[i]; } + sq = (exit - r()).norm(); + mq = tetra->_neighbors[leavingFace]; + // if no exit point was found, advance the current point by a small distance, // recalculate the cell index, and return to the start of the loop if (mq == NO_INDEX) @@ -1562,6 +1072,7 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator { propagater(sq + _grid->_eps); setSegment(_mr, sq); + write(exit, sq); _mr = mq; // if we're outside the domain, terminate the path after returning this path segment @@ -1573,6 +1084,76 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator case State::Outside: { + // outside the convex hull and inside extent + if (wasInside && _grid->extent().contains(r())) + { + double t_x, t_y, t_z; + if (kx() < 0) + t_x = (_grid->extent().xmin() - rx()) / kx(); + else + t_x = (_grid->extent().xmax() - rx()) / kx(); + if (ky() < 0) + t_y = (_grid->extent().ymin() - ry()) / ky(); + else + t_y = (_grid->extent().ymax() - ry()) / ky(); + if (kz() < 0) + t_z = (_grid->extent().zmin() - rz()) / kz(); + else + t_z = (_grid->extent().zmax() - rz()) / kz(); + + double ds = min(t_x, min(t_y, t_z)); + propagater(ds + _grid->_eps); + write(r(), ds); + stopwriting(); + return false; + } + + // figure out if the path intersects the convex hull + if (!wasInside) + { + const Plucker ray = Plucker::createFromDir(r(), k()); + int enteringfaces = 0; + int entering = -1; + + std::array w; + std::array vertices; + for (int i = 0; i < _grid->_tetrahedra.size(); i++) + { + const Tetra* tetra = _grid->_tetrahedra[i]; + for (int f = 0; f < 4; f++) + { + int n = tetra->_neighbors[f]; + // edge face + if (n == -1) + { + Face face = Face(tetra, f); + if (face.intersects(w, ray, false)) + { + _mr = i; + enteringfaces++; // debug variable + if (enteringfaces == 0) + { + Vec exit; + for (int i = 0; i < 3; i++) + { + exit += tetra->_vertices[face._vertex_indices[i]] * w[i]; + } + + double sq = (exit - r()).norm(); + propagater(sq + _grid->_eps); + write(exit, sq); + } + else + { + _grid->log()->error("too many entering faces for outside ray"); + } + // break; + } + } + } + } + stopwriting(); + } } } return false; diff --git a/SKIRT/core/TetraMeshSnapshot.hpp b/SKIRT/core/TetraMeshSnapshot.hpp index 81d04655..4fb6806a 100644 --- a/SKIRT/core/TetraMeshSnapshot.hpp +++ b/SKIRT/core/TetraMeshSnapshot.hpp @@ -117,7 +117,7 @@ class TetraMeshSnapshot : public Snapshot During its operation, the function logs some statistical information about the imported snapshot and the resulting data structures. */ - void readAndClose() override; + // void readAndClose() override; //========== Configuration ========== @@ -133,7 +133,7 @@ class TetraMeshSnapshot : public Snapshot volume-integrated mass/number column, and (2) the snapshot will not be required to generate random positions or trace paths. Violating these conditions will result in undefined behavior. */ - void foregoTetraMesh(); + // void foregoTetraMesh(); //========== Specialty constructors ========== @@ -192,6 +192,8 @@ class TetraMeshSnapshot : public Snapshot class Plucker; + class Face; + // temp test std::vector centers; std::vector radii; @@ -231,7 +233,7 @@ class TetraMeshSnapshot : public Snapshot /** Private function to recursively build a binary search tree (see en.wikipedia.org/wiki/Kd-tree) */ - Node* buildTree(vector::iterator first, vector::iterator last, int depth) const; + // Node* buildTree(vector::iterator first, vector::iterator last, int depth) const; /** This private function builds data structures that allow accelerating the operation of the cellIndex() function. It assumes that the Tetra mesh has already been built. @@ -248,17 +250,17 @@ class TetraMeshSnapshot : public Snapshot To further reduce the search time within blocks that overlap with a large number of cells, the function builds a binary search tree on the cell sites for those blocks (see for example en.wikipedia.org/wiki/Kd-tree). */ - void buildSearchPerBlock(); + // void buildSearchPerBlock(); /** This private function builds a data structure that allows accelerating the operation of the cellIndex() function without using the Tetra mesh. The domain is not partitioned in blocks. The function builds a single binary search tree on all cell sites (see for example en.wikipedia.org/wiki/Kd-tree). */ - void buildSearchSingle(); + // void buildSearchSingle(); /** This private function returns true if the given point is closer to the site with index m than to the sites with indices ids. */ - bool isPointClosestTo(Vec r, int m, const vector& ids) const; + // bool isPointClosestTo(Vec r, int m, const vector& ids) const; bool inTetrahedra(const Tetra* tetra) const; @@ -286,7 +288,7 @@ class TetraMeshSnapshot : public Snapshot /** This function returns the centroid of the Tetra cell with index \em m. If the index is out of range, the behavior is undefined. */ - Position centroidPosition(int m) const; + // Position centroidPosition(int m) const; /** This function returns the volume of the Tetra cell with index \em m. If the index is out of range, the behavior is undefined. */ diff --git a/SKIRT/core/TetraMeshSpatialGrid.cpp b/SKIRT/core/TetraMeshSpatialGrid.cpp index 97d4b59b..56426963 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.cpp +++ b/SKIRT/core/TetraMeshSpatialGrid.cpp @@ -176,7 +176,7 @@ int TetraMeshSpatialGrid::cellIndex(Position bfr) const Position TetraMeshSpatialGrid::centralPositionInCell(int m) const { - return _mesh->centroidPosition(m); + return _mesh->position(m); } ////////////////////////////////////////////////////////////////////// diff --git a/SKIRT/utils/PathSegmentGenerator.hpp b/SKIRT/utils/PathSegmentGenerator.hpp index f515ad21..b33659b4 100644 --- a/SKIRT/utils/PathSegmentGenerator.hpp +++ b/SKIRT/utils/PathSegmentGenerator.hpp @@ -8,6 +8,8 @@ #include "SpatialGridPath.hpp" +class TetraMeshSnapshot; + ////////////////////////////////////////////////////////////////////// /** PathSegmentGenerator is the abstract base class for classes that calculate and return the @@ -197,6 +199,8 @@ class PathSegmentGenerator // ------- Data members ------- + friend TetraMeshSnapshot; + private: State _state{State::Unknown}; double _rx{0.}; From 0def91f53195ba318107fe00c94e8367fc7ca2b1 Mon Sep 17 00:00:00 2001 From: arlauwer Date: Wed, 1 Nov 2023 18:35:34 +0100 Subject: [PATCH 08/51] working PathSegmentGenerator --- SKIRT/core/TetraMeshSnapshot.cpp | 353 +++++++++++++++---------------- 1 file changed, 176 insertions(+), 177 deletions(-) diff --git a/SKIRT/core/TetraMeshSnapshot.cpp b/SKIRT/core/TetraMeshSnapshot.cpp index b1b76978..361a72c6 100644 --- a/SKIRT/core/TetraMeshSnapshot.cpp +++ b/SKIRT/core/TetraMeshSnapshot.cpp @@ -137,13 +137,13 @@ class TetraMeshSnapshot::Tetra : public Box int equal_vert = 0; int opposite = 0 + 1 + 2 + 3; - for (size_t i = 0; i < 4; i++) + for (int i = 0; i < 4; i++) { - const int vi = _vertex_indices[i]; + int vi = _vertex_indices[i]; - for (size_t j = 0; j < 4; j++) + for (int j = 0; j < 4; j++) { - const int vj = other->_vertex_indices[i]; + int vj = other->_vertex_indices[j]; if (vi == vj) { @@ -219,16 +219,10 @@ class TetraMeshSnapshot::Plucker static inline Plucker createFromDir(const Vec& pos, const Vec& dir) { return Plucker(dir, Vec::cross(dir, pos)); } - static inline Plucker createFromVertices(const Vec& v1, const Vec& v2) - { - Plucker p; - p.U = v2 - v1; - p.V = Vec::cross(p.U, v1); - return p; - } + static inline Plucker createFromVertices(const Vec& v1, const Vec& v2) { return createFromDir(v1, v2 - v1); } // permuted inner product - static inline double dot(const Plucker& a, const Plucker& b) { return Vec::dot(a.U, b.V) + Vec::dot(b.U, a.V); } + static inline double dot(const Plucker& r, const Plucker& s) { return Vec::dot(r.U, s.V) + Vec::dot(s.U, r.V); } }; class TetraMeshSnapshot::Face @@ -242,19 +236,19 @@ class TetraMeshSnapshot::Face Face(const Tetra* tetra, int face) { - int v1 = (face + 1) % 4; - int v2 = (face + 2) % 4; - int v3 = (face + 3) % 4; + int v0 = (face + 1) % 4; + int v1 = (face + 2) % 4; + int v2 = (face + 3) % 4; // if face is even we should swap two edges - if (face % 2 == 0) std::swap(v2, v3); + if (face % 2 == 0) std::swap(v0, v2); _edges[0] = Plucker::createFromVertices(tetra->_vertices[v1], tetra->_vertices[v2]); - _edges[1] = Plucker::createFromVertices(tetra->_vertices[v2], tetra->_vertices[v3]); - _edges[2] = Plucker::createFromVertices(tetra->_vertices[v3], tetra->_vertices[v1]); - _vertex_indices[0] = v1; - _vertex_indices[1] = v2; - _vertex_indices[2] = v3; + _edges[1] = Plucker::createFromVertices(tetra->_vertices[v2], tetra->_vertices[v0]); + _edges[2] = Plucker::createFromVertices(tetra->_vertices[v0], tetra->_vertices[v1]); + _vertex_indices[0] = v0; + _vertex_indices[1] = v1; + _vertex_indices[2] = v2; } // leaving = true: returns true if the ray is leaving @@ -264,7 +258,7 @@ class TetraMeshSnapshot::Face for (int i = 0; i < 3; i++) { double prod = Plucker::dot(ray, _edges[i]); - if (leaving == prod < 0) return false; // weird if prod == 0 + if (leaving != (prod < 0)) return false; // weird if prod == 0 w[i] = prod; } double W = w[0] + w[1] + w[2]; @@ -400,25 +394,25 @@ namespace void TetraMeshSnapshot::buildMesh(bool relax) { /////////////////////////////////////////////////////////////////////////// - setExtent(Box(-1, -1, -1, 1, 1, 1)); - vector sites; - for (size_t i = 0; i < 7; i++) - { - sites.push_back(random()->position(_extent)); - } - - // sites.push_back(Vec(0.2, 0.2, 0)); - // sites.push_back(Vec(0.2, -0.2, 0)); + // setExtent(Box(-1, -1, -1, 1, 1, 1)); + // vector sites; + // for (size_t i = 0; i < 10; i++) + // { + // sites.push_back(random()->position(_extent)); + // } + + // sites.push_back(Vec(0.2, 0.2, 0.1)); + // sites.push_back(Vec(0.2, -0.2, 0.1)); // sites.push_back(Vec(0.4, 0.4, 0)); // sites.push_back(Vec(0.4, -0.4, 0)); - // sites.push_back(Vec(-0.5, 0, 0)); - // sites.push_back(Vec(0, 0, 0.5)); - // sites.push_back(Vec(0, 0, -0.5)); + // sites.push_back(Vec(-0.5, 0, 0.1)); + // sites.push_back(Vec(0, 0, 0.6)); + // sites.push_back(Vec(0, 0, -0.4)); - int n = sites.size(); - _cells.resize(n); - for (int m = 0; m != n; ++m) _cells[m] = new Cell(sites[m]); + // int n = sites.size(); + // _cells.resize(n); + // for (int m = 0; m != n; ++m) _cells[m] = new Cell(sites[m]); const double A = extent().rmax().norm() * 1000; @@ -969,193 +963,198 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator void startwriting() { - if (!out.is_open()) out.open("photon.txt"); + // if (!out.is_open()) out.open("photon.txt"); } - void stopwriting() { out.close(); } + void stopwriting() + { + // out.close(); + } - void write(const Vec& exit, double ds) + void write(const Vec& exit, double ds, int face) { - out << "photon=" << _mr << "\n"; - out << "ds=" << ds << "\n"; - out << "r=" << r().x() << "," << r().y() << "," << r().z() << "\n"; - out << "exit=" << exit.x() << "," << exit.y() << "," << exit.z() << "\n"; - out << "k=" << k().x() << "," << k().y() << "," << k().z() << "\n"; - // out.flush(); // for debug + // out << "photon=" << _mr << "," << face << "\n"; + // out << "ds=" << ds << "\n"; + // out << "r=" << r().x() << "," << r().y() << "," << r().z() << "\n"; + // out << "exit=" << exit.x() << "," << exit.y() << "," << exit.z() << "\n"; + // out << "k=" << k().x() << "," << k().y() << "," << k().z() << "\n"; } bool next() override { - switch (state()) + if (state() == State::Unknown) { - case State::Unknown: - { - startwriting(); + startwriting(); - // try moving the photon packet inside the grid; if this is impossible, return an empty path - if (!moveInside(_grid->extent(), _grid->_eps)) return false; + // try moving the photon packet inside the grid; if this is impossible, return an empty path + if (!moveInside(_grid->extent(), _grid->_eps)) return false; - // get the index of the cell containing the current position - _mr = _grid->cellIndex(r()); + // get the index of the cell containing the current position + _mr = _grid->cellIndex(r()); - if (_mr == -1) - setState(State::Outside); - else - wasInside = true; + if (_mr == -1) + setState(State::Outside); + else + wasInside = true; - // if the photon packet started outside the grid, return the corresponding nonzero-length segment; - // otherwise fall through to determine the first actual segment - if (ds() > 0.) return true; + // if the photon packet started outside the grid, return the corresponding nonzero-length segment; + // otherwise fall through to determine the first actual segment + if (ds() > 0.) return true; - write(Vec(), 0); - } + write(r(), 0, -1); + } - // intentionally falls through - case State::Inside: + // intentionally falls through + if (state() == State::Inside) + { + // loop in case no exit point was found (which should happen only rarely) + while (true) { - // loop in case no exit point was found (which should happen only rarely) - while (true) + // initialize the smallest nonnegative intersection distance and corresponding index + double sq = DBL_MAX; // very large, but not infinity (so that infinite si values are discarded) + const int NO_INDEX = -99; // meaningless cell index + int mq = NO_INDEX; + + // plucker coords + const Plucker ray = Plucker::createFromDir(r(), k()); + const Tetra* tetra = _grid->_tetrahedra[_mr]; + + int leavingIndex = -1; + std::array w; + Face leavingFace; + + for (int f = 0; f < 4; f++) { - // initialize the smallest nonnegative intersection distance and corresponding index - double sq = DBL_MAX; // very large, but not infinity (so that infinite si values are discarded) - const int NO_INDEX = -99; // meaningless cell index - int mq = NO_INDEX; - - // plucker coords - const Plucker ray = Plucker::createFromDir(r(), k()); - const Tetra* tetra = _grid->_tetrahedra[_mr]; - - int leavingFace = -1; - int leaving_faces = 0; - std::array w; - Face face; - for (int f = 0; f < 4; f++) - { - face = Face(tetra, f); + leavingFace = Face(tetra, f); - if (face.intersects(w, ray, true)) - { - leaving_faces++; - leavingFace = f; - bool allZero = std::all_of(w.begin(), w.end(), [](double val) { return val == 0.0; }); - if (allZero) _grid->log()->error("TetraMeshSnapshot: all plucker prods == 0"); - // break; - } + if (leavingFace.intersects(w, ray, true)) + { + leavingIndex = f; + bool allZero = std::all_of(w.begin(), w.end(), [](double val) { return val == 0.0; }); + if (allZero) std::cout << "TetraMeshSnapshot: all plucker prods == 0" << std::endl; + break; } - if (leaving_faces != 1) _grid->log()->error("TetraMeshSnapshot: too many leaving faces!"); + } - Vec exit; + Vec exit; + if (leavingIndex != -1) + { for (int i = 0; i < 3; i++) { - exit += tetra->_vertices[face._vertex_indices[i]] * w[i]; + exit += tetra->_vertices[leavingFace._vertex_indices[i]] * w[i]; } - sq = (exit - r()).norm(); - mq = tetra->_neighbors[leavingFace]; + mq = tetra->_neighbors[leavingIndex]; + } - // if no exit point was found, advance the current point by a small distance, - // recalculate the cell index, and return to the start of the loop - if (mq == NO_INDEX) - { - propagater(_grid->_eps); - _mr = _grid->cellIndex(r()); + // if no exit point was found, advance the current point by a small distance, + // recalculate the cell index, and return to the start of the loop + if (mq == NO_INDEX) + { + propagater(_grid->_eps); + _mr = _grid->cellIndex(r()); - // if we're outside the domain, terminate the path without returning a path segment - if (_mr < 0) - { - setState(State::Outside); - return false; - } - } - // otherwise set the current point to the exit point and return the path segment - else + // if we're outside the domain, terminate the path without returning a path segment + if (_mr < 0) { - propagater(sq + _grid->_eps); - setSegment(_mr, sq); - write(exit, sq); - _mr = mq; - - // if we're outside the domain, terminate the path after returning this path segment - if (_mr < 0) setState(State::Outside); - return true; + setState(State::Outside); + return false; } } + // otherwise set the current point to the exit point and return the path segment + else + { + propagater(sq + _grid->_eps); + setSegment(_mr, sq); + write(exit, sq, leavingIndex); + _mr = mq; + + // if we're outside the domain, terminate the path after returning this path segment + if (_mr < 0) setState(State::Outside); + return true; + } } + } - case State::Outside: + if (state() == State::Outside) + { + // outside the convex hull and inside extent + if (wasInside && _grid->extent().contains(r())) { - // outside the convex hull and inside extent - if (wasInside && _grid->extent().contains(r())) - { - double t_x, t_y, t_z; - if (kx() < 0) - t_x = (_grid->extent().xmin() - rx()) / kx(); - else - t_x = (_grid->extent().xmax() - rx()) / kx(); - if (ky() < 0) - t_y = (_grid->extent().ymin() - ry()) / ky(); - else - t_y = (_grid->extent().ymax() - ry()) / ky(); - if (kz() < 0) - t_z = (_grid->extent().zmin() - rz()) / kz(); - else - t_z = (_grid->extent().zmax() - rz()) / kz(); - - double ds = min(t_x, min(t_y, t_z)); - propagater(ds + _grid->_eps); - write(r(), ds); - stopwriting(); - return false; - } + double t_x, t_y, t_z; + if (kx() < 0) + t_x = (_grid->extent().xmin() - rx()) / kx(); + else + t_x = (_grid->extent().xmax() - rx()) / kx(); + if (ky() < 0) + t_y = (_grid->extent().ymin() - ry()) / ky(); + else + t_y = (_grid->extent().ymax() - ry()) / ky(); + if (kz() < 0) + t_z = (_grid->extent().zmin() - rz()) / kz(); + else + t_z = (_grid->extent().zmax() - rz()) / kz(); - // figure out if the path intersects the convex hull - if (!wasInside) - { - const Plucker ray = Plucker::createFromDir(r(), k()); - int enteringfaces = 0; - int entering = -1; + double ds = min(t_x, min(t_y, t_z)); + propagater(ds + _grid->_eps); + write(r(), ds, -1); + stopwriting(); + return false; + } + + // figure out if the path intersects the convex hull + if (!wasInside) + { + const Plucker ray = Plucker::createFromDir(r(), k()); + int enteringfaces = 0; + int entering = -1; - std::array w; - std::array vertices; - for (int i = 0; i < _grid->_tetrahedra.size(); i++) + std::array w; + std::array vertices; + for (int i = 0; i < _grid->_tetrahedra.size(); i++) + { + const Tetra* tetra = _grid->_tetrahedra[i]; + for (int f = 0; f < 4; f++) { - const Tetra* tetra = _grid->_tetrahedra[i]; - for (int f = 0; f < 4; f++) + int n = tetra->_neighbors[f]; + // edge face + if (n == -1) { - int n = tetra->_neighbors[f]; - // edge face - if (n == -1) + Face face = Face(tetra, f); + if (face.intersects(w, ray, false)) { - Face face = Face(tetra, f); - if (face.intersects(w, ray, false)) + _mr = i; + + if (enteringfaces++ == 0) { - _mr = i; - enteringfaces++; // debug variable - if (enteringfaces == 0) - { - Vec exit; - for (int i = 0; i < 3; i++) - { - exit += tetra->_vertices[face._vertex_indices[i]] * w[i]; - } - - double sq = (exit - r()).norm(); - propagater(sq + _grid->_eps); - write(exit, sq); - } - else + Vec exit; + for (int i = 0; i < 3; i++) { - _grid->log()->error("too many entering faces for outside ray"); + exit += tetra->_vertices[face._vertex_indices[i]] * w[i]; } - // break; + + double sq = (exit - r()).norm(); + propagater(sq + _grid->_eps); + write(exit, sq, f); + } + else + { + _grid->log()->error("too many entering faces for outside ray"); } + // break; } } } - stopwriting(); + } + if (enteringfaces > 0) + { + wasInside = true; + setState(State::Inside); + return true; } } } + stopwriting(); return false; } }; From bbb956d6f34806a7c77605b45b6355cd7d0624f1 Mon Sep 17 00:00:00 2001 From: arlauwer Date: Tue, 7 Nov 2023 12:49:19 +0100 Subject: [PATCH 09/51] started work on efficient ray traversal --- SKIRT/core/TetraMeshSnapshot.cpp | 593 +++++++++++++++++-------------- SKIRT/core/TetraMeshSnapshot.hpp | 113 ++++-- configSKIRT_debug.sh | 44 +++ makeSKIRT_debug.sh | 37 ++ 4 files changed, 492 insertions(+), 295 deletions(-) create mode 100755 configSKIRT_debug.sh create mode 100755 makeSKIRT_debug.sh diff --git a/SKIRT/core/TetraMeshSnapshot.cpp b/SKIRT/core/TetraMeshSnapshot.cpp index 361a72c6..bf3ba0fb 100644 --- a/SKIRT/core/TetraMeshSnapshot.cpp +++ b/SKIRT/core/TetraMeshSnapshot.cpp @@ -22,7 +22,6 @@ #include "Units.hpp" #include #include -#include #include "container.hh" //////////////////////////////////////////////////////////////////// @@ -41,17 +40,13 @@ class TetraMeshSnapshot::Cell // constructor derives the site position from the first three property values and stores the user properties; // the other data members are set to zero or empty - Cell(const Array& prop) : _r(prop[0], prop[1], prop[2]), _properties{prop} {} + Cell(const Array& prop) : _r(prop[0], prop[1], prop[2]), _properties{prop} {} // WIP // adjusts the site position with the specified offset void relax(double cx, double cy, double cz) { _r += Vec(cx, cy, cz); } // initializes the receiver with information taken from the specified fully computed Tetra cell - void init(voro::voronoicell_neighbor& cell) - { - // copy a list of neighboring cell/site ids - cell.neighbors(_neighbors); - } + void init(voro::voronoicell_neighbor& cell) { cell.neighbors(_neighbors); } // clears the information taken from a Tetra cell so it can be reinitialized void clear() { _neighbors.clear(); } @@ -72,200 +67,257 @@ class TetraMeshSnapshot::Cell const Array& properties() { return _properties; } }; -class TetraMeshSnapshot::Tetra : public Box +//////////////////////////////////////////////////////////////////// + +TetraMeshSnapshot::Plucker::Plucker() {} + +//////////////////////////////////////////////////////////////////// + +TetraMeshSnapshot::Plucker::Plucker(const Vec& pos, const Vec& dir) : U(dir), V(Vec::cross(dir, pos)) {} + +//////////////////////////////////////////////////////////////////// + +// permuted inner product +inline double TetraMeshSnapshot::Plucker::dot(const Plucker& a, const Plucker& b) { -public: - double _volume; - std::array _vertices; - std::array _neighbors = {-1, -1, -1, -1}; - std::array _vertex_indices; - Array _properties; // user-defined properties, if any + return Vec::dot(a.U, b.V) + Vec::dot(b.U, a.V); +} -public: - Tetra(const vector& _cells, int i, int j, int k, int l) - { - _vertices[0] = _cells[i]->_r; - _vertices[1] = _cells[j]->_r; - _vertices[2] = _cells[k]->_r; - _vertices[3] = _cells[l]->_r; - - double xmin = DBL_MAX; - double ymin = DBL_MAX; - double zmin = DBL_MAX; - double xmax = -DBL_MAX; - double ymax = -DBL_MAX; - double zmax = -DBL_MAX; - for (const Vec& vertex : _vertices) - { - xmin = min(xmin, vertex.x()); - ymin = min(ymin, vertex.y()); - zmin = min(zmin, vertex.z()); - xmax = max(xmax, vertex.x()); - ymax = max(ymax, vertex.y()); - zmax = max(zmax, vertex.z()); - } - setExtent(Box(xmin, ymin, zmin, xmax, ymax, zmax)); +//////////////////////////////////////////////////////////////////// - _vertex_indices[0] = i; - _vertex_indices[1] = j; - _vertex_indices[2] = k; - _vertex_indices[3] = l; +TetraMeshSnapshot::Edge::Edge(const int i1, const int i2, const Vec& v1, const Vec& v2) + : Plucker((i1 < i2) ? v1 : v2, (i1 < i2) ? v2 : v1), i1(std::min(i1, i2)), i2(std::max(i1, i2)) +{} - _volume = Box::volume(); //temp - } +//////////////////////////////////////////////////////////////////// - // compute normal for face 3 (in clockwise direction of vertices 012) and dot with vertex 3 - double orient() +bool TetraMeshSnapshot::Edge::operator==(const Edge& edge) const +{ + return (i1 == edge.i1 && i2 == edge.i2); +} + +//////////////////////////////////////////////////////////////////// + +size_t TetraMeshSnapshot::Edge::HashFunction::operator()(const Edge* edge) const +{ + return ((size_t)edge->i2 << 32) + (size_t)edge->i1; +} + +//////////////////////////////////////////////////////////////////// + +bool TetraMeshSnapshot::Edge::EqualFunction::operator()(const Edge* e1, const Edge* e2) const +{ + return *e1 == *e2; +} + +//////////////////////////////////////////////////////////////////// + +TetraMeshSnapshot::Tetra::Tetra(const vector& _cells, int i, int j, int k, int l) +{ + _vertices[0] = &_cells[i]->_r; + _vertices[1] = &_cells[j]->_r; + _vertices[2] = &_cells[k]->_r; + _vertices[3] = &_cells[l]->_r; + _vertex_indices[0] = i; + _vertex_indices[1] = j; + _vertex_indices[2] = k; + _vertex_indices[3] = l; + + double xmin = DBL_MAX; + double ymin = DBL_MAX; + double zmin = DBL_MAX; + double xmax = -DBL_MAX; + double ymax = -DBL_MAX; + double zmax = -DBL_MAX; + for (const Vec* vertex : _vertices) { - const Vec e01 = _vertices[1] - _vertices[0]; - const Vec e02 = _vertices[2] - _vertices[0]; - const Vec e03 = _vertices[3] - _vertices[0]; - // this convention makes edges go clockwise around leaving rays from inside the tetrahedron - double orientation = Vec::dot(Vec::cross(e02, e01), e03); - if (orientation < 0) - { - std::swap(_vertices[0], _vertices[1]); - std::swap(_vertex_indices[0], _vertex_indices[1]); - } - return orientation; + xmin = min(xmin, vertex->x()); + ymin = min(ymin, vertex->y()); + zmin = min(zmin, vertex->z()); + xmax = max(xmax, vertex->x()); + ymax = max(ymax, vertex->y()); + zmax = max(zmax, vertex->z()); } + setExtent(Box(xmin, ymin, zmin, xmax, ymax, zmax)); - // return -1 if no shared face - // return 0-3 for opposite vertex of shared face - int shareFace(const Tetra* other) const + _volume = Box::volume(); // WIP +} + +//////////////////////////////////////////////////////////////////// + +int TetraMeshSnapshot::Tetra::getEdge(int t1, int t2) const +{ + // negative index shows that the edge is inverted + if (t1 > t2) + return -((t2 == 0) ? t1 - 1 : t2 + t1); + else + return (t1 == 0) ? t2 - 1 : t1 + t2; +} + +//////////////////////////////////////////////////////////////////// + +double TetraMeshSnapshot::Tetra::orient() +{ + const Vec e01 = *_vertices[1] - *_vertices[0]; + const Vec e02 = *_vertices[2] - *_vertices[0]; + const Vec e03 = *_vertices[3] - *_vertices[0]; + // this convention makes edges go clockwise around leaving rays from inside the tetrahedron + // so their plucker products are all negative if the ray leaves + double orientation = Vec::dot(Vec::cross(e01, e02), e03); + if (orientation > 0) { - int equal_vert = 0; - int opposite = 0 + 1 + 2 + 3; + // swap last 2, this means first 2 indices can be ordered i < j + std::swap(_vertices[2], _vertices[3]); + std::swap(_vertex_indices[2], _vertex_indices[3]); + } + return orientation; +} - for (int i = 0; i < 4; i++) +//////////////////////////////////////////////////////////////////// + +void TetraMeshSnapshot::Tetra::addEdges(EdgeSet& edgeset) +{ + for (int t1 = 0; t1 < 3; t1++) + { + for (int t2 = 1; t2 < 4; t2++) { - int vi = _vertex_indices[i]; + if (t2 <= t1) continue; - for (int j = 0; j < 4; j++) - { - int vj = other->_vertex_indices[j]; + Edge* edge = new Edge(_vertex_indices[t1], _vertex_indices[t2], *_vertices[t1], *_vertices[t2]); + auto res = edgeset.insert(edge); - if (vi == vj) - { - equal_vert++; - opposite -= i; - break; - } + // insertion unsuccessful (edge already exists) + if (!res.second) + { + delete edge; + edge = *res.first; } + + _edges[getEdge(t1, t2)] = edge; } - if (equal_vert == 3) return opposite; - return -1; } +} + +//////////////////////////////////////////////////////////////////// + +int TetraMeshSnapshot::Tetra::shareFace(const Tetra* other) const +{ + int equal_vert = 0; + int opposite = 0 + 1 + 2 + 3; - bool equals(const Tetra* other) const + for (int i = 0; i < 4; i++) { - for (int v : _vertex_indices) + int vi = _vertex_indices[i]; + + for (int j = 0; j < 4; j++) { - bool match = false; - for (int u : other->_vertex_indices) + int vj = other->_vertex_indices[j]; + + if (vi == vj) { - if (u == v) - { - match = true; - break; - } + equal_vert++; + opposite -= i; + break; } - if (!match) return false; } - return true; } + if (equal_vert == 3) return opposite; + return -1; +} - bool SameSide(const Vec& v0, const Vec& v1, const Vec& v2, const Vec& v3, const Vec& pos) const - { - Vec normal = Vec::cross(v1 - v0, v2 - v0); - double dotV4 = Vec::dot(normal, v3 - v0); - double dotP = Vec::dot(normal, pos - v0); - return (dotV4 > 0) == (dotP > 0); - } +//////////////////////////////////////////////////////////////////// + +bool TetraMeshSnapshot::Tetra::intersects(std::array& prods, const Plucker& ray, int face, + bool leaving = true) const +{ + int t0 = (face + 1) % 4; + int t1 = (face + 2) % 4; + int t2 = (face + 3) % 4; + + // if face is even we should swap two edges + if (face % 2 == 0) std::swap(t0, t2); + + Edge* edges[3] = {_edges[getEdge(t1, t2)], _edges[getEdge(t2, t0)], _edges[getEdge(t0, t1)]}; - bool inside(const Position& bfr) const + double sum = 0; + for (int i = 0; i < 3; i++) { - // very poor implementation - const Vec& v0 = _vertices[0]; - const Vec& v1 = _vertices[1]; - const Vec& v2 = _vertices[2]; - const Vec& v3 = _vertices[3]; - return SameSide(v0, v1, v2, v3, bfr) && SameSide(v1, v2, v3, v0, bfr) && SameSide(v2, v3, v0, v1, bfr) - && SameSide(v3, v0, v1, v2, bfr); + double prod = Plucker::dot(ray, *_edges[i]); + if (leaving != (prod < 0)) return false; + prods[i] = prod; + sum += prod; } + prods = {prods[0] / sum, prods[1] / sum, prods[2] / sum}; + return true; +} + +//////////////////////////////////////////////////////////////////// - Position position() const +bool TetraMeshSnapshot::Tetra::equals(const Tetra* other) const +{ + for (int v : _vertex_indices) { - Position pos; - for (int i = 0; i < 4; i++) pos += _vertices[i]; - pos /= 4; - return pos; + bool match = false; + for (int u : other->_vertex_indices) + { + if (u == v) + { + match = true; + break; + } + } + if (!match) return false; } + return true; +} - double volume() const { return _volume; } - - const Array& properties() { return _properties; } -}; +//////////////////////////////////////////////////////////////////// -class TetraMeshSnapshot::Plucker +bool TetraMeshSnapshot::Tetra::SameSide(const Vec& v0, const Vec& v1, const Vec& v2, const Vec& v3, + const Vec& pos) const { -private: - Vec U, V; + Vec normal = Vec::cross(v1 - v0, v2 - v0); + double dotV4 = Vec::dot(normal, v3 - v0); + double dotP = Vec::dot(normal, pos - v0); + return (dotV4 > 0) == (dotP > 0); +} - Plucker(const Vec& U, const Vec& V) : U(U), V(V) {} +//////////////////////////////////////////////////////////////////// -public: - Plucker() {} +bool TetraMeshSnapshot::Tetra::inside(const Position& bfr) const +{ + // very poor implementation + const Vec& v0 = *_vertices[0]; + const Vec& v1 = *_vertices[1]; + const Vec& v2 = *_vertices[2]; + const Vec& v3 = *_vertices[3]; + return SameSide(v0, v1, v2, v3, bfr) && SameSide(v1, v2, v3, v0, bfr) && SameSide(v2, v3, v0, v1, bfr) + && SameSide(v3, v0, v1, v2, bfr); +} - static inline Plucker createFromDir(const Vec& pos, const Vec& dir) { return Plucker(dir, Vec::cross(dir, pos)); } +//////////////////////////////////////////////////////////////////// - static inline Plucker createFromVertices(const Vec& v1, const Vec& v2) { return createFromDir(v1, v2 - v1); } +Position TetraMeshSnapshot::Tetra::position() const +{ + Position pos; + for (int i = 0; i < 4; i++) pos += *_vertices[i]; + pos /= 4; + return pos; +} - // permuted inner product - static inline double dot(const Plucker& r, const Plucker& s) { return Vec::dot(r.U, s.V) + Vec::dot(s.U, r.V); } -}; +//////////////////////////////////////////////////////////////////// -class TetraMeshSnapshot::Face +double TetraMeshSnapshot::Tetra::volume() const { -public: - std::array _edges; - std::array _vertex_indices; - -public: - Face() {} + return _volume; +} - Face(const Tetra* tetra, int face) - { - int v0 = (face + 1) % 4; - int v1 = (face + 2) % 4; - int v2 = (face + 3) % 4; - - // if face is even we should swap two edges - if (face % 2 == 0) std::swap(v0, v2); - - _edges[0] = Plucker::createFromVertices(tetra->_vertices[v1], tetra->_vertices[v2]); - _edges[1] = Plucker::createFromVertices(tetra->_vertices[v2], tetra->_vertices[v0]); - _edges[2] = Plucker::createFromVertices(tetra->_vertices[v0], tetra->_vertices[v1]); - _vertex_indices[0] = v0; - _vertex_indices[1] = v1; - _vertex_indices[2] = v2; - } +//////////////////////////////////////////////////////////////////// - // leaving = true: returns true if the ray is leaving - // leaving = false: returns true if the ray is entering - inline bool intersects(std::array& w, const Plucker& ray, bool leaving = true) - { - for (int i = 0; i < 3; i++) - { - double prod = Plucker::dot(ray, _edges[i]); - if (leaving != (prod < 0)) return false; // weird if prod == 0 - w[i] = prod; - } - double W = w[0] + w[1] + w[2]; - w = {w[0] / W, w[1] / W, w[2] / W}; - return true; - } -}; +const Array& TetraMeshSnapshot::Tetra::properties() +{ + return _properties; +} //////////////////////////////////////////////////////////////////// @@ -393,30 +445,7 @@ namespace void TetraMeshSnapshot::buildMesh(bool relax) { - /////////////////////////////////////////////////////////////////////////// - // setExtent(Box(-1, -1, -1, 1, 1, 1)); - // vector sites; - // for (size_t i = 0; i < 10; i++) - // { - // sites.push_back(random()->position(_extent)); - // } - - // sites.push_back(Vec(0.2, 0.2, 0.1)); - // sites.push_back(Vec(0.2, -0.2, 0.1)); - // sites.push_back(Vec(0.4, 0.4, 0)); - // sites.push_back(Vec(0.4, -0.4, 0)); - - // sites.push_back(Vec(-0.5, 0, 0.1)); - // sites.push_back(Vec(0, 0, 0.6)); - // sites.push_back(Vec(0, 0, -0.4)); - - // int n = sites.size(); - // _cells.resize(n); - // for (int m = 0; m != n; ++m) _cells[m] = new Cell(sites[m]); - - const double A = extent().rmax().norm() * 1000; - - /////////////////////////////////////////////////////////////////////////// + const double A = extent().rmax().norm() * 1000; // WIP int numCells = _cells.size(); @@ -541,7 +570,7 @@ void TetraMeshSnapshot::buildMesh(bool relax) // for each site: // - compute the corresponding cell in the Tetra tesselation // - extract and copy the relevant information to the cell object with the corresponding index in our vector - log()->info("Constructing Tetra tessellation with " + std::to_string(numCells) + " cells"); + log()->info("Constructing intermediate Voronoi tessellation with " + std::to_string(numCells) + " cells"); log()->infoSetElapsed(numCells); // allocate a separate cell calculator for each thread to avoid conflicts voro::voro_compute vcompute(vcon, _nb, _nb, _nb); @@ -566,14 +595,15 @@ void TetraMeshSnapshot::buildMesh(bool relax) } while (vloop.inc()); if (numDone > 0) log()->infoIfElapsed("Computed Tetra cells: ", numDone); - //////////////////////////////////////////////////////////////////////////////////////////// for (int i = 0; i < numCells; i++) { Cell* c1 = _cells[i]; for (int j : c1->neighbors()) { - if (j < 0) continue; + // first 2 indices can always be ordered i < j + // last 2 indices can be swapped if orientation is not correct + if (j < 0 || j > i) continue; Cell* c2 = _cells[j]; @@ -629,16 +659,19 @@ void TetraMeshSnapshot::buildMesh(bool relax) if (r < R) { delaunay = false; - break; + goto end_delaunay; } } - if (!delaunay) break; } + end_delaunay: if (!delaunay) continue; // orient tetrahedron in the same consistent way tetra->orient(); + //add edges + tetra->addEdges(_edgeset); + centers.push_back(center); radii.push_back(sqrt(R)); _tetrahedra.push_back(tetra); @@ -662,7 +695,6 @@ void TetraMeshSnapshot::buildMesh(bool relax) if (shared != -1) tetra->_neighbors[shared] = j; } } - //////////////////////////////////////////////////////////////////////////////////////////// } //////////////////////////////////////////////////////////////////// @@ -811,10 +843,10 @@ void TetraMeshSnapshot::writeGridPlotFiles(const SimulationItem* probe) const for (size_t j = 0; j < 4; j++) { - const Vec& v = tetra->_vertices[j]; - coords.push_back(v.x()); - coords.push_back(v.y()); - coords.push_back(v.z()); + const Vec* v = tetra->_vertices[j]; + coords.push_back(v->x()); + coords.push_back(v->y()); + coords.push_back(v->z()); } // write the edges of the cell to the plot files // if (bounds.zmin() <= 0 && bounds.zmax() >= 0) plotxy.writePolyhedron(coords, indices); @@ -955,6 +987,7 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator int _mr{-1}; // true if the path is was ever inside the convex hull bool wasInside = false; + int enteringFace = -1; public: MySegmentGenerator(const TetraMeshSnapshot* grid) : _grid(grid) {} @@ -963,21 +996,18 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator void startwriting() { - // if (!out.is_open()) out.open("photon.txt"); + if (!out.is_open()) out.open("photon.txt"); } - void stopwriting() - { - // out.close(); - } + void stopwriting() { out.close(); } void write(const Vec& exit, double ds, int face) { - // out << "photon=" << _mr << "," << face << "\n"; - // out << "ds=" << ds << "\n"; - // out << "r=" << r().x() << "," << r().y() << "," << r().z() << "\n"; - // out << "exit=" << exit.x() << "," << exit.y() << "," << exit.z() << "\n"; - // out << "k=" << k().x() << "," << k().y() << "," << k().z() << "\n"; + out << "photon=" << _mr << "," << face << "\n"; + out << "ds=" << ds << "\n"; + out << "r=" << r().x() << "," << r().y() << "," << r().z() << "\n"; + out << "exit=" << exit.x() << "," << exit.y() << "," << exit.z() << "\n"; + out << "k=" << k().x() << "," << k().y() << "," << k().z() << "\n"; } bool next() override @@ -995,7 +1025,7 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator if (_mr == -1) setState(State::Outside); else - wasInside = true; + wasInside = true; // setState has already been called // if the photon packet started outside the grid, return the corresponding nonzero-length segment; // otherwise fall through to determine the first actual segment @@ -1016,32 +1046,81 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator int mq = NO_INDEX; // plucker coords - const Plucker ray = Plucker::createFromDir(r(), k()); + const Plucker ray = Plucker(r(), k()); const Tetra* tetra = _grid->_tetrahedra[_mr]; - int leavingIndex = -1; - std::array w; - Face leavingFace; - - for (int f = 0; f < 4; f++) + int leavingFace = -1; + std::array prods; + if (enteringFace == -1) // this should only be done once + { + for (int face = 0; face < 4; face++) + { + if (tetra->intersects(prods, ray, face, true)) + { + leavingFace = face; + break; + } + } + } + else { - leavingFace = Face(tetra, f); + int t0 = (enteringFace + 1) % 4; + int t1 = (enteringFace + 2) % 4; + int t2 = (enteringFace + 3) % 4; + // if face is even we should swap two edges + if (enteringFace % 2 == 0) std::swap(t0, t2); + + int edges[3]; + edges[0] = tetra->getEdge(t0, enteringFace); + edges[1] = tetra->getEdge(t1, enteringFace); + edges[2] = tetra->getEdge(t2, enteringFace); + + // 2 step decision tree + int i = 0, prev_i; + for (int _ = 0; _ < 2; _++) + { + int e = edges[i]; + if (e < 0) + prods[i] = -Plucker::dot(ray, *tetra->_edges[-e]); + else + prods[i] = Plucker::dot(ray, *tetra->_edges[e]); + + // if clockwise move clockwise + prev_i = i; + if (prods[i] < 0) + i = (i + 1) % 3; + else // if counter move counter + i = (i - 1) % 3; + } + + // if 2 clockwise: face=t0 + // if 2 counter: face=t0 + // if clockwise then counter: face=t2 + // if counter then clockwise: face=t1 + + // and calculate last inner products + if (prods[0] == prods[i]) + { + leavingFace = t0; - if (leavingFace.intersects(w, ray, true)) + + + } + else { - leavingIndex = f; - bool allZero = std::all_of(w.begin(), w.end(), [](double val) { return val == 0.0; }); - if (allZero) std::cout << "TetraMeshSnapshot: all plucker prods == 0" << std::endl; - break; + if (prods[0] < 0) + leavingFace = t2; + else + leavingFace = t1; } } Vec exit; - if (leavingIndex != -1) + if (leavingFace != -1) { for (int i = 0; i < 3; i++) { - exit += tetra->_vertices[leavingFace._vertex_indices[i]] * w[i]; + exit += tetra->_vertices[leavingFace._indices[i]] * w[i]; } sq = (exit - r()).norm(); mq = tetra->_neighbors[leavingIndex]; @@ -1066,7 +1145,7 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator { propagater(sq + _grid->_eps); setSegment(_mr, sq); - write(exit, sq, leavingIndex); + write(exit, sq, leavingFace); _mr = mq; // if we're outside the domain, terminate the path after returning this path segment @@ -1105,47 +1184,47 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator // figure out if the path intersects the convex hull if (!wasInside) { - const Plucker ray = Plucker::createFromDir(r(), k()); + const Plucker ray = Plucker(r(), k()); int enteringfaces = 0; int entering = -1; - std::array w; - std::array vertices; - for (int i = 0; i < _grid->_tetrahedra.size(); i++) - { - const Tetra* tetra = _grid->_tetrahedra[i]; - for (int f = 0; f < 4; f++) - { - int n = tetra->_neighbors[f]; - // edge face - if (n == -1) - { - Face face = Face(tetra, f); - if (face.intersects(w, ray, false)) - { - _mr = i; - - if (enteringfaces++ == 0) - { - Vec exit; - for (int i = 0; i < 3; i++) - { - exit += tetra->_vertices[face._vertex_indices[i]] * w[i]; - } - - double sq = (exit - r()).norm(); - propagater(sq + _grid->_eps); - write(exit, sq, f); - } - else - { - _grid->log()->error("too many entering faces for outside ray"); - } - // break; - } - } - } - } + // std::array w; + // std::array vertices; + // for (int i = 0; i < _grid->_tetrahedra.size(); i++) + // { + // const Tetra* tetra = _grid->_tetrahedra[i]; + // for (int f = 0; f < 4; f++) + // { + // int n = tetra->_neighbors[f]; + // // edge face + // if (n == -1) + // { + // Face face = Face(tetra, f); + // if (face.intersects(w, ray, false)) + // { + // _mr = i; + + // if (enteringfaces++ == 0) + // { + // Vec exit; + // for (int i = 0; i < 3; i++) + // { + // exit += tetra->_vertices[face._indices[i]] * w[i]; + // } + + // double sq = (exit - r()).norm(); + // propagater(sq + _grid->_eps); + // write(exit, sq, f); + // } + // else + // { + // _grid->log()->error("too many entering faces for outside ray"); + // } + // // break; + // } + // } + // } + // } if (enteringfaces > 0) { wasInside = true; diff --git a/SKIRT/core/TetraMeshSnapshot.hpp b/SKIRT/core/TetraMeshSnapshot.hpp index 4fb6806a..5eefcf5e 100644 --- a/SKIRT/core/TetraMeshSnapshot.hpp +++ b/SKIRT/core/TetraMeshSnapshot.hpp @@ -8,6 +8,7 @@ #include "Array.hpp" #include "Snapshot.hpp" +#include class PathSegmentGenerator; class SiteListInterface; class SpatialGridPath; @@ -188,11 +189,81 @@ class TetraMeshSnapshot : public Snapshot paths and densities; see the buildMesh() function. */ class Cell; - class Tetra; + class Plucker + { + private: + Vec U, V; - class Plucker; + public: + Plucker(); - class Face; + Plucker(const Vec& pos, const Vec& dir); + + // permuted inner product + static inline double dot(const Plucker& a, const Plucker& b); + }; + + class Edge : public Plucker + { + public: + const int i1, i2; + + Edge(int i1, int i2, const Vec& v1, const Vec& v2); + + bool operator==(const Edge& edge) const; + + struct HashFunction + { + size_t operator()(const Edge* edge) const; + }; + + struct EqualFunction + { + bool operator()(const Edge* e1, const Edge* e2) const; + }; + }; + + typedef std::unordered_set EdgeSet; + + class Tetra : public Box + { + public: + double _volume; + std::array _vertices; + std::array _vertex_indices; + std::array _neighbors = {-1, -1, -1, -1}; + std::array _edges; + Array _properties; + + public: + Tetra(const vector& _cells, int i, int j, int k, int l); + + // get the edge using the tetra indices [0, 3] + int getEdge(int t1, int t2) const; + + // compute normal for face 3 (in clockwise direction of vertices 012) and dot with vertex 3 + double orient(); + + void addEdges(EdgeSet& edgeset); + + // return -1 if no shared face + // return 0-3 for opposite vertex of shared face + int shareFace(const Tetra* other) const; + + bool intersects(std::array& prods, const Plucker& ray, int face, bool leaving = true) const; + + bool equals(const Tetra* other) const; + + bool SameSide(const Vec& v0, const Vec& v1, const Vec& v2, const Vec& v3, const Vec& pos) const; + + bool inside(const Position& bfr) const; + + Position position() const; + + double volume() const; + + const Array& properties(); + }; // temp test std::vector centers; @@ -231,37 +302,6 @@ class TetraMeshSnapshot : public Snapshot density columns being imported. */ void calculateDensityAndMass(); - /** Private function to recursively build a binary search tree (see - en.wikipedia.org/wiki/Kd-tree) */ - // Node* buildTree(vector::iterator first, vector::iterator last, int depth) const; - - /** This private function builds data structures that allow accelerating the operation of the - cellIndex() function. It assumes that the Tetra mesh has already been built. - - The domain is partitioned using a linear cubodial grid into cells that are called \em - blocks. For each block, the function builds and stores a list of all Tetra cells that - possibly overlap that block. Retrieving the list of cells possibly overlapping a given - point in the domain then comes down to locating the block containing the point (which is - trivial since the grid is linear). The current implementation uses a Tetra cell's - enclosing cuboid to test for intersection with a block. Performing a precise intersection - test is \em really slow and the shortened block lists don't substantially accelerate the - cellIndex() function. - - To further reduce the search time within blocks that overlap with a large number of cells, - the function builds a binary search tree on the cell sites for those blocks (see for example - en.wikipedia.org/wiki/Kd-tree). */ - // void buildSearchPerBlock(); - - /** This private function builds a data structure that allows accelerating the operation of the - cellIndex() function without using the Tetra mesh. The domain is not partitioned in - blocks. The function builds a single binary search tree on all cell sites (see for example - en.wikipedia.org/wiki/Kd-tree). */ - // void buildSearchSingle(); - - /** This private function returns true if the given point is closer to the site with index m - than to the sites with indices ids. */ - // bool isPointClosestTo(Vec r, int m, const vector& ids) const; - bool inTetrahedra(const Tetra* tetra) const; //====================== Output ===================== @@ -286,10 +326,6 @@ class TetraMeshSnapshot : public Snapshot range, the behavior is undefined. */ Position position(int m) const override; - /** This function returns the centroid of the Tetra cell with index \em m. If the index is - out of range, the behavior is undefined. */ - // Position centroidPosition(int m) const; - /** This function returns the volume of the Tetra cell with index \em m. If the index is out of range, the behavior is undefined. */ double volume(int m) const override; @@ -471,6 +507,7 @@ class TetraMeshSnapshot : public Snapshot vector _cells; // cell objects, indexed on m vector _tetrahedra; + EdgeSet _edgeset; // data members initialized when processing snapshot input, but only if a density policy has been set Array _rhov; // density for each cell (not normalized) diff --git a/configSKIRT_debug.sh b/configSKIRT_debug.sh new file mode 100755 index 00000000..891a818c --- /dev/null +++ b/configSKIRT_debug.sh @@ -0,0 +1,44 @@ +#!/bin/bash +# (use "chmod +rx scriptname" to make script executable) +# +# For use on any Unix system, including Mac OS X and Linux +# +# Execute this script with "git" as default directory to configure +# the release build options for your current local copy of the code +# +# To adjust a build option, enter: ./configSKIRT.sh = +# For example, to enable MPI, enter: ./configSKIRT.sh BUILD_WITH_MPI=ON +# + +# -------------------------------------------------------------------- + +# Look for cmake in the default path; exit with an error if we don't find it +CMAKEPATH="$(which cmake)" +if [ "$CMAKEPATH" == "" ] +then +echo +echo Fatal error: there is no cmake in the default path +echo +exit +else +echo +echo Using $CMAKEPATH to generate build files +echo +fi + +# Assemble the user options from the script arguments +USEROPTIONS="" +for ARGUMENT in "$@" +do + USEROPTIONS="$USEROPTIONS -D$ARGUMENT" +done + +# Generate the build files +$CMAKEPATH -E make_directory ../debug +$CMAKEPATH -E chdir ../debug $CMAKEPATH $USEROPTIONS -DCMAKE_BUILD_TYPE:STRING=Debug -L ../git + +# Provide instructions to the user +echo +echo "To adjust build options, enter: ./configSKIRT.sh = = ..." +echo "For example, to enable MPI, enter: ./configSKIRT.sh BUILD_WITH_MPI=ON" +echo diff --git a/makeSKIRT_debug.sh b/makeSKIRT_debug.sh new file mode 100755 index 00000000..5f31e233 --- /dev/null +++ b/makeSKIRT_debug.sh @@ -0,0 +1,37 @@ +#!/bin/bash +# (use "chmod +rx scriptname" to make script executable) +# +# For use on any Unix system, including Mac OS X and Linux +# +# Execute this script with "git" as default directory to +# build a release version of skirt in the "release" directory +# using your current local copy of the code +# +# By default the build uses a single thread; you can specify the +# number of parallel threads as the first command line argument +# + +# -------------------------------------------------------------------- + +# Look for cmake in the default path; exit with an error if we don't find it +CMAKEPATH="$(which cmake)" +if [ "$CMAKEPATH" == "" ] +then +echo +echo Fatal error: there is no cmake in the default path +echo +exit +else +echo +echo Using $CMAKEPATH to generate build files +echo +fi + +# Generate the build files +$CMAKEPATH -E make_directory ../debug +$CMAKEPATH -E chdir ../debug $CMAKEPATH -DCMAKE_BUILD_TYPE:STRING=Debug -DCMAKE_CXX_FLAGS:STRING="-O0 -g" -L ../git +echo + +# Perform the build +make -j ${1:-1} -C ../debug +echo From 3930fc6fe763bb4d9966f4fc5dbba3d8df10933b Mon Sep 17 00:00:00 2001 From: arlauwer Date: Sun, 19 Nov 2023 02:03:28 +0100 Subject: [PATCH 10/51] implemented traversal from maria et al. --- SKIRT/core/TetraMeshSnapshot.cpp | 484 ++++++++++++++++--------------- SKIRT/core/TetraMeshSnapshot.hpp | 38 +-- 2 files changed, 260 insertions(+), 262 deletions(-) diff --git a/SKIRT/core/TetraMeshSnapshot.cpp b/SKIRT/core/TetraMeshSnapshot.cpp index bf3ba0fb..2d95002f 100644 --- a/SKIRT/core/TetraMeshSnapshot.cpp +++ b/SKIRT/core/TetraMeshSnapshot.cpp @@ -77,7 +77,6 @@ TetraMeshSnapshot::Plucker::Plucker(const Vec& pos, const Vec& dir) : U(dir), V( //////////////////////////////////////////////////////////////////// -// permuted inner product inline double TetraMeshSnapshot::Plucker::dot(const Plucker& a, const Plucker& b) { return Vec::dot(a.U, b.V) + Vec::dot(b.U, a.V); @@ -86,28 +85,23 @@ inline double TetraMeshSnapshot::Plucker::dot(const Plucker& a, const Plucker& b //////////////////////////////////////////////////////////////////// TetraMeshSnapshot::Edge::Edge(const int i1, const int i2, const Vec& v1, const Vec& v2) - : Plucker((i1 < i2) ? v1 : v2, (i1 < i2) ? v2 : v1), i1(std::min(i1, i2)), i2(std::max(i1, i2)) + : Plucker(v1, v2 - v1), i1(i1), i2(i2) {} //////////////////////////////////////////////////////////////////// bool TetraMeshSnapshot::Edge::operator==(const Edge& edge) const { - return (i1 == edge.i1 && i2 == edge.i2); + return (i1 == edge.i1 && i2 == edge.i2) || (i1 == edge.i2 && i2 == edge.i1); } //////////////////////////////////////////////////////////////////// -size_t TetraMeshSnapshot::Edge::HashFunction::operator()(const Edge* edge) const +size_t TetraMeshSnapshot::Edge::hash(const int i1, const int i2) { - return ((size_t)edge->i2 << 32) + (size_t)edge->i1; -} - -//////////////////////////////////////////////////////////////////// - -bool TetraMeshSnapshot::Edge::EqualFunction::operator()(const Edge* e1, const Edge* e2) const -{ - return *e1 == *e2; + size_t max = std::max(i1, i2); + size_t min = std::min(i1, i2); + return (max << 32) + min; } //////////////////////////////////////////////////////////////////// @@ -140,18 +134,28 @@ TetraMeshSnapshot::Tetra::Tetra(const vector& _cells, int i, int j, int k } setExtent(Box(xmin, ymin, zmin, xmax, ymax, zmax)); - _volume = Box::volume(); // WIP + _volume = 1 / 6. + * abs(Vec::dot(Vec::cross(*_vertices[1] - *_vertices[0], *_vertices[2] - *_vertices[0]), + *_vertices[3] - *_vertices[0])); } //////////////////////////////////////////////////////////////////// -int TetraMeshSnapshot::Tetra::getEdge(int t1, int t2) const +double TetraMeshSnapshot::Tetra::getProd(const Plucker& ray, int t1, int t2) const { - // negative index shows that the edge is inverted + double prod = 1; if (t1 > t2) - return -((t2 == 0) ? t1 - 1 : t2 + t1); - else - return (t1 == 0) ? t2 - 1 : t1 + t2; + { + std::swap(t1, t2); + prod *= -1; + } + int e = (t1 == 0) ? t2 - 1 : t1 + t2; + Edge* edge = _edges[e]; + if (_vertex_indices[t1] != edge->i1) // not the same order + { + prod *= -1; + } + return prod * Plucker::dot(ray, *edge); } //////////////////////////////////////////////////////////////////// @@ -175,25 +179,35 @@ double TetraMeshSnapshot::Tetra::orient() //////////////////////////////////////////////////////////////////// -void TetraMeshSnapshot::Tetra::addEdges(EdgeSet& edgeset) +void TetraMeshSnapshot::Tetra::addEdges(EdgeMap& edgemap) { + for (int t1 = 0; t1 < 3; t1++) { - for (int t2 = 1; t2 < 4; t2++) + for (int t2 = t1 + 1; t2 < 4; t2++) { - if (t2 <= t1) continue; - - Edge* edge = new Edge(_vertex_indices[t1], _vertex_indices[t2], *_vertices[t1], *_vertices[t2]); - auto res = edgeset.insert(edge); - - // insertion unsuccessful (edge already exists) - if (!res.second) + // _edgemap + int i1 = _vertex_indices[t1]; + int i2 = _vertex_indices[t2]; + size_t key = Edge::hash(i1, i2); + auto it = edgemap.find(key); + + Edge* edge; + if (it == edgemap.end()) // not in map { - delete edge; - edge = *res.first; + edge = new Edge(i1, i2, *_vertices[t1], *_vertices[t2]); + // add this to the map + edgemap[key] = edge; + } + else // already in map + { + edge = it->second; } - _edges[getEdge(t1, t2)] = edge; + // edges are labelled: + // 0 1 2 3 4 5 + // 01 02 03 12 13 23 + _edges[(t1 == 0) ? t2 - 1 : t1 + t2] = edge; } } } @@ -227,27 +241,23 @@ int TetraMeshSnapshot::Tetra::shareFace(const Tetra* other) const //////////////////////////////////////////////////////////////////// -bool TetraMeshSnapshot::Tetra::intersects(std::array& prods, const Plucker& ray, int face, - bool leaving = true) const +bool TetraMeshSnapshot::Tetra::intersects(std::array& barycoords, const Plucker& ray, int face, + bool leaving) const { - int t0 = (face + 1) % 4; - int t1 = (face + 2) % 4; - int t2 = (face + 3) % 4; - - // if face is even we should swap two edges - if (face % 2 == 0) std::swap(t0, t2); - - Edge* edges[3] = {_edges[getEdge(t1, t2)], _edges[getEdge(t2, t0)], _edges[getEdge(t0, t1)]}; + std::array t = clockwiseVertices(face); double sum = 0; for (int i = 0; i < 3; i++) { - double prod = Plucker::dot(ray, *_edges[i]); + // edges: 12, 20, 01 + // verts: 0, 1, 2 + double prod = getProd(ray, t[(i + 1) % 3], t[(i + 2) % 3]); if (leaving != (prod < 0)) return false; - prods[i] = prod; + barycoords[i] = prod; sum += prod; } - prods = {prods[0] / sum, prods[1] / sum, prods[2] / sum}; + for (int i = 0; i < 3; i++) barycoords[i] /= sum; + return true; } @@ -286,6 +296,15 @@ bool TetraMeshSnapshot::Tetra::SameSide(const Vec& v0, const Vec& v1, const Vec& bool TetraMeshSnapshot::Tetra::inside(const Position& bfr) const { + // std::array bary = getBary(_vertices); + // double sum = 0.; + // for (int i = 0; i < 3; i++) + // { + // if (bary[i] < 0.) return false; + // sum += bary[i]; + // } + // return 1 - sum >= 0; + // very poor implementation const Vec& v0 = *_vertices[0]; const Vec& v1 = *_vertices[1]; @@ -293,6 +312,28 @@ bool TetraMeshSnapshot::Tetra::inside(const Position& bfr) const const Vec& v3 = *_vertices[3]; return SameSide(v0, v1, v2, v3, bfr) && SameSide(v1, v2, v3, v0, bfr) && SameSide(v2, v3, v0, v1, bfr) && SameSide(v3, v0, v1, v2, bfr); + + // Vec AB = *_vertices[1] - *_vertices[0]; + // Vec AC = *_vertices[2] - *_vertices[0]; + // Vec AD = *_vertices[3] - *_vertices[0]; + // Vec AP = bfr - *_vertices[0]; + + // double volume_ABC = Vec::dot(Vec::cross(AB, AC), AD); + // double volume_PBC = Vec::dot(Vec::cross(AP, AC), AD); + // double volume_PAC = Vec::dot(Vec::cross(AB, AP), AD); + // double volume_PAB = Vec::dot(Vec::cross(AB, AC), AP); + + // return (volume_ABC > 0) == (volume_PBC > 0) == (volume_PAC > 0) == (volume_PAB > 0); +} + +//////////////////////////////////////////////////////////////////// + +Vec TetraMeshSnapshot::Tetra::calcExit(const std::array& barycoords, int face) const +{ + std::array t = Tetra::clockwiseVertices(face); + Vec exit; + for (int i = 0; i < 3; i++) exit += *_vertices[t[i]] * barycoords[i]; + return exit; } //////////////////////////////////////////////////////////////////// @@ -321,6 +362,16 @@ const Array& TetraMeshSnapshot::Tetra::properties() //////////////////////////////////////////////////////////////////// +std::array TetraMeshSnapshot::Tetra::clockwiseVertices(int face) +{ + std::array t = {(face + 1) % 4, (face + 2) % 4, (face + 3) % 4}; + // if face is even we should swap two edges + if (face % 2 == 0) std::swap(t[0], t[2]); + return t; +} + +//////////////////////////////////////////////////////////////////// + namespace { template bool invec(const vector& vec, const T& e) @@ -336,6 +387,46 @@ namespace - x2 * y1 * z0 + x2 * y1 * z3 + x2 * y3 * z0 - x2 * y3 * z1 - x3 * y0 * z1 + x3 * y0 * z2 + x3 * y1 * z0 - x3 * y1 * z2 - x3 * y2 * z0 + x3 * y2 * z1; } + + // std::array getBary(const std::array& vertices) + // { + // double T[3][3]; + // double T_inv[3][3]; + + // for (int i = 0; i < 3; i++) + // { + // T[0][i] = vertices[i]->x() - vertices[3]->x(); + // T[1][i] = vertices[i]->y() - vertices[3]->y(); + // T[2][i] = vertices[i]->z() - vertices[3]->z(); + // } + + // double det = T[0][0] * (T[1][1] * T[2][2] - T[2][1] * T[1][2]) + // - T[0][1] * (T[1][0] * T[2][2] - T[1][2] * T[2][0]) + // + T[0][2] * (T[1][0] * T[2][1] - T[1][1] * T[2][0]); + + // double invdet = 1 / det; + + // T_inv[0][0] = (T[1][1] * T[2][2] - T[2][1] * T[1][2]) * invdet; + // T_inv[0][1] = (T[0][2] * T[2][1] - T[0][1] * T[2][2]) * invdet; + // T_inv[0][2] = (T[0][1] * T[1][2] - T[0][2] * T[1][1]) * invdet; + // T_inv[1][0] = (T[1][2] * T[2][0] - T[1][0] * T[2][2]) * invdet; + // T_inv[1][1] = (T[0][0] * T[2][2] - T[0][2] * T[2][0]) * invdet; + // T_inv[1][2] = (T[1][0] * T[0][2] - T[0][0] * T[1][2]) * invdet; + // T_inv[2][0] = (T[1][0] * T[2][1] - T[2][0] * T[1][1]) * invdet; + // T_inv[2][1] = (T[2][0] * T[0][1] - T[0][0] * T[2][1]) * invdet; + // T_inv[2][2] = (T[0][0] * T[1][1] - T[1][0] * T[0][1]) * invdet; + + // std::array bary; + // double vec[3] = {vertices[3]->x(), vertices[3]->y(), vertices[3]->z()}; + // for (int i = 0; i < 3; i++) + // { + // for (int j = 0; j < 3; j++) + // { + // bary[i] += T_inv[i][j] * vec[j]; + // } + // } + // return bary; + // } } //////////////////////////////////////////////////////////////////// @@ -348,7 +439,6 @@ TetraMeshSnapshot::~TetraMeshSnapshot() { for (auto cell : _cells) delete cell; for (auto tetra : _tetrahedra) delete tetra; - for (auto tree : _blocktrees) delete tree; } //////////////////////////////////////////////////////////////////// @@ -670,15 +760,15 @@ void TetraMeshSnapshot::buildMesh(bool relax) tetra->orient(); //add edges - tetra->addEdges(_edgeset); + tetra->addEdges(_edgemap); - centers.push_back(center); - radii.push_back(sqrt(R)); _tetrahedra.push_back(tetra); } } } } + _tetrahedra.shrink_to_fit(); + numTetra = _tetrahedra.size(); // find neighbors brute force for (size_t i = 0; i < _tetrahedra.size(); i++) @@ -695,6 +785,10 @@ void TetraMeshSnapshot::buildMesh(bool relax) if (shared != -1) tetra->_neighbors[shared] = j; } } + + log()->info("Done computing Delaunay tetrahedralization with " + std::to_string(_tetrahedra.size()) + " cells"); + log()->info("number of edges: " + std::to_string(_edgemap.size())); + log()->info("bucket count for edgeset: " + std::to_string(_edgemap.bucket_count())); } //////////////////////////////////////////////////////////////////// @@ -776,21 +870,9 @@ bool TetraMeshSnapshot::inTetrahedra(const Tetra* tetra) const //////////////////////////////////////////////////////////////////// -void TetraMeshSnapshot::writeGridPlotFiles(const SimulationItem* probe) const +void TetraMeshSnapshot::writeGridPlotFiles(const SimulationItem* /*probe*/) const { - // create the plot files - // SpatialGridPlotFile plotxy(probe, probe->itemName() + "_grid_xy"); - // SpatialGridPlotFile plotxz(probe, probe->itemName() + "_grid_xz"); - // SpatialGridPlotFile plotyz(probe, probe->itemName() + "_grid_yz"); - SpatialGridPlotFile plotxyz(probe, probe->itemName() + "_grid_xyz"); - - // vector test = {1, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 1}; - // vector test2 = {3, 0, 1, 2, 3, 0, 1, 3, 3, 0, 2, 3, 3, 1, 2, 3}; - // plotxyz.writePolyhedron(test, test2); - // return; - - //////////////////////////////////////////////////////////////////////////////////////////////// - std::ofstream outputFile("input.txt"); + std::ofstream outputFile("data/input.txt"); outputFile << "voronois=" << _cells.size() << "\n"; for (size_t i = 0; i < _cells.size(); i++) { @@ -823,37 +905,14 @@ void TetraMeshSnapshot::writeGridPlotFiles(const SimulationItem* probe) const } outputFile << "\n"; } - for (size_t i = 0; i < _tetrahedra.size(); i++) - { - outputFile << "circumsphere=" << i << "\n"; - outputFile << centers[i].x() << "," << centers[i].y() << "," << centers[i].z() << "\n"; - outputFile << radii[i] << "\n"; - } + // for (size_t i = 0; i < _tetrahedra.size(); i++) + // { + // outputFile << "circumsphere=" << i << "\n"; + // outputFile << centers[i].x() << "," << centers[i].y() << "," << centers[i].z() << "\n"; + // outputFile << radii[i] << "\n"; + // } - // Close the output file outputFile.close(); - //////////////////////////////////////////////////////////////////////////////////////////////// - - for (size_t i = 0; i < _tetrahedra.size(); i++) - { - const Tetra* tetra = _tetrahedra[i]; - - vector coords; - vector indices = {3, 0, 1, 2, 3, 0, 1, 3, 3, 0, 2, 3, 3, 1, 2, 3}; - - for (size_t j = 0; j < 4; j++) - { - const Vec* v = tetra->_vertices[j]; - coords.push_back(v->x()); - coords.push_back(v->y()); - coords.push_back(v->z()); - } - // write the edges of the cell to the plot files - // if (bounds.zmin() <= 0 && bounds.zmax() >= 0) plotxy.writePolyhedron(coords, indices); - // if (bounds.ymin() <= 0 && bounds.ymax() >= 0) plotxz.writePolyhedron(coords, indices); - // if (bounds.xmin() <= 0 && bounds.xmax() >= 0) plotyz.writePolyhedron(coords, indices); - if (_tetrahedra.size() <= 1000) plotxyz.writePolyhedron(coords, indices); - } } //////////////////////////////////////////////////////////////////// @@ -917,18 +976,34 @@ double TetraMeshSnapshot::mass() const Position TetraMeshSnapshot::generatePosition(int m) const { - // get loop-invariant information about the cell - const Box& box = _tetrahedra[m]->extent(); + // https://vcg.isti.cnr.it/activities/OLD/geometryegraphics/pointintetraedro.html + double s = random()->uniform(); + double t = random()->uniform(); + double u = random()->uniform(); + if (s + t > 1.0) + { // cut'n fold the cube into a prism + + s = 1.0 - s; + t = 1.0 - t; + } + if (t + u > 1.0) + { // cut'n fold the prism into a tetrahedron - // generate random points in the enclosing box until one happens to be inside the cell - for (int i = 0; i < 10000; i++) + double tmp = u; + u = 1.0 - s - t; + t = 1.0 - tmp; + } + else if (s + t + u > 1.0) { - Position r = random()->position(box); - if (_tetrahedra[m]->inside(r)) return r; + + double tmp = u; + u = s + t + u - 1.0; + s = 1 - t - tmp; } - log()->error("can't find random poisition in tetrahedron"); - return Position(); - // throw FATALERROR("Can't find random position in cell"); + double a = 1 - s - t - u; // a,s,t,u are the barycentric coordinates of the random point. + + const auto& vert = _tetrahedra[m]->_vertices; + return Position(*vert[0] * a + *vert[1] * s + *vert[2] * t + *vert[3] * u); } //////////////////////////////////////////////////////////////////// @@ -948,8 +1023,7 @@ Position TetraMeshSnapshot::generatePosition() const int TetraMeshSnapshot::cellIndex(Position bfr) const { - int tetras = _tetrahedra.size(); - for (int i = 0; i < tetras; i++) + for (int i = 0; i < numTetra; i++) { const Tetra* tetra = _tetrahedra[i]; // speed up by rejecting all points that are not in de bounding box @@ -969,7 +1043,7 @@ const Array& TetraMeshSnapshot::properties(int m) const int TetraMeshSnapshot::nearestEntity(Position bfr) const { - return _blocktrees.size() ? cellIndex(bfr) : -1; + return cellIndex(bfr); } //////////////////////////////////////////////////////////////////// @@ -985,37 +1059,16 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator { const TetraMeshSnapshot* _grid{nullptr}; int _mr{-1}; - // true if the path is was ever inside the convex hull - bool wasInside = false; + bool wasInside = false; // true if the path is was ever inside the convex hull int enteringFace = -1; public: MySegmentGenerator(const TetraMeshSnapshot* grid) : _grid(grid) {} - std::ofstream out; - - void startwriting() - { - if (!out.is_open()) out.open("photon.txt"); - } - - void stopwriting() { out.close(); } - - void write(const Vec& exit, double ds, int face) - { - out << "photon=" << _mr << "," << face << "\n"; - out << "ds=" << ds << "\n"; - out << "r=" << r().x() << "," << r().y() << "," << r().z() << "\n"; - out << "exit=" << exit.x() << "," << exit.y() << "," << exit.z() << "\n"; - out << "k=" << k().x() << "," << k().y() << "," << k().z() << "\n"; - } - bool next() override { if (state() == State::Unknown) { - startwriting(); - // try moving the photon packet inside the grid; if this is impossible, return an empty path if (!moveInside(_grid->extent(), _grid->_eps)) return false; @@ -1030,8 +1083,6 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator // if the photon packet started outside the grid, return the corresponding nonzero-length segment; // otherwise fall through to determine the first actual segment if (ds() > 0.) return true; - - write(r(), 0, -1); } // intentionally falls through @@ -1040,18 +1091,14 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator // loop in case no exit point was found (which should happen only rarely) while (true) { - // initialize the smallest nonnegative intersection distance and corresponding index - double sq = DBL_MAX; // very large, but not infinity (so that infinite si values are discarded) - const int NO_INDEX = -99; // meaningless cell index - int mq = NO_INDEX; - - // plucker coords const Plucker ray = Plucker(r(), k()); const Tetra* tetra = _grid->_tetrahedra[_mr]; - int leavingFace = -1; + // temp variable to store plucker products and eventually the barycentric coordinates std::array prods; - if (enteringFace == -1) // this should only be done once + + int leavingFace = -1; + if (enteringFace == -1) // this should only be done once max per ray { for (int face = 0; face < 4; face++) { @@ -1061,74 +1108,40 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator break; } } + // if (leavingFace == -1) + // { + // _grid->log()->warning("no leaving face found!"); + // } } else { - int t0 = (enteringFace + 1) % 4; - int t1 = (enteringFace + 2) % 4; - int t2 = (enteringFace + 3) % 4; - // if face is even we should swap two edges - if (enteringFace % 2 == 0) std::swap(t0, t2); - - int edges[3]; - edges[0] = tetra->getEdge(t0, enteringFace); - edges[1] = tetra->getEdge(t1, enteringFace); - edges[2] = tetra->getEdge(t2, enteringFace); + std::array t = Tetra::clockwiseVertices(enteringFace); // 2 step decision tree - int i = 0, prev_i; - for (int _ = 0; _ < 2; _++) - { - int e = edges[i]; - if (e < 0) - prods[i] = -Plucker::dot(ray, *tetra->_edges[-e]); - else - prods[i] = Plucker::dot(ray, *tetra->_edges[e]); - - // if clockwise move clockwise - prev_i = i; - if (prods[i] < 0) - i = (i + 1) % 3; - else // if counter move counter - i = (i - 1) % 3; - } + prods[0] = tetra->getProd(ray, t[0], enteringFace); + bool clockwise0 = prods[0] < 0; + int i = clockwise0 ? 1 : 2; // if (counter)clockwise move (counter)clockwise + prods[i] = tetra->getProd(ray, t[i], enteringFace); // if 2 clockwise: face=t0 // if 2 counter: face=t0 // if clockwise then counter: face=t2 // if counter then clockwise: face=t1 - // and calculate last inner products - if (prods[0] == prods[i]) - { - leavingFace = t0; - - - - } + if (clockwise0 == (prods[i] < 0)) + leavingFace = t[0]; + else if (clockwise0) + leavingFace = t[2]; else - { - if (prods[0] < 0) - leavingFace = t2; - else - leavingFace = t1; - } - } + leavingFace = t[1]; - Vec exit; - if (leavingFace != -1) - { - for (int i = 0; i < 3; i++) - { - exit += tetra->_vertices[leavingFace._indices[i]] * w[i]; - } - sq = (exit - r()).norm(); - mq = tetra->_neighbors[leavingIndex]; + // get prods (this calculates one prod too many but is much cleaner) + tetra->intersects(prods, ray, leavingFace, true); } // if no exit point was found, advance the current point by a small distance, // recalculate the cell index, and return to the start of the loop - if (mq == NO_INDEX) + if (leavingFace == -1) { propagater(_grid->_eps); _mr = _grid->cellIndex(r()); @@ -1143,10 +1156,28 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator // otherwise set the current point to the exit point and return the path segment else { - propagater(sq + _grid->_eps); - setSegment(_mr, sq); - write(exit, sq, leavingFace); - _mr = mq; + Vec exit = tetra->calcExit(prods, leavingFace); + + double ds = (exit - r()).norm(); + int next_mr = tetra->_neighbors[leavingFace]; + // we could assert if r+exit and r+sq*k are the same + + propagater(ds + _grid->_eps); + setSegment(_mr, ds); + + // set enteringFace if there is a neighboring cell + if (next_mr != -1) + { + auto& neighbors = _grid->_tetrahedra[next_mr]->_neighbors; + enteringFace = + std::distance(neighbors.begin(), std::find(neighbors.begin(), neighbors.end(), _mr)); + } + else + { + enteringFace = -1; + } + // set new cell + _mr = next_mr; // if we're outside the domain, terminate the path after returning this path segment if (_mr < 0) setState(State::Outside); @@ -1174,10 +1205,9 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator else t_z = (_grid->extent().zmax() - rz()) / kz(); - double ds = min(t_x, min(t_y, t_z)); + double ds = min({t_x, t_y, t_z}); propagater(ds + _grid->_eps); - write(r(), ds, -1); - stopwriting(); + setSegment(_mr, ds); return false; } @@ -1185,55 +1215,35 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator if (!wasInside) { const Plucker ray = Plucker(r(), k()); - int enteringfaces = 0; - int entering = -1; - - // std::array w; - // std::array vertices; - // for (int i = 0; i < _grid->_tetrahedra.size(); i++) - // { - // const Tetra* tetra = _grid->_tetrahedra[i]; - // for (int f = 0; f < 4; f++) - // { - // int n = tetra->_neighbors[f]; - // // edge face - // if (n == -1) - // { - // Face face = Face(tetra, f); - // if (face.intersects(w, ray, false)) - // { - // _mr = i; - - // if (enteringfaces++ == 0) - // { - // Vec exit; - // for (int i = 0; i < 3; i++) - // { - // exit += tetra->_vertices[face._indices[i]] * w[i]; - // } - - // double sq = (exit - r()).norm(); - // propagater(sq + _grid->_eps); - // write(exit, sq, f); - // } - // else - // { - // _grid->log()->error("too many entering faces for outside ray"); - // } - // // break; - // } - // } - // } - // } - if (enteringfaces > 0) + std::array barycoords; + for (int i = 0; i < _grid->numTetra; i++) { - wasInside = true; - setState(State::Inside); - return true; + const Tetra* tetra = _grid->_tetrahedra[i]; + for (int face = 0; face < 4; face++) + { + // convex hull face + if (tetra->_neighbors[face] == -1) + { + if (tetra->intersects(barycoords, ray, face, false)) + { + _mr = i; + enteringFace = face; + + Vec exit = tetra->calcExit(barycoords, enteringFace); + + double ds = (exit - r()).norm(); + propagater(ds + _grid->_eps); + // setSegment(-1, ds); // not sure if this is needed + + wasInside = true; + setState(State::Inside); + return true; + } + } + } } } } - stopwriting(); return false; } }; diff --git a/SKIRT/core/TetraMeshSnapshot.hpp b/SKIRT/core/TetraMeshSnapshot.hpp index 5eefcf5e..f5f6f86e 100644 --- a/SKIRT/core/TetraMeshSnapshot.hpp +++ b/SKIRT/core/TetraMeshSnapshot.hpp @@ -8,7 +8,7 @@ #include "Array.hpp" #include "Snapshot.hpp" -#include +#include class PathSegmentGenerator; class SiteListInterface; class SpatialGridPath; @@ -212,18 +212,11 @@ class TetraMeshSnapshot : public Snapshot bool operator==(const Edge& edge) const; - struct HashFunction - { - size_t operator()(const Edge* edge) const; - }; - - struct EqualFunction - { - bool operator()(const Edge* e1, const Edge* e2) const; - }; + // hash function + static size_t hash(const int i1, const int i2); }; - typedef std::unordered_set EdgeSet; + typedef std::unordered_map EdgeMap; class Tetra : public Box { @@ -238,14 +231,14 @@ class TetraMeshSnapshot : public Snapshot public: Tetra(const vector& _cells, int i, int j, int k, int l); - // get the edge using the tetra indices [0, 3] - int getEdge(int t1, int t2) const; + double getProd(const Plucker& ray, int t1, int t2) const; // compute normal for face 3 (in clockwise direction of vertices 012) and dot with vertex 3 double orient(); - void addEdges(EdgeSet& edgeset); + void addEdges(EdgeMap& edgemap); + // used to build the neighbor array // return -1 if no shared face // return 0-3 for opposite vertex of shared face int shareFace(const Tetra* other) const; @@ -258,20 +251,16 @@ class TetraMeshSnapshot : public Snapshot bool inside(const Position& bfr) const; + Vec calcExit(const std::array& barycoords, int face) const; + Position position() const; double volume() const; const Array& properties(); - }; - // temp test - std::vector centers; - std::vector radii; - - /** Private class to hold a node in the internal binary search tree; see the buildTree() - and buildSearch() functions. */ - class Node; + static inline std::array clockwiseVertices(int face); + }; /** Given a list of generating sites (represented as partially initialized Cell objects), this private function builds the Tetra tessellation and stores the @@ -501,13 +490,13 @@ class TetraMeshSnapshot : public Snapshot // data members initialized during configuration Box _extent; // the spatial domain of the mesh double _eps{0.}; // small fraction of extent - bool _foregoTetraMesh{false}; // true if using search tree instead of Tetra tessellation + int numTetra; // data members initialized when processing snapshot input and further completed by BuildMesh() vector _cells; // cell objects, indexed on m vector _tetrahedra; - EdgeSet _edgeset; + EdgeMap _edgemap; // data members initialized when processing snapshot input, but only if a density policy has been set Array _rhov; // density for each cell (not normalized) @@ -519,7 +508,6 @@ class TetraMeshSnapshot : public Snapshot int _nb2{0}; // nb*nb int _nb3{0}; // nb*nb*nb vector> _blocklists; // list of cell indices per block, indexed on i*_nb2+j*_nb+k - vector _blocktrees; // root node of search tree or null for each block, indexed on i*_nb2+j*_nb+k // allow our path segment generator to access our private data members class MySegmentGenerator; From dcd9aa104dd44be84da8233ad6977f8891c402f0 Mon Sep 17 00:00:00 2001 From: arlauwer Date: Tue, 28 Nov 2023 18:59:23 +0100 Subject: [PATCH 11/51] tetgen functional --- SKIRT/CMakeLists.txt | 1 + SKIRT/core/CMakeLists.txt | 4 +- SKIRT/core/TetraMeshSnapshot.cpp | 1067 +- SKIRT/core/TetraMeshSnapshot.hpp | 79 +- SKIRT/tetgen/CMakeLists.txt | 33 + SKIRT/tetgen/predicates.cxx | 4710 ++++ SKIRT/tetgen/tetgen.cxx | 36567 +++++++++++++++++++++++++++++ SKIRT/tetgen/tetgen.h | 3613 +++ 8 files changed, 45540 insertions(+), 534 deletions(-) create mode 100644 SKIRT/tetgen/CMakeLists.txt create mode 100644 SKIRT/tetgen/predicates.cxx create mode 100644 SKIRT/tetgen/tetgen.cxx create mode 100644 SKIRT/tetgen/tetgen.h diff --git a/SKIRT/CMakeLists.txt b/SKIRT/CMakeLists.txt index 639e88d5..59d52eca 100644 --- a/SKIRT/CMakeLists.txt +++ b/SKIRT/CMakeLists.txt @@ -10,6 +10,7 @@ # add all relevant subdirectories; each subdirectory defines a single target add_subdirectory(fitsio) add_subdirectory(voro) +add_subdirectory(tetgen) add_subdirectory(mpi) add_subdirectory(utils) add_subdirectory(core) diff --git a/SKIRT/core/CMakeLists.txt b/SKIRT/core/CMakeLists.txt index 2e728512..c97f59e2 100644 --- a/SKIRT/core/CMakeLists.txt +++ b/SKIRT/core/CMakeLists.txt @@ -22,8 +22,8 @@ target_link_libraries(${TARGET} schema fundamentals) include_directories(../../SMILE/schema ../../SMILE/fundamentals) # add SKIRT library dependencies -target_link_libraries(${TARGET} fitsio voro mpi utils) -include_directories(../fitsio ../voro ../mpi ../utils) +target_link_libraries(${TARGET} fitsio voro mpi utils tetgen) +include_directories(../fitsio ../voro ../mpi ../utils ../tetgen) # adjust C++ compiler flags to our needs include("../../SMILE/build/CompilerFlags.cmake") diff --git a/SKIRT/core/TetraMeshSnapshot.cpp b/SKIRT/core/TetraMeshSnapshot.cpp index 2d95002f..79ad046f 100644 --- a/SKIRT/core/TetraMeshSnapshot.cpp +++ b/SKIRT/core/TetraMeshSnapshot.cpp @@ -20,14 +20,72 @@ #include "Table.hpp" #include "TextInFile.hpp" #include "Units.hpp" +#include "tetgen.h" #include #include #include "container.hh" //////////////////////////////////////////////////////////////////// +namespace +{ + // classes used for serializing/deserializing Voronoi cell geometry when communicating the results of + // grid construction between multiple processes with the ProcessManager::broadcastAllToAll() function + + // decorates a std::vector with functions to write serialized versions of various data types + class SerializedWrite + { + private: + vector& _data; + + public: + SerializedWrite(vector& data) : _data(data) { _data.clear(); } + void write(double v) { _data.push_back(v); } + void write(Vec v) { _data.insert(_data.end(), {v.x(), v.y(), v.z()}); } + void write(Box v) { _data.insert(_data.end(), {v.xmin(), v.ymin(), v.zmin(), v.xmax(), v.ymax(), v.zmax()}); } + void write(const vector& v) + { + _data.push_back(v.size()); + _data.insert(_data.end(), v.begin(), v.end()); + } + }; + + // decorates a std::vector with functions to read serialized versions of various data types + class SerializedRead + { + private: + const double* _data; + const double* _end; + + public: + SerializedRead(const vector& data) : _data(data.data()), _end(data.data() + data.size()) {} + bool empty() { return _data == _end; } + int readInt() { return *_data++; } + void read(double& v) { v = *_data++; } + void read(Vec& v) + { + v.set(*_data, *(_data + 1), *(_data + 2)); + _data += 3; + } + void read(Box& v) + { + v = Box(*_data, *(_data + 1), *(_data + 2), *(_data + 3), *(_data + 4), *(_data + 5)); + _data += 6; + } + void read(vector& v) + { + int n = *_data++; + v.clear(); + v.reserve(n); + for (int i = 0; i != n; ++i) v.push_back(*_data++); + } + }; +} + +//////////////////////////////////////////////////////////////////// + // class to hold the information about a Tetra cell that is relevant for calculating paths and densities -class TetraMeshSnapshot::Cell +class TetraMeshSnapshot::Site { public: Vec _r; // site position @@ -36,11 +94,11 @@ class TetraMeshSnapshot::Cell public: // constructor stores the specified site position; the other data members are set to zero or empty - Cell(Vec r) : _r(r) {} + Site(Vec r) : _r(r) {} // constructor derives the site position from the first three property values and stores the user properties; // the other data members are set to zero or empty - Cell(const Array& prop) : _r(prop[0], prop[1], prop[2]), _properties{prop} {} // WIP + Site(const Array& prop) : _r(prop[0], prop[1], prop[2]), _properties{prop} {} // WIP // adjusts the site position with the specified offset void relax(double cx, double cy, double cz) { _r += Vec(cx, cy, cz); } @@ -65,6 +123,20 @@ class TetraMeshSnapshot::Cell // returns the cell/site user properties, if any const Array& properties() { return _properties; } + + // writes the Voronoi cell geometry to the serialized data buffer, preceded by the specified cell index, + // if the cell geometry has been calculated for this cell; otherwise does nothing + void writeGeometryIfPresent(SerializedWrite& wdata, int m) + { + if (!_neighbors.empty()) + { + wdata.write(m); + wdata.write(_neighbors); + } + } + + // reads the Voronoi cell geometry from the serialized data buffer + void readGeometry(SerializedRead& rdata) { rdata.read(_neighbors); } }; //////////////////////////////////////////////////////////////////// @@ -84,39 +156,14 @@ inline double TetraMeshSnapshot::Plucker::dot(const Plucker& a, const Plucker& b //////////////////////////////////////////////////////////////////// -TetraMeshSnapshot::Edge::Edge(const int i1, const int i2, const Vec& v1, const Vec& v2) - : Plucker(v1, v2 - v1), i1(i1), i2(i2) -{} +TetraMeshSnapshot::Edge::Edge(int i1, int i2, const Vec* v1, const Vec* v2) : Plucker(*v1, *v2 - *v1), i1(i1), i2(i2) {} //////////////////////////////////////////////////////////////////// -bool TetraMeshSnapshot::Edge::operator==(const Edge& edge) const +TetraMeshSnapshot::Tetra::Tetra(const std::array& vertices, const std::array& indices, + const std::array& neighbors, const std::array& edges) + : _vertices(vertices), _indices(indices), _neighbors(neighbors), _edges(edges) { - return (i1 == edge.i1 && i2 == edge.i2) || (i1 == edge.i2 && i2 == edge.i1); -} - -//////////////////////////////////////////////////////////////////// - -size_t TetraMeshSnapshot::Edge::hash(const int i1, const int i2) -{ - size_t max = std::max(i1, i2); - size_t min = std::min(i1, i2); - return (max << 32) + min; -} - -//////////////////////////////////////////////////////////////////// - -TetraMeshSnapshot::Tetra::Tetra(const vector& _cells, int i, int j, int k, int l) -{ - _vertices[0] = &_cells[i]->_r; - _vertices[1] = &_cells[j]->_r; - _vertices[2] = &_cells[k]->_r; - _vertices[3] = &_cells[l]->_r; - _vertex_indices[0] = i; - _vertex_indices[1] = j; - _vertex_indices[2] = k; - _vertex_indices[3] = l; - double xmin = DBL_MAX; double ymin = DBL_MAX; double zmin = DBL_MAX; @@ -137,106 +184,33 @@ TetraMeshSnapshot::Tetra::Tetra(const vector& _cells, int i, int j, int k _volume = 1 / 6. * abs(Vec::dot(Vec::cross(*_vertices[1] - *_vertices[0], *_vertices[2] - *_vertices[0]), *_vertices[3] - *_vertices[0])); -} - -//////////////////////////////////////////////////////////////////// - -double TetraMeshSnapshot::Tetra::getProd(const Plucker& ray, int t1, int t2) const -{ - double prod = 1; - if (t1 > t2) - { - std::swap(t1, t2); - prod *= -1; - } - int e = (t1 == 0) ? t2 - 1 : t1 + t2; - Edge* edge = _edges[e]; - if (_vertex_indices[t1] != edge->i1) // not the same order - { - prod *= -1; - } - return prod * Plucker::dot(ray, *edge); -} -//////////////////////////////////////////////////////////////////// - -double TetraMeshSnapshot::Tetra::orient() -{ const Vec e01 = *_vertices[1] - *_vertices[0]; const Vec e02 = *_vertices[2] - *_vertices[0]; const Vec e03 = *_vertices[3] - *_vertices[0]; // this convention makes edges go clockwise around leaving rays from inside the tetrahedron - // so their plucker products are all negative if the ray leaves + // so their plucker products are all positive if the ray leaves double orientation = Vec::dot(Vec::cross(e01, e02), e03); - if (orientation > 0) + if (orientation < 0) { + std::cout << "ORIENTATION SWITCHED!!!!!!!!!" << std::endl; // swap last 2, this means first 2 indices can be ordered i < j std::swap(_vertices[2], _vertices[3]); - std::swap(_vertex_indices[2], _vertex_indices[3]); - } - return orientation; -} - -//////////////////////////////////////////////////////////////////// - -void TetraMeshSnapshot::Tetra::addEdges(EdgeMap& edgemap) -{ - - for (int t1 = 0; t1 < 3; t1++) - { - for (int t2 = t1 + 1; t2 < 4; t2++) - { - // _edgemap - int i1 = _vertex_indices[t1]; - int i2 = _vertex_indices[t2]; - size_t key = Edge::hash(i1, i2); - auto it = edgemap.find(key); - - Edge* edge; - if (it == edgemap.end()) // not in map - { - edge = new Edge(i1, i2, *_vertices[t1], *_vertices[t2]); - // add this to the map - edgemap[key] = edge; - } - else // already in map - { - edge = it->second; - } - - // edges are labelled: - // 0 1 2 3 4 5 - // 01 02 03 12 13 23 - _edges[(t1 == 0) ? t2 - 1 : t1 + t2] = edge; - } + std::swap(_neighbors[2], _neighbors[3]); + std::swap(_indices[2], _indices[3]); } } //////////////////////////////////////////////////////////////////// -int TetraMeshSnapshot::Tetra::shareFace(const Tetra* other) const +double TetraMeshSnapshot::Tetra::getProd(const Plucker& ray, int t1, int t2) const { - int equal_vert = 0; - int opposite = 0 + 1 + 2 + 3; - - for (int i = 0; i < 4; i++) - { - int vi = _vertex_indices[i]; - - for (int j = 0; j < 4; j++) - { - int vj = other->_vertex_indices[j]; + int e = (std::min(t1, t2) == 0) ? std::max(t1, t2) - 1 : t1 + t2; + Edge* edge = _edges[e]; - if (vi == vj) - { - equal_vert++; - opposite -= i; - break; - } - } - } - if (equal_vert == 3) return opposite; - return -1; + // not the same order -> *-1 + // beginning of t1 == beginning of edge (= same order) + return (_indices[t1] == edge->i1 ? 1 : -1) * Plucker::dot(ray, *edge); } //////////////////////////////////////////////////////////////////// @@ -244,7 +218,7 @@ int TetraMeshSnapshot::Tetra::shareFace(const Tetra* other) const bool TetraMeshSnapshot::Tetra::intersects(std::array& barycoords, const Plucker& ray, int face, bool leaving) const { - std::array t = clockwiseVertices(face); + std::array t = counterclockwiseVertices(face); double sum = 0; for (int i = 0; i < 3; i++) @@ -252,7 +226,7 @@ bool TetraMeshSnapshot::Tetra::intersects(std::array& barycoords, con // edges: 12, 20, 01 // verts: 0, 1, 2 double prod = getProd(ray, t[(i + 1) % 3], t[(i + 2) % 3]); - if (leaving != (prod < 0)) return false; + if (leaving != (prod >= 0)) return false; // change this so for both leavig and entering prod=0 works barycoords[i] = prod; sum += prod; } @@ -263,74 +237,58 @@ bool TetraMeshSnapshot::Tetra::intersects(std::array& barycoords, con //////////////////////////////////////////////////////////////////// -bool TetraMeshSnapshot::Tetra::equals(const Tetra* other) const +bool TetraMeshSnapshot::Tetra::inside(const Position& bfr) const { - for (int v : _vertex_indices) - { - bool match = false; - for (int u : other->_vertex_indices) - { - if (u == v) - { - match = true; - break; - } - } - if (!match) return false; - } - return true; -} + if (!Box::contains(bfr)) return false; + + /* + face: normals for which the other vertex has a positive dot product with + 3:*02 x 01*| 10 x 12 | 21 x 20 + 2: 13 x 10 |*01 x 03*| 30 x 31 + 1: 20 x 23 | 32 x 30 |*03 x 02* + 0: 31 x 32 | 23 x 21 |*12 x 13* // last one doesn't matter + */ + + // optimized version (probably not that much better) + Vec e0p = bfr - *_vertices[0]; + Vec e02 = *_vertices[2] - *_vertices[0]; + Vec e01 = *_vertices[1] - *_vertices[0]; + if (Vec::dot(Vec::cross(e02, e01), e0p) > 0) // 02 x 01 + return false; -//////////////////////////////////////////////////////////////////// + Vec e03 = *_vertices[3] - *_vertices[0]; + if (Vec::dot(Vec::cross(e01, e03), e0p) > 0) // 01 x 03 + return false; -bool TetraMeshSnapshot::Tetra::SameSide(const Vec& v0, const Vec& v1, const Vec& v2, const Vec& v3, - const Vec& pos) const -{ - Vec normal = Vec::cross(v1 - v0, v2 - v0); - double dotV4 = Vec::dot(normal, v3 - v0); - double dotP = Vec::dot(normal, pos - v0); - return (dotV4 > 0) == (dotP > 0); -} + if (Vec::dot(Vec::cross(e03, e02), e0p) > 0) // 03 x 02 + return false; -//////////////////////////////////////////////////////////////////// + Vec e1p = bfr - *_vertices[1]; + Vec e12 = *_vertices[2] - *_vertices[1]; + Vec e13 = *_vertices[3] - *_vertices[1]; + return Vec::dot(Vec::cross(e12, e13), e1p) < 0; // 12 x 13 -bool TetraMeshSnapshot::Tetra::inside(const Position& bfr) const -{ - // std::array bary = getBary(_vertices); - // double sum = 0.; - // for (int i = 0; i < 3; i++) + // checks 3 edges too many but very simple + // for (int face = 0; face < 4; face++) // { - // if (bary[i] < 0.) return false; - // sum += bary[i]; + // std::array t = clockwiseVertices(face); + // Vec& v0 = *_vertices[t[0]]; + // Vec& clock = *_vertices[t[1]]; + // Vec& counter = *_vertices[t[2]]; + // Vec normal = Vec::cross(counter - v0, clock - v0); + // if (Vec::dot(normal, bfr - v0) < 0) // is pos on the same side as v3 + // { + // return false; + // } // } - // return 1 - sum >= 0; - - // very poor implementation - const Vec& v0 = *_vertices[0]; - const Vec& v1 = *_vertices[1]; - const Vec& v2 = *_vertices[2]; - const Vec& v3 = *_vertices[3]; - return SameSide(v0, v1, v2, v3, bfr) && SameSide(v1, v2, v3, v0, bfr) && SameSide(v2, v3, v0, v1, bfr) - && SameSide(v3, v0, v1, v2, bfr); - - // Vec AB = *_vertices[1] - *_vertices[0]; - // Vec AC = *_vertices[2] - *_vertices[0]; - // Vec AD = *_vertices[3] - *_vertices[0]; - // Vec AP = bfr - *_vertices[0]; - - // double volume_ABC = Vec::dot(Vec::cross(AB, AC), AD); - // double volume_PBC = Vec::dot(Vec::cross(AP, AC), AD); - // double volume_PAC = Vec::dot(Vec::cross(AB, AP), AD); - // double volume_PAB = Vec::dot(Vec::cross(AB, AC), AP); - - // return (volume_ABC > 0) == (volume_PBC > 0) == (volume_PAC > 0) == (volume_PAB > 0); + // return true; } //////////////////////////////////////////////////////////////////// Vec TetraMeshSnapshot::Tetra::calcExit(const std::array& barycoords, int face) const { - std::array t = Tetra::clockwiseVertices(face); + std::array t = Tetra::counterclockwiseVertices(face); Vec exit; for (int i = 0; i < 3; i++) exit += *_vertices[t[i]] * barycoords[i]; return exit; @@ -362,7 +320,7 @@ const Array& TetraMeshSnapshot::Tetra::properties() //////////////////////////////////////////////////////////////////// -std::array TetraMeshSnapshot::Tetra::clockwiseVertices(int face) +std::array TetraMeshSnapshot::Tetra::counterclockwiseVertices(int face) { std::array t = {(face + 1) % 4, (face + 2) % 4, (face + 3) % 4}; // if face is even we should swap two edges @@ -374,60 +332,167 @@ std::array TetraMeshSnapshot::Tetra::clockwiseVertices(int face) namespace { - template bool invec(const vector& vec, const T& e) + bool lessthan(Vec p1, Vec p2, int axis) { - return std::find(vec.begin(), vec.end(), e) != vec.end(); + switch (axis) + { + case 0: // split on x + if (p1.x() < p2.x()) return true; + if (p1.x() > p2.x()) return false; + if (p1.y() < p2.y()) return true; + if (p1.y() > p2.y()) return false; + if (p1.z() < p2.z()) return true; + return false; + case 1: // split on y + if (p1.y() < p2.y()) return true; + if (p1.y() > p2.y()) return false; + if (p1.z() < p2.z()) return true; + if (p1.z() > p2.z()) return false; + if (p1.x() < p2.x()) return true; + return false; + case 2: // split on z + if (p1.z() < p2.z()) return true; + if (p1.z() > p2.z()) return false; + if (p1.x() < p2.x()) return true; + if (p1.x() > p2.x()) return false; + if (p1.y() < p2.y()) return true; + return false; + default: // this should never happen + return false; + } } - double det4(double x0, double y0, double z0, double x1, double y1, double z1, double x2, double y2, double z2, - double x3, double y3, double z3) + void addFacet(tetgenio::facet* f, std::array vertices) { - return x0 * y1 * z2 - x0 * y1 * z3 - x0 * y2 * z1 + x0 * y2 * z3 + x0 * y3 * z1 - x0 * y3 * z2 - x1 * y0 * z2 - + x1 * y0 * z3 + x1 * y2 * z0 - x1 * y2 * z3 - x1 * y3 * z0 + x1 * y3 * z2 + x2 * y0 * z1 - x2 * y0 * z3 - - x2 * y1 * z0 + x2 * y1 * z3 + x2 * y3 * z0 - x2 * y3 * z1 - x3 * y0 * z1 + x3 * y0 * z2 + x3 * y1 * z0 - - x3 * y1 * z2 - x3 * y2 * z0 + x3 * y2 * z1; + f->numberofpolygons = 1; + f->polygonlist = new tetgenio::polygon[f->numberofpolygons]; + f->numberofholes = 0; + f->holelist = NULL; + tetgenio::polygon* p = &f->polygonlist[0]; + p->numberofvertices = 4; + p->vertexlist = new int[p->numberofvertices]; + p->vertexlist[0] = vertices[0]; + p->vertexlist[1] = vertices[1]; + p->vertexlist[2] = vertices[2]; + p->vertexlist[3] = vertices[3]; } +} - // std::array getBary(const std::array& vertices) - // { - // double T[3][3]; - // double T_inv[3][3]; +//////////////////////////////////////////////////////////////////// - // for (int i = 0; i < 3; i++) - // { - // T[0][i] = vertices[i]->x() - vertices[3]->x(); - // T[1][i] = vertices[i]->y() - vertices[3]->y(); - // T[2][i] = vertices[i]->z() - vertices[3]->z(); - // } +class TetraMeshSnapshot::Node +{ +private: + int _m; // index in _cells to the site defining the split at this node + int _axis; // split axis for this node (0,1,2) + Node* _up; // ptr to the parent node + Node* _left; // ptr to the left child node + Node* _right; // ptr to the right child node + std::vector tetra; - // double det = T[0][0] * (T[1][1] * T[2][2] - T[2][1] * T[1][2]) - // - T[0][1] * (T[1][0] * T[2][2] - T[1][2] * T[2][0]) - // + T[0][2] * (T[1][0] * T[2][1] - T[1][1] * T[2][0]); - - // double invdet = 1 / det; - - // T_inv[0][0] = (T[1][1] * T[2][2] - T[2][1] * T[1][2]) * invdet; - // T_inv[0][1] = (T[0][2] * T[2][1] - T[0][1] * T[2][2]) * invdet; - // T_inv[0][2] = (T[0][1] * T[1][2] - T[0][2] * T[1][1]) * invdet; - // T_inv[1][0] = (T[1][2] * T[2][0] - T[1][0] * T[2][2]) * invdet; - // T_inv[1][1] = (T[0][0] * T[2][2] - T[0][2] * T[2][0]) * invdet; - // T_inv[1][2] = (T[1][0] * T[0][2] - T[0][0] * T[1][2]) * invdet; - // T_inv[2][0] = (T[1][0] * T[2][1] - T[2][0] * T[1][1]) * invdet; - // T_inv[2][1] = (T[2][0] * T[0][1] - T[0][0] * T[2][1]) * invdet; - // T_inv[2][2] = (T[0][0] * T[1][1] - T[1][0] * T[0][1]) * invdet; - - // std::array bary; - // double vec[3] = {vertices[3]->x(), vertices[3]->y(), vertices[3]->z()}; - // for (int i = 0; i < 3; i++) - // { - // for (int j = 0; j < 3; j++) - // { - // bary[i] += T_inv[i][j] * vec[j]; - // } - // } - // return bary; - // } -} + // returns the square of its argument + static double sqr(double x) { return x * x; } + +public: + // constructor stores the specified site index and child pointers (which may be null) + Node(int m, int depth, Node* left, Node* right) : _m(m), _axis(depth % 3), _up(0), _left(left), _right(right) + { + if (_left) _left->setParent(this); + if (_right) _right->setParent(this); + } + + // destructor destroys the children + ~Node() + { + delete _left; + delete _right; + } + + // sets parent pointer (called from parent's constructor) + void setParent(Node* up) { _up = up; } + + // returns the corresponding data member + int m() const { return _m; } + Node* up() const { return _up; } + Node* left() const { return _left; } + Node* right() const { return _right; } + + // returns the apropriate child for the specified query point + Node* child(Vec bfr, const vector& sites) const + { + return lessthan(bfr, sites[_m]->position(), _axis) ? _left : _right; + } + + // returns the other child than the one that would be apropriate for the specified query point + Node* otherChild(Vec bfr, const vector& sites) const + { + return lessthan(bfr, sites[_m]->position(), _axis) ? _right : _left; + } + + // returns the squared distance from the query point to the split plane + double squaredDistanceToSplitPlane(Vec bfr, const vector& sites) const + { + switch (_axis) + { + case 0: // split on x + return sqr(sites[_m]->position().x() - bfr.x()); + case 1: // split on y + return sqr(sites[_m]->position().y() - bfr.y()); + case 2: // split on z + return sqr(sites[_m]->position().z() - bfr.z()); + default: // this should never happen + return 0; + } + } + + // returns the node in this subtree that represents the site nearest to the query point + Node* nearest(Vec bfr, const vector& sites) + { + // recursively descend the tree until a leaf node is reached, going left or right depending on + // whether the specified point is less than or greater than the current node in the split dimension + Node* current = this; + while (Node* child = current->child(bfr, sites)) current = child; + + // unwind the recursion, looking for the nearest node while climbing up + Node* best = current; + double bestSD = sites[best->m()]->squaredDistanceTo(bfr); + while (true) + { + // if the current node is closer than the current best, then it becomes the current best + double currentSD = sites[current->m()]->squaredDistanceTo(bfr); + if (currentSD < bestSD) + { + best = current; + bestSD = currentSD; + } + + // if there could be points on the other side of the splitting plane for the current node + // that are closer to the search point than the current best, then ... + double splitSD = current->squaredDistanceToSplitPlane(bfr, sites); + if (splitSD < bestSD) + { + // move down the other branch of the tree from the current node looking for closer points, + // following the same recursive process as the entire search + Node* other = current->otherChild(bfr, sites); + if (other) + { + Node* otherBest = other->nearest(bfr, sites); + double otherBestSD = sites[otherBest->m()]->squaredDistanceTo(bfr); + if (otherBestSD < bestSD) + { + best = otherBest; + bestSD = otherBestSD; + } + } + } + + // move up to the parent until we meet the top node + if (current == this) break; + current = current->up(); + } + return best; + } +}; //////////////////////////////////////////////////////////////////// @@ -437,8 +502,9 @@ TetraMeshSnapshot::TetraMeshSnapshot() {} TetraMeshSnapshot::~TetraMeshSnapshot() { - for (auto cell : _cells) delete cell; + for (auto cell : _sites) delete cell; for (auto tetra : _tetrahedra) delete tetra; + for (auto tree : _blocktrees) delete tree; } //////////////////////////////////////////////////////////////////// @@ -459,7 +525,7 @@ TetraMeshSnapshot::TetraMeshSnapshot(const SimulationItem* item, const Box& exte in.addColumn("position y", "length", "pc"); in.addColumn("position z", "length", "pc"); Array coords; - while (in.readRow(coords)) _cells.push_back(new Cell(Vec(coords[0], coords[1], coords[2]))); + while (in.readRow(coords)) _sites.push_back(new Site(Vec(coords[0], coords[1], coords[2]))); in.close(); // calculate the Tetra cells @@ -475,8 +541,8 @@ TetraMeshSnapshot::TetraMeshSnapshot(const SimulationItem* item, const Box& exte { // prepare the data int n = sli->numSites(); - _cells.resize(n); - for (int m = 0; m != n; ++m) _cells[m] = new Cell(sli->sitePosition(m)); + _sites.resize(n); + for (int m = 0; m != n; ++m) _sites[m] = new Site(sli->sitePosition(m)); // calculate the Tetra cells setContext(item); @@ -492,8 +558,8 @@ TetraMeshSnapshot::TetraMeshSnapshot(const SimulationItem* item, const Box& exte { // prepare the data int n = sites.size(); - _cells.resize(n); - for (int m = 0; m != n; ++m) _cells[m] = new Cell(sites[m]); + _sites.resize(n); + for (int m = 0; m != n; ++m) _sites[m] = new Site(sites[m]); // calculate the Tetra cells setContext(item); @@ -535,260 +601,120 @@ namespace void TetraMeshSnapshot::buildMesh(bool relax) { - const double A = extent().rmax().norm() * 1000; // WIP + tetgenio in, out; + tetgenio::facet* f; + tetgenio::polygon* p; - int numCells = _cells.size(); + in.firstnumber = 0; + in.numberofpoints = 8; + in.pointlist = new REAL[in.numberofpoints * 3]; - // remove sites that lie outside of the domain - int numOutside = 0; - for (int m = 0; m != numCells; ++m) - { - if (!_extent.contains(_cells[m]->position())) - { - delete _cells[m]; - _cells[m] = 0; - numOutside++; - } - } - if (numOutside) numCells = eraseNullPointers(_cells); + // _extent = Box(-1, -1, -1, 1, 1, 1); - // sort sites in order of increasing x coordinate to accelerate search for nearby sites - std::sort(_cells.begin(), _cells.end(), [](Cell* c1, Cell* c2) { return c1->x() < c2->x(); }); + // bottom half (zmin) + in.pointlist[0] = _extent.xmin(); + in.pointlist[1] = _extent.ymin(); + in.pointlist[2] = _extent.zmin(); - // remove sites that lie too nearby another site - int numNearby = 0; - for (int m = 0; m != numCells; ++m) - { - for (int j = m + 1; j != numCells && _cells[j]->x() - _cells[m]->x() < _eps; ++j) - { - if ((_cells[j]->position() - _cells[m]->position()).norm2() < _eps * _eps) - { - delete _cells[m]; - _cells[m] = 0; - numNearby++; - break; - } - } - } - if (numNearby) numCells = eraseNullPointers(_cells); - - // log the number of sites - if (!numOutside && !numNearby) - { - log()->info(" Number of sites: " + std::to_string(numCells)); - } - else - { - if (numOutside) log()->info(" Number of sites outside domain: " + std::to_string(numOutside)); - if (numNearby) log()->info(" Number of sites too nearby others: " + std::to_string(numNearby)); - log()->info(" Number of sites retained: " + std::to_string(numCells)); - } - - // abort if there are no cells to calculate - if (numCells <= 0) return; + in.pointlist[3] = _extent.xmax(); + in.pointlist[4] = _extent.xmin(); + in.pointlist[5] = _extent.zmin(); - // calculate number of blocks in each direction based on number of cells - _nb = max(3, min(250, static_cast(cbrt(numCells)))); - _nb2 = _nb * _nb; - _nb3 = _nb * _nb * _nb; + in.pointlist[6] = _extent.xmax(); + in.pointlist[7] = _extent.xmax(); + in.pointlist[8] = _extent.zmin(); - // ========= RELAXATION ========= + in.pointlist[9] = _extent.xmin(); + in.pointlist[10] = _extent.ymax(); + in.pointlist[11] = _extent.zmin(); - // if requested, perform a single relaxation step - if (relax) + // top half (zmax) + for (int i = 0; i < 4; i++) { - // table to hold the calculate relaxation offset for each site - // (initialized to zero so we can communicate the result between parallel processes using sumAll) - Table<2> offsets(numCells, 3); - - // add the retained original sites to a temporary Voronoi container, using the cell index m as ID - voro::container vcon(_extent.xmin(), _extent.xmax(), _extent.ymin(), _extent.ymax(), _extent.zmin(), - _extent.zmax(), _nb, _nb, _nb, false, false, false, 16); - for (int m = 0; m != numCells; ++m) - { - Vec r = _cells[m]->position(); - vcon.put(m, r.x(), r.y(), r.z()); - } - - // compute the cell in the Voronoi tesselation corresponding to each site - // and store the cell's centroid (relative to the site position) as the relaxation offset - log()->info("Relaxing Voronoi tessellation with " + std::to_string(numCells) + " cells"); - log()->infoSetElapsed(numCells); - auto parallel = log()->find()->parallelDistributed(); - parallel->call(numCells, [this, &vcon, &offsets](size_t firstIndex, size_t numIndices) { - // allocate a separate cell calculator for each thread to avoid conflicts - voro::voro_compute vcompute(vcon, _nb, _nb, _nb); - // allocate space for the resulting cell info - voro::voronoicell vcell; - - // loop over all cells and work on the ones that have a particle index in our dedicated range - // (we cannot access cells in the container based on cell index m without building an extra data structure) - int numDone = 0; - voro::c_loop_all vloop(vcon); - if (vloop.start()) do - { - size_t m = vloop.pid(); - if (m >= firstIndex && m < firstIndex + numIndices) - { - // compute the cell and store its centroid as relaxation offset - bool ok = vcompute.compute_cell(vcell, vloop.ijk, vloop.q, vloop.i, vloop.j, vloop.k); - if (ok) vcell.centroid(offsets(m, 0), offsets(m, 1), offsets(m, 2)); + in.pointlist[12 + i * 3 + 0] = in.pointlist[i * 3 + 0]; + in.pointlist[12 + i * 3 + 1] = in.pointlist[i * 3 + 1]; + in.pointlist[12 + i * 3 + 2] = _extent.zmax(); + } - // log message if the minimum time has elapsed - numDone = (numDone + 1) % logProgressChunkSize; - if (numDone == 0) log()->infoIfElapsed("Computed Voronoi cells: ", logProgressChunkSize); - } - } while (vloop.inc()); - if (numDone > 0) log()->infoIfElapsed("Computed Voronoi cells: ", numDone); - }); + in.numberoffacets = 6; + in.facetlist = new tetgenio::facet[in.numberoffacets]; + addFacet(&in.facetlist[0], {0, 1, 2, 3}); // Facet 1. bottom + addFacet(&in.facetlist[1], {4, 5, 6, 7}); // Facet 2. top + addFacet(&in.facetlist[2], {0, 4, 5, 1}); // Facet 3. front + addFacet(&in.facetlist[3], {1, 5, 6, 2}); // Facet 4. right + addFacet(&in.facetlist[4], {2, 6, 7, 3}); // Facet 5. back + addFacet(&in.facetlist[5], {3, 7, 4, 0}); // Facet 6. left + + tetgenbehavior behavior; + behavior.plc = 1; // -p PLC + behavior.quality = 1; // -q quality mesh + behavior.neighout = 2; // -nn neighbors and edges? + behavior.zeroindex = 1; // -z zero index + behavior.edgesout = 1; // -e edges + + // parameters + behavior.minratio = 2.0; // -q quality + behavior.maxvolume = 0.9; // -a max volume + // behavior.mindihedral = 5.0; // -q/ minimal angle + + tetrahedralize(&behavior, &in, &out); + numTetra = out.numberoftetrahedra; + numEdges = out.numberofedges; + numVertices = out.numberofpoints; + + _vertices.resize(numVertices); + for (int i = 0; i < numVertices; i++) + { + double x = out.pointlist[3 * i + 0]; + double y = out.pointlist[3 * i + 1]; + double z = out.pointlist[3 * i + 2]; - // communicate the calculated offsets between parallel processes, if needed, and apply them to the cells - ProcessManager::sumToAll(offsets.data()); - for (int m = 0; m != numCells; ++m) _cells[m]->relax(offsets(m, 0), offsets(m, 1), offsets(m, 2)); + _vertices[i] = new Vec(x, y, z); } - // ========= FINAL GRID ========= - - // add the final sites to a temporary Tetra container, using the cell index m as ID - voro::container vcon(-A, A, -A, A, -A, A, _nb, _nb, _nb, false, false, false, 16); - for (int m = 0; m != numCells; ++m) + _edges.resize(numEdges); + for (int i = 0; i < numEdges; i++) { - Vec r = _cells[m]->position(); - vcon.put(m, r.x(), r.y(), r.z()); + int v1 = out.edgelist[2 * i]; + int v2 = out.edgelist[2 * i + 1]; + _edges[i] = new Edge(v1, v2, _vertices[v1], _vertices[v2]); } - // for each site: - // - compute the corresponding cell in the Tetra tesselation - // - extract and copy the relevant information to the cell object with the corresponding index in our vector - log()->info("Constructing intermediate Voronoi tessellation with " + std::to_string(numCells) + " cells"); - log()->infoSetElapsed(numCells); - // allocate a separate cell calculator for each thread to avoid conflicts - voro::voro_compute vcompute(vcon, _nb, _nb, _nb); - // allocate space for the resulting cell info - voro::voronoicell_neighbor vcell; - std::vector vcells; - - // loop over all cells and work on the ones that have a particle index in our dedicated range - // (we cannot access cells in the container based on cell index m without building an extra data structure) - int numDone = 0; - voro::c_loop_all vloop(vcon); - if (vloop.start()) do - { - size_t m = vloop.pid(); - // compute the cell and copy all relevant information to the cell object that will stay around - bool ok = vcompute.compute_cell(vcell, vloop.ijk, vloop.q, vloop.i, vloop.j, vloop.k); - if (ok) _cells[m]->init(vcell); - - // log message if the minimum time has elapsed - numDone = (numDone + 1) % logProgressChunkSize; - if (numDone == 0) log()->infoIfElapsed("Computed Tetra cells: ", logProgressChunkSize); - } while (vloop.inc()); - if (numDone > 0) log()->infoIfElapsed("Computed Tetra cells: ", numDone); - - for (int i = 0; i < numCells; i++) + _tetrahedra.resize(numTetra); + for (int i = 0; i < numTetra; i++) { - Cell* c1 = _cells[i]; - - for (int j : c1->neighbors()) + std::array vertices; + std::array indices; + std::array neighbors; + std::array edges; + for (int c = 0; c < 4; c++) { - // first 2 indices can always be ordered i < j - // last 2 indices can be swapped if orientation is not correct - if (j < 0 || j > i) continue; - - Cell* c2 = _cells[j]; - - for (int k : c2->neighbors()) - { - if (k < 0 || k == i) continue; - - // mutual neighbours - if (!invec(c1->neighbors(), k)) continue; - - for (int l : _cells[k]->neighbors()) - { - if (l < 0 || l == j || l == i) continue; - - // mutual neighbours of both c1 and c2 - if (!invec(c1->neighbors(), l) || !invec(c2->neighbors(), l)) continue; - - // no duplicates in different order // probably use set - Tetra* tetra = new Tetra(_cells, i, j, k, l); - if (inTetrahedra(tetra)) continue; - - // check if tetrahedron is Delaunay - Vec v0 = _cells[i]->_r; - Vec v1 = _cells[j]->_r; - Vec v2 = _cells[k]->_r; - Vec v3 = _cells[l]->_r; - double x0 = v0.x(), y0 = v0.y(), z0 = v0.z(); - double x1 = v1.x(), y1 = v1.y(), z1 = v1.z(); - double x2 = v2.x(), y2 = v2.y(), z2 = v2.z(); - double x3 = v3.x(), y3 = v3.y(), z3 = v3.z(); - - double r0 = v0.norm2(); - double r1 = v1.norm2(); - double r2 = v2.norm2(); - double r3 = v3.norm2(); - - double a = det4(x0, y0, z0, x1, y1, z1, x2, y2, z2, x3, y3, z3); - double Dx = det4(r0, y0, z0, r1, y1, z1, r2, y2, z2, r3, y3, z3); - double Dy = -det4(r0, x0, z0, r1, x1, z1, r2, x2, z2, r3, x3, z3); - double Dz = det4(r0, x0, y0, r1, x1, y1, r2, x2, y2, r3, x3, y3); - - Vec center(Dx / (2 * a), Dy / (2 * a), Dz / (2 * a)); - double R = (center - v0).norm2(); - - bool delaunay = true; - for (int v : tetra->_vertex_indices) - { - Cell* c = _cells[v]; - for (int n : c->neighbors()) - { - if (n < 0 || n == i || n == j || n == k || n == l) continue; - double r = (center - _cells[n]->_r).norm2(); - if (r < R) - { - delaunay = false; - goto end_delaunay; - } - } - } - end_delaunay: - if (!delaunay) continue; - - // orient tetrahedron in the same consistent way - tetra->orient(); - - //add edges - tetra->addEdges(_edgemap); - - _tetrahedra.push_back(tetra); - } - } + indices[c] = out.tetrahedronlist[4 * i + c]; + vertices[c] = _vertices[indices[c]]; + neighbors[c] = out.neighborlist[4 * i + c]; } - } - _tetrahedra.shrink_to_fit(); - numTetra = _tetrahedra.size(); - - // find neighbors brute force - for (size_t i = 0; i < _tetrahedra.size(); i++) - { - Tetra* tetra = _tetrahedra[i]; - - for (size_t j = 0; j < _tetrahedra.size(); j++) + for (int e = 0; e < 6; e++) { - if (i == j) continue; + int ei = out.tet2edgelist[6 * i + e]; - const Tetra* other = _tetrahedra[j]; + Edge* edge = _edges[ei]; + auto t1 = std::find(indices.begin(), indices.end(), edge->i1) - indices.begin(); + auto t2 = std::find(indices.begin(), indices.end(), edge->i2) - indices.begin(); - int shared = tetra->shareFace(other); - if (shared != -1) tetra->_neighbors[shared] = j; + // tetgen edge order: 23 03 01 12 13 02 + // static constexpr int tetgen_order[12] = {2, 3, 0, 3, 0, 1, 1, 2, 1, 3, 0, 2}; + // int t1 = tetgen_order[2 * e]; // 2 0 0 1 1 0 + // int t2 = tetgen_order[2 * e + 1]; // 3 3 1 2 3 2 + + if (t1 > t2) std::swap(t1, t2); + edges[(t1 == 0) ? t2 - 1 : t1 + t2] = _edges[ei]; } + _tetrahedra[i] = new Tetra(vertices, indices, neighbors, edges); } - log()->info("Done computing Delaunay tetrahedralization with " + std::to_string(_tetrahedra.size()) + " cells"); - log()->info("number of edges: " + std::to_string(_edgemap.size())); - log()->info("bucket count for edgeset: " + std::to_string(_edgemap.bucket_count())); + log()->info("number of vertices " + std::to_string(numVertices)); + log()->info("number of edges " + std::to_string(numEdges)); + log()->info("number of tetrahedra " + std::to_string(numTetra)); } //////////////////////////////////////////////////////////////////// @@ -859,51 +785,138 @@ void TetraMeshSnapshot::calculateDensityAndMass() //////////////////////////////////////////////////////////////////// -bool TetraMeshSnapshot::inTetrahedra(const Tetra* tetra) const +TetraMeshSnapshot::Node* TetraMeshSnapshot::buildTree(vector::iterator first, vector::iterator last, + int depth) const { - for (const Tetra* t : _tetrahedra) + auto length = last - first; + if (length > 0) { - if (tetra->equals(t)) return true; + auto median = length >> 1; + std::nth_element(first, first + median, last, [this, depth](int m1, int m2) { + return m1 != m2 && lessthan(_sites[m1]->position(), _sites[m2]->position(), depth % 3); + }); + return new TetraMeshSnapshot::Node(*(first + median), depth, buildTree(first, first + median, depth + 1), + buildTree(first + median + 1, last, depth + 1)); } - return false; + return nullptr; } //////////////////////////////////////////////////////////////////// -void TetraMeshSnapshot::writeGridPlotFiles(const SimulationItem* /*probe*/) const +void TetraMeshSnapshot::buildSearchPerBlock() { - std::ofstream outputFile("data/input.txt"); - outputFile << "voronois=" << _cells.size() << "\n"; - for (size_t i = 0; i < _cells.size(); i++) + // abort if there are no cells + int numTetra = _sites.size(); + if (!numTetra) return; + + log()->info("Building data structures to accelerate searching the Tetra tesselation"); + + // ------------- block lists ------------- + + // initialize a vector of nb x nb x nb lists, each containing the cells overlapping a certain block in the domain + _blocklists.resize(_nb3); + + // add the cell object to the lists for all blocks it may overlap + int i1, j1, k1, i2, j2, k2; + for (int m = 0; m != numTetra; ++m) + { + auto& vert = _tetrahedra[m]->_vertices; + auto vert_minmax = + std::minmax_element(vert.begin(), vert.end(), [](Vec* a, Vec* b) { return a->norm2() < b->norm2(); }); + + _extent.cellIndices(i1, j1, k1, **vert_minmax.first - Vec(_eps, _eps, _eps), _nb, _nb, _nb); + _extent.cellIndices(i2, j2, k2, **vert_minmax.second + Vec(_eps, _eps, _eps), _nb, _nb, _nb); + for (int i = i1; i <= i2; i++) + for (int j = j1; j <= j2; j++) + for (int k = k1; k <= k2; k++) _blocklists[i * _nb2 + j * _nb + k].push_back(m); + } + + // compile block list statistics + int minRefsPerBlock = INT_MAX; + int maxRefsPerBlock = 0; + int64_t totalBlockRefs = 0; + for (int b = 0; b < _nb3; b++) { - outputFile << "voronoi=" << i << "\n"; - Vec& r = _cells[i]->_r; - outputFile << r.x() << ", " << r.y() << ", " << r.z() << "\n"; + int refs = _blocklists[b].size(); + totalBlockRefs += refs; + minRefsPerBlock = min(minRefsPerBlock, refs); + maxRefsPerBlock = max(maxRefsPerBlock, refs); + } + double avgRefsPerBlock = double(totalBlockRefs) / _nb3; - outputFile << _cells[i]->neighbors().size() << " neighbors="; - for (int n : _cells[i]->neighbors()) - { - if (n >= 0) outputFile << " " << n << ","; - } - outputFile << "\n"; + // log block list statistics + log()->info(" Number of blocks in search grid: " + std::to_string(_nb3) + " (" + std::to_string(_nb) + "^3)"); + log()->info(" Average number of cells per block: " + StringUtils::toString(avgRefsPerBlock, 'f', 1)); + log()->info(" Minimum number of cells per block: " + std::to_string(minRefsPerBlock)); + log()->info(" Maximum number of cells per block: " + std::to_string(maxRefsPerBlock)); + + // ------------- search trees ------------- + + // for each block that contains more than a predefined number of cells, + // construct a search tree on the site locations of the cells + _blocktrees.resize(_nb3); + for (int b = 0; b < _nb3; b++) + { + vector& ids = _blocklists[b]; + if (ids.size() > 9) _blocktrees[b] = buildTree(ids.begin(), ids.end(), 0); } - outputFile << "tetrahedra=" << _tetrahedra.size() << "\n"; + // compile and log search tree statistics + int numTrees = 0; + for (int b = 0; b < _nb3; b++) + if (_blocktrees[b]) numTrees++; + log()->info(" Number of search trees: " + std::to_string(numTrees) + " (" + + StringUtils::toString(100. * numTrees / _nb3, 'f', 1) + "% of blocks)"); +} + +//////////////////////////////////////////////////////////////////// + +void TetraMeshSnapshot::buildSearchSingle() +{ + // log the number of sites + int numCells = _sites.size(); + log()->info(" Number of sites: " + std::to_string(numCells)); + + // abort if there are no cells + if (!numCells) return; + + // construct a single search tree on the site locations of all cells + log()->info("Building data structure to accelerate searching " + std::to_string(numCells) + " Voronoi sites"); + _blocktrees.resize(1); + vector ids(numCells); + for (int m = 0; m != numCells; ++m) ids[m] = m; + _blocktrees[0] = buildTree(ids.begin(), ids.end(), 0); +} + +//////////////////////////////////////////////////////////////////// + +void TetraMeshSnapshot::writeGridPlotFiles(const SimulationItem* /*probe*/) const +{ + std::ofstream outputFile("data/tetrahedra.txt"); + // outputFile << "vertices=" << _vertices.size() << "\n"; + // for (size_t i = 0; i < _vertices.size(); i++) + // { + // outputFile << "vertex=" << i << "\n"; + // Vec& r = *_vertices[i]; + // outputFile << r.x() << ", " << r.y() << ", " << r.z() << "\n"; + // outputFile << "\n"; + // } + for (size_t i = 0; i < _tetrahedra.size(); i++) { const Tetra* tetra = _tetrahedra[i]; - outputFile << "tetrahedron=" << i << "\nvertices="; for (size_t l = 0; l < 4; l++) { - outputFile << " " << tetra->_vertex_indices[l] << ","; + const Vec* r = tetra->_vertices[l]; + outputFile << r->x() << ", " << r->y() << ", " << r->z() << "\n"; } - - outputFile << "\n" << tetra->_neighbors.size() << " neighbors="; - for (size_t j = 0; j < 4; j++) - { - outputFile << " " << tetra->_neighbors[j] << ","; - } - outputFile << "\n"; + // outputFile << "neighbors="; + // for (size_t j = 0; j < 4; j++) + // { + // outputFile << " " << tetra->_neighbors[j]; + // if (j != 3) outputFile << ","; + // } + // outputFile << "\n"; } // for (size_t i = 0; i < _tetrahedra.size(); i++) // { @@ -1023,11 +1036,30 @@ Position TetraMeshSnapshot::generatePosition() const int TetraMeshSnapshot::cellIndex(Position bfr) const { + // make sure the position is inside the domain + // if (!_extent.contains(bfr)) return -1; + + // determine the block in which the point falls + // if we didn't build a Voronoi mesh, the search tree is always in the first "block" + // int i, j, k; + // _extent.cellIndices(i, j, k, bfr, _nb, _nb, _nb); + // int b = i * _nb2 + j * _nb + k; + + // look for the closest site in this block, using the search tree if there is one + // Node* tree = _blocktrees[b]; + // int m = tree->nearest(bfr, _sites)->m(); + // find all edges that connect to m + // Site* site = _sites[m]; + + // I can retreive all edges by looking at neighbors of Site + // i want tetra though so I'll build hashmap for Tetra instead of vector + + // if there is no search tree, simply loop over the index list + // maybe use a k-d tree here to find nearest tetrahedra for (int i = 0; i < numTetra; i++) { const Tetra* tetra = _tetrahedra[i]; - // speed up by rejecting all points that are not in de bounding box - if (tetra->Box::contains(bfr) && tetra->Tetra::inside(bfr)) return i; + if (tetra->Tetra::inside(bfr)) return i; } return -1; } @@ -1036,7 +1068,7 @@ int TetraMeshSnapshot::cellIndex(Position bfr) const const Array& TetraMeshSnapshot::properties(int m) const { - return _cells[m]->properties(); + return _sites[m]->properties(); } //////////////////////////////////////////////////////////////////// @@ -1065,6 +1097,30 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator public: MySegmentGenerator(const TetraMeshSnapshot* grid) : _grid(grid) {} +#define WRITE + +#ifdef WRITE + std::ofstream out; + + void startwriting() + { + if (!out.is_open()) out.open("data/photon.txt"); + } + + void stopwriting() + { + out.close(); + } + + void write(const Vec& exit, double ds, int face) + { + out << "photon=" << _mr << "," << face << "\n"; + out << "ds=" << ds << "\n"; + out << "r=" << r().x() << "," << r().y() << "," << r().z() << "\n"; + out << "exit=" << exit.x() << "," << exit.y() << "," << exit.z() << "\n"; + out << "k=" << k().x() << "," << k().y() << "," << k().z() << std::endl; + } +#endif bool next() override { if (state() == State::Unknown) @@ -1083,6 +1139,11 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator // if the photon packet started outside the grid, return the corresponding nonzero-length segment; // otherwise fall through to determine the first actual segment if (ds() > 0.) return true; + +#ifdef WRITE + startwriting(); + write(r(), 0, _mr); +#endif } // intentionally falls through @@ -1098,7 +1159,7 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator std::array prods; int leavingFace = -1; - if (enteringFace == -1) // this should only be done once max per ray + if (enteringFace == -1) // start ray traversal inside { for (int face = 0; face < 4; face++) { @@ -1108,18 +1169,14 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator break; } } - // if (leavingFace == -1) - // { - // _grid->log()->warning("no leaving face found!"); - // } } else { - std::array t = Tetra::clockwiseVertices(enteringFace); + std::array t = Tetra::counterclockwiseVertices(enteringFace); // 2 step decision tree prods[0] = tetra->getProd(ray, t[0], enteringFace); - bool clockwise0 = prods[0] < 0; + bool clockwise0 = prods[0] > 0; int i = clockwise0 ? 1 : 2; // if (counter)clockwise move (counter)clockwise prods[i] = tetra->getProd(ray, t[i], enteringFace); @@ -1128,14 +1185,14 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator // if clockwise then counter: face=t2 // if counter then clockwise: face=t1 - if (clockwise0 == (prods[i] < 0)) + if (clockwise0 == (prods[i] > 0)) leavingFace = t[0]; else if (clockwise0) leavingFace = t[2]; else leavingFace = t[1]; - // get prods (this calculates one prod too many but is much cleaner) + // get prods (this calculates prod[i] again but is much cleaner) tetra->intersects(prods, ray, leavingFace, true); } @@ -1145,6 +1202,7 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator { propagater(_grid->_eps); _mr = _grid->cellIndex(r()); + enteringFace = -1; // if we're outside the domain, terminate the path without returning a path segment if (_mr < 0) @@ -1164,7 +1222,9 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator propagater(ds + _grid->_eps); setSegment(_mr, ds); - +#ifdef WRITE + write(exit, ds, leavingFace); +#endif // set enteringFace if there is a neighboring cell if (next_mr != -1) { @@ -1175,12 +1235,11 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator else { enteringFace = -1; + setState(State::Outside); } // set new cell _mr = next_mr; - // if we're outside the domain, terminate the path after returning this path segment - if (_mr < 0) setState(State::Outside); return true; } } @@ -1208,6 +1267,10 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator double ds = min({t_x, t_y, t_z}); propagater(ds + _grid->_eps); setSegment(_mr, ds); +#ifdef WRITE + write(r(), ds, -1); + stopwriting(); +#endif return false; } @@ -1233,7 +1296,10 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator double ds = (exit - r()).norm(); propagater(ds + _grid->_eps); - // setSegment(-1, ds); // not sure if this is needed +// setSegment(-1, ds); // not sure if this is needed +#ifdef WRITE + write(exit, ds, enteringFace); +#endif wasInside = true; setState(State::Inside); @@ -1244,6 +1310,9 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator } } } +#ifdef WRITE + stopwriting(); +#endif return false; } }; diff --git a/SKIRT/core/TetraMeshSnapshot.hpp b/SKIRT/core/TetraMeshSnapshot.hpp index f5f6f86e..0856a80b 100644 --- a/SKIRT/core/TetraMeshSnapshot.hpp +++ b/SKIRT/core/TetraMeshSnapshot.hpp @@ -9,6 +9,7 @@ #include "Array.hpp" #include "Snapshot.hpp" #include +#include "array" class PathSegmentGenerator; class SiteListInterface; class SpatialGridPath; @@ -187,7 +188,9 @@ class TetraMeshSnapshot : public Snapshot private: /** Private class to hold the information about a Tetra cell that is relevant for calculating paths and densities; see the buildMesh() function. */ - class Cell; + class Site; + + class Node; class Plucker { @@ -207,48 +210,27 @@ class TetraMeshSnapshot : public Snapshot { public: const int i1, i2; - - Edge(int i1, int i2, const Vec& v1, const Vec& v2); - - bool operator==(const Edge& edge) const; - - // hash function - static size_t hash(const int i1, const int i2); + Edge(int i1, int i2, const Vec* v1, const Vec* v2); }; - typedef std::unordered_map EdgeMap; - class Tetra : public Box { public: - double _volume; std::array _vertices; - std::array _vertex_indices; - std::array _neighbors = {-1, -1, -1, -1}; + std::array _indices; std::array _edges; + std::array _neighbors = {-1, -1, -1, -1}; + double _volume; Array _properties; public: - Tetra(const vector& _cells, int i, int j, int k, int l); + Tetra(const std::array& vertices, const std::array& indices, + const std::array& neighbors, const std::array& edges); double getProd(const Plucker& ray, int t1, int t2) const; - // compute normal for face 3 (in clockwise direction of vertices 012) and dot with vertex 3 - double orient(); - - void addEdges(EdgeMap& edgemap); - - // used to build the neighbor array - // return -1 if no shared face - // return 0-3 for opposite vertex of shared face - int shareFace(const Tetra* other) const; - bool intersects(std::array& prods, const Plucker& ray, int face, bool leaving = true) const; - bool equals(const Tetra* other) const; - - bool SameSide(const Vec& v0, const Vec& v1, const Vec& v2, const Vec& v3, const Vec& pos) const; - bool inside(const Position& bfr) const; Vec calcExit(const std::array& barycoords, int face) const; @@ -259,7 +241,7 @@ class TetraMeshSnapshot : public Snapshot const Array& properties(); - static inline std::array clockwiseVertices(int face); + static inline std::array counterclockwiseVertices(int face); }; /** Given a list of generating sites (represented as partially initialized Cell @@ -291,6 +273,33 @@ class TetraMeshSnapshot : public Snapshot density columns being imported. */ void calculateDensityAndMass(); + /** Private function to recursively build a binary search tree (see + en.wikipedia.org/wiki/Kd-tree) */ + Node* buildTree(vector::iterator first, vector::iterator last, int depth) const; + + /** This private function builds data structures that allow accelerating the operation of the + cellIndex() function. It assumes that the Voronoi mesh has already been built. + + The domain is partitioned using a linear cubodial grid into cells that are called \em + blocks. For each block, the function builds and stores a list of all Voronoi cells that + possibly overlap that block. Retrieving the list of cells possibly overlapping a given + point in the domain then comes down to locating the block containing the point (which is + trivial since the grid is linear). The current implementation uses a Voronoi cell's + enclosing cuboid to test for intersection with a block. Performing a precise intersection + test is \em really slow and the shortened block lists don't substantially accelerate the + cellIndex() function. + + To further reduce the search time within blocks that overlap with a large number of cells, + the function builds a binary search tree on the cell sites for those blocks (see for example + en.wikipedia.org/wiki/Kd-tree). */ + void buildSearchPerBlock(); + + /** This private function builds a data structure that allows accelerating the operation of the + cellIndex() function without using the Voronoi mesh. The domain is not partitioned in + blocks. The function builds a single binary search tree on all cell sites (see for example + en.wikipedia.org/wiki/Kd-tree). */ + void buildSearchSingle(); + bool inTetrahedra(const Tetra* tetra) const; //====================== Output ===================== @@ -488,15 +497,18 @@ class TetraMeshSnapshot : public Snapshot private: // data members initialized during configuration - Box _extent; // the spatial domain of the mesh - double _eps{0.}; // small fraction of extent + Box _extent; // the spatial domain of the mesh + double _eps{0.}; // small fraction of extent int numTetra; + int numEdges; + int numVertices; // data members initialized when processing snapshot input and further completed by BuildMesh() - vector _cells; // cell objects, indexed on m + vector _sites; // cell objects, indexed on m vector _tetrahedra; - EdgeMap _edgemap; + vector _edges; + vector _vertices; // data members initialized when processing snapshot input, but only if a density policy has been set Array _rhov; // density for each cell (not normalized) @@ -508,6 +520,7 @@ class TetraMeshSnapshot : public Snapshot int _nb2{0}; // nb*nb int _nb3{0}; // nb*nb*nb vector> _blocklists; // list of cell indices per block, indexed on i*_nb2+j*_nb+k + vector _blocktrees; // root node of search tree or null for each block, indexed on i*_nb2+j*_nb+k // allow our path segment generator to access our private data members class MySegmentGenerator; diff --git a/SKIRT/tetgen/CMakeLists.txt b/SKIRT/tetgen/CMakeLists.txt new file mode 100644 index 00000000..d360f22a --- /dev/null +++ b/SKIRT/tetgen/CMakeLists.txt @@ -0,0 +1,33 @@ +# ////////////////////////////////////////////////////////////////// +# /// The SKIRT project -- advanced radiative transfer /// +# /// © Astronomical Observatory, Ghent University /// +# ////////////////////////////////////////////////////////////////// + +# ------------------------------------------------------------------ +# Builds a library that supports building Tetra meshes. +# The source code is taken from the TetGen library written by +# Hang Si (Weierstrass Institute for Applied Analysis and Stochastics (WIAS)) +# at https://github.com/libigl/tetgen (git commit 4f3bfba). +# +# Changes: +# - +#--------------------------------------------------------------------- + +# set the target name +set(TARGET tetgen) + +# list the source files in this directory +file(GLOB SOURCES "*.cxx") +file(GLOB HEADERS "*.h") + +# create the library target +add_library(${TARGET} STATIC ${SOURCES} ${HEADERS}) +target_compile_definitions(tetgen PRIVATE -DTETLIBRARY) +# Generate position independent code +# set_target_properties(tetgen PROPERTIES POSITION_INDEPENDENT_CODE ON) + +# adjust C++ compiler flags to our needs +# set(NO_EXTRA_WARNINGS true) # to avoid warnings in the Voro++ code +include("../../SMILE/build/CompilerFlags.cmake") + + diff --git a/SKIRT/tetgen/predicates.cxx b/SKIRT/tetgen/predicates.cxx new file mode 100644 index 00000000..bb713704 --- /dev/null +++ b/SKIRT/tetgen/predicates.cxx @@ -0,0 +1,4710 @@ +/*****************************************************************************/ +/* */ +/* Routines for Arbitrary Precision Floating-point Arithmetic */ +/* and Fast Robust Geometric Predicates */ +/* (predicates.c) */ +/* */ +/* May 18, 1996 */ +/* */ +/* Placed in the public domain by */ +/* Jonathan Richard Shewchuk */ +/* School of Computer Science */ +/* Carnegie Mellon University */ +/* 5000 Forbes Avenue */ +/* Pittsburgh, Pennsylvania 15213-3891 */ +/* jrs@cs.cmu.edu */ +/* */ +/* This file contains C implementation of algorithms for exact addition */ +/* and multiplication of floating-point numbers, and predicates for */ +/* robustly performing the orientation and incircle tests used in */ +/* computational geometry. The algorithms and underlying theory are */ +/* described in Jonathan Richard Shewchuk. "Adaptive Precision Floating- */ +/* Point Arithmetic and Fast Robust Geometric Predicates." Technical */ +/* Report CMU-CS-96-140, School of Computer Science, Carnegie Mellon */ +/* University, Pittsburgh, Pennsylvania, May 1996. (Submitted to */ +/* Discrete & Computational Geometry.) */ +/* */ +/* This file, the paper listed above, and other information are available */ +/* from the Web page http://www.cs.cmu.edu/~quake/robust.html . */ +/* */ +/*****************************************************************************/ + +/*****************************************************************************/ +/* */ +/* Using this code: */ +/* */ +/* First, read the short or long version of the paper (from the Web page */ +/* above). */ +/* */ +/* Be sure to call exactinit() once, before calling any of the arithmetic */ +/* functions or geometric predicates. Also be sure to turn on the */ +/* optimizer when compiling this file. */ +/* */ +/* */ +/* Several geometric predicates are defined. Their parameters are all */ +/* points. Each point is an array of two or three floating-point */ +/* numbers. The geometric predicates, described in the papers, are */ +/* */ +/* orient2d(pa, pb, pc) */ +/* orient2dfast(pa, pb, pc) */ +/* orient3d(pa, pb, pc, pd) */ +/* orient3dfast(pa, pb, pc, pd) */ +/* incircle(pa, pb, pc, pd) */ +/* incirclefast(pa, pb, pc, pd) */ +/* insphere(pa, pb, pc, pd, pe) */ +/* inspherefast(pa, pb, pc, pd, pe) */ +/* */ +/* Those with suffix "fast" are approximate, non-robust versions. Those */ +/* without the suffix are adaptive precision, robust versions. There */ +/* are also versions with the suffices "exact" and "slow", which are */ +/* non-adaptive, exact arithmetic versions, which I use only for timings */ +/* in my arithmetic papers. */ +/* */ +/* */ +/* An expansion is represented by an array of floating-point numbers, */ +/* sorted from smallest to largest magnitude (possibly with interspersed */ +/* zeros). The length of each expansion is stored as a separate integer, */ +/* and each arithmetic function returns an integer which is the length */ +/* of the expansion it created. */ +/* */ +/* Several arithmetic functions are defined. Their parameters are */ +/* */ +/* e, f Input expansions */ +/* elen, flen Lengths of input expansions (must be >= 1) */ +/* h Output expansion */ +/* b Input scalar */ +/* */ +/* The arithmetic functions are */ +/* */ +/* grow_expansion(elen, e, b, h) */ +/* grow_expansion_zeroelim(elen, e, b, h) */ +/* expansion_sum(elen, e, flen, f, h) */ +/* expansion_sum_zeroelim1(elen, e, flen, f, h) */ +/* expansion_sum_zeroelim2(elen, e, flen, f, h) */ +/* fast_expansion_sum(elen, e, flen, f, h) */ +/* fast_expansion_sum_zeroelim(elen, e, flen, f, h) */ +/* linear_expansion_sum(elen, e, flen, f, h) */ +/* linear_expansion_sum_zeroelim(elen, e, flen, f, h) */ +/* scale_expansion(elen, e, b, h) */ +/* scale_expansion_zeroelim(elen, e, b, h) */ +/* compress(elen, e, h) */ +/* */ +/* All of these are described in the long version of the paper; some are */ +/* described in the short version. All return an integer that is the */ +/* length of h. Those with suffix _zeroelim perform zero elimination, */ +/* and are recommended over their counterparts. The procedure */ +/* fast_expansion_sum_zeroelim() (or linear_expansion_sum_zeroelim() on */ +/* processors that do not use the round-to-even tiebreaking rule) is */ +/* recommended over expansion_sum_zeroelim(). Each procedure has a */ +/* little note next to it (in the code below) that tells you whether or */ +/* not the output expansion may be the same array as one of the input */ +/* expansions. */ +/* */ +/* */ +/* If you look around below, you'll also find macros for a bunch of */ +/* simple unrolled arithmetic operations, and procedures for printing */ +/* expansions (commented out because they don't work with all C */ +/* compilers) and for generating random floating-point numbers whose */ +/* significand bits are all random. Most of the macros have undocumented */ +/* requirements that certain of their parameters should not be the same */ +/* variable; for safety, better to make sure all the parameters are */ +/* distinct variables. Feel free to send email to jrs@cs.cmu.edu if you */ +/* have questions. */ +/* */ +/*****************************************************************************/ + +#include +#include +#include +#ifdef CPU86 +#include +#endif /* CPU86 */ +#ifdef LINUX +#include +#endif /* LINUX */ + +#include "tetgen.h" // Defines the symbol REAL (float or double). + +#ifdef USE_CGAL_PREDICATES + #include + typedef CGAL::Exact_predicates_inexact_constructions_kernel cgalEpick; + typedef cgalEpick::Point_3 Point; + cgalEpick cgal_pred_obj; +#endif // #ifdef USE_CGAL_PREDICATES + +/* On some machines, the exact arithmetic routines might be defeated by the */ +/* use of internal extended precision floating-point registers. Sometimes */ +/* this problem can be fixed by defining certain values to be volatile, */ +/* thus forcing them to be stored to memory and rounded off. This isn't */ +/* a great solution, though, as it slows the arithmetic down. */ +/* */ +/* To try this out, write "#define INEXACT volatile" below. Normally, */ +/* however, INEXACT should be defined to be nothing. ("#define INEXACT".) */ + +#define INEXACT /* Nothing */ +/* #define INEXACT volatile */ + +/* #define REAL double */ /* float or double */ +#define REALPRINT doubleprint +#define REALRAND doublerand +#define NARROWRAND narrowdoublerand +#define UNIFORMRAND uniformdoublerand + +/* Which of the following two methods of finding the absolute values is */ +/* fastest is compiler-dependent. A few compilers can inline and optimize */ +/* the fabs() call; but most will incur the overhead of a function call, */ +/* which is disastrously slow. A faster way on IEEE machines might be to */ +/* mask the appropriate bit, but that's difficult to do in C. */ + +//#define Absolute(a) ((a) >= 0.0 ? (a) : -(a)) +#define Absolute(a) fabs(a) + +/* Many of the operations are broken up into two pieces, a main part that */ +/* performs an approximate operation, and a "tail" that computes the */ +/* roundoff error of that operation. */ +/* */ +/* The operations Fast_Two_Sum(), Fast_Two_Diff(), Two_Sum(), Two_Diff(), */ +/* Split(), and Two_Product() are all implemented as described in the */ +/* reference. Each of these macros requires certain variables to be */ +/* defined in the calling routine. The variables `bvirt', `c', `abig', */ +/* `_i', `_j', `_k', `_l', `_m', and `_n' are declared `INEXACT' because */ +/* they store the result of an operation that may incur roundoff error. */ +/* The input parameter `x' (or the highest numbered `x_' parameter) must */ +/* also be declared `INEXACT'. */ + +#define Fast_Two_Sum_Tail(a, b, x, y) \ + bvirt = x - a; \ + y = b - bvirt + +#define Fast_Two_Sum(a, b, x, y) \ + x = (REAL) (a + b); \ + Fast_Two_Sum_Tail(a, b, x, y) + +#define Fast_Two_Diff_Tail(a, b, x, y) \ + bvirt = a - x; \ + y = bvirt - b + +#define Fast_Two_Diff(a, b, x, y) \ + x = (REAL) (a - b); \ + Fast_Two_Diff_Tail(a, b, x, y) + +#define Two_Sum_Tail(a, b, x, y) \ + bvirt = (REAL) (x - a); \ + avirt = x - bvirt; \ + bround = b - bvirt; \ + around = a - avirt; \ + y = around + bround + +#define Two_Sum(a, b, x, y) \ + x = (REAL) (a + b); \ + Two_Sum_Tail(a, b, x, y) + +#define Two_Diff_Tail(a, b, x, y) \ + bvirt = (REAL) (a - x); \ + avirt = x + bvirt; \ + bround = bvirt - b; \ + around = a - avirt; \ + y = around + bround + +#define Two_Diff(a, b, x, y) \ + x = (REAL) (a - b); \ + Two_Diff_Tail(a, b, x, y) + +#define Split(a, ahi, alo) \ + c = (REAL) (splitter * a); \ + abig = (REAL) (c - a); \ + ahi = c - abig; \ + alo = a - ahi + +#define Two_Product_Tail(a, b, x, y) \ + Split(a, ahi, alo); \ + Split(b, bhi, blo); \ + err1 = x - (ahi * bhi); \ + err2 = err1 - (alo * bhi); \ + err3 = err2 - (ahi * blo); \ + y = (alo * blo) - err3 + +#define Two_Product(a, b, x, y) \ + x = (REAL) (a * b); \ + Two_Product_Tail(a, b, x, y) + +/* Two_Product_Presplit() is Two_Product() where one of the inputs has */ +/* already been split. Avoids redundant splitting. */ + +#define Two_Product_Presplit(a, b, bhi, blo, x, y) \ + x = (REAL) (a * b); \ + Split(a, ahi, alo); \ + err1 = x - (ahi * bhi); \ + err2 = err1 - (alo * bhi); \ + err3 = err2 - (ahi * blo); \ + y = (alo * blo) - err3 + +/* Two_Product_2Presplit() is Two_Product() where both of the inputs have */ +/* already been split. Avoids redundant splitting. */ + +#define Two_Product_2Presplit(a, ahi, alo, b, bhi, blo, x, y) \ + x = (REAL) (a * b); \ + err1 = x - (ahi * bhi); \ + err2 = err1 - (alo * bhi); \ + err3 = err2 - (ahi * blo); \ + y = (alo * blo) - err3 + +/* Square() can be done more quickly than Two_Product(). */ + +#define Square_Tail(a, x, y) \ + Split(a, ahi, alo); \ + err1 = x - (ahi * ahi); \ + err3 = err1 - ((ahi + ahi) * alo); \ + y = (alo * alo) - err3 + +#define Square(a, x, y) \ + x = (REAL) (a * a); \ + Square_Tail(a, x, y) + +/* Macros for summing expansions of various fixed lengths. These are all */ +/* unrolled versions of Expansion_Sum(). */ + +#define Two_One_Sum(a1, a0, b, x2, x1, x0) \ + Two_Sum(a0, b , _i, x0); \ + Two_Sum(a1, _i, x2, x1) + +#define Two_One_Diff(a1, a0, b, x2, x1, x0) \ + Two_Diff(a0, b , _i, x0); \ + Two_Sum( a1, _i, x2, x1) + +#define Two_Two_Sum(a1, a0, b1, b0, x3, x2, x1, x0) \ + Two_One_Sum(a1, a0, b0, _j, _0, x0); \ + Two_One_Sum(_j, _0, b1, x3, x2, x1) + +#define Two_Two_Diff(a1, a0, b1, b0, x3, x2, x1, x0) \ + Two_One_Diff(a1, a0, b0, _j, _0, x0); \ + Two_One_Diff(_j, _0, b1, x3, x2, x1) + +#define Four_One_Sum(a3, a2, a1, a0, b, x4, x3, x2, x1, x0) \ + Two_One_Sum(a1, a0, b , _j, x1, x0); \ + Two_One_Sum(a3, a2, _j, x4, x3, x2) + +#define Four_Two_Sum(a3, a2, a1, a0, b1, b0, x5, x4, x3, x2, x1, x0) \ + Four_One_Sum(a3, a2, a1, a0, b0, _k, _2, _1, _0, x0); \ + Four_One_Sum(_k, _2, _1, _0, b1, x5, x4, x3, x2, x1) + +#define Four_Four_Sum(a3, a2, a1, a0, b4, b3, b1, b0, x7, x6, x5, x4, x3, x2, \ + x1, x0) \ + Four_Two_Sum(a3, a2, a1, a0, b1, b0, _l, _2, _1, _0, x1, x0); \ + Four_Two_Sum(_l, _2, _1, _0, b4, b3, x7, x6, x5, x4, x3, x2) + +#define Eight_One_Sum(a7, a6, a5, a4, a3, a2, a1, a0, b, x8, x7, x6, x5, x4, \ + x3, x2, x1, x0) \ + Four_One_Sum(a3, a2, a1, a0, b , _j, x3, x2, x1, x0); \ + Four_One_Sum(a7, a6, a5, a4, _j, x8, x7, x6, x5, x4) + +#define Eight_Two_Sum(a7, a6, a5, a4, a3, a2, a1, a0, b1, b0, x9, x8, x7, \ + x6, x5, x4, x3, x2, x1, x0) \ + Eight_One_Sum(a7, a6, a5, a4, a3, a2, a1, a0, b0, _k, _6, _5, _4, _3, _2, \ + _1, _0, x0); \ + Eight_One_Sum(_k, _6, _5, _4, _3, _2, _1, _0, b1, x9, x8, x7, x6, x5, x4, \ + x3, x2, x1) + +#define Eight_Four_Sum(a7, a6, a5, a4, a3, a2, a1, a0, b4, b3, b1, b0, x11, \ + x10, x9, x8, x7, x6, x5, x4, x3, x2, x1, x0) \ + Eight_Two_Sum(a7, a6, a5, a4, a3, a2, a1, a0, b1, b0, _l, _6, _5, _4, _3, \ + _2, _1, _0, x1, x0); \ + Eight_Two_Sum(_l, _6, _5, _4, _3, _2, _1, _0, b4, b3, x11, x10, x9, x8, \ + x7, x6, x5, x4, x3, x2) + +/* Macros for multiplying expansions of various fixed lengths. */ + +#define Two_One_Product(a1, a0, b, x3, x2, x1, x0) \ + Split(b, bhi, blo); \ + Two_Product_Presplit(a0, b, bhi, blo, _i, x0); \ + Two_Product_Presplit(a1, b, bhi, blo, _j, _0); \ + Two_Sum(_i, _0, _k, x1); \ + Fast_Two_Sum(_j, _k, x3, x2) + +#define Four_One_Product(a3, a2, a1, a0, b, x7, x6, x5, x4, x3, x2, x1, x0) \ + Split(b, bhi, blo); \ + Two_Product_Presplit(a0, b, bhi, blo, _i, x0); \ + Two_Product_Presplit(a1, b, bhi, blo, _j, _0); \ + Two_Sum(_i, _0, _k, x1); \ + Fast_Two_Sum(_j, _k, _i, x2); \ + Two_Product_Presplit(a2, b, bhi, blo, _j, _0); \ + Two_Sum(_i, _0, _k, x3); \ + Fast_Two_Sum(_j, _k, _i, x4); \ + Two_Product_Presplit(a3, b, bhi, blo, _j, _0); \ + Two_Sum(_i, _0, _k, x5); \ + Fast_Two_Sum(_j, _k, x7, x6) + +#define Two_Two_Product(a1, a0, b1, b0, x7, x6, x5, x4, x3, x2, x1, x0) \ + Split(a0, a0hi, a0lo); \ + Split(b0, bhi, blo); \ + Two_Product_2Presplit(a0, a0hi, a0lo, b0, bhi, blo, _i, x0); \ + Split(a1, a1hi, a1lo); \ + Two_Product_2Presplit(a1, a1hi, a1lo, b0, bhi, blo, _j, _0); \ + Two_Sum(_i, _0, _k, _1); \ + Fast_Two_Sum(_j, _k, _l, _2); \ + Split(b1, bhi, blo); \ + Two_Product_2Presplit(a0, a0hi, a0lo, b1, bhi, blo, _i, _0); \ + Two_Sum(_1, _0, _k, x1); \ + Two_Sum(_2, _k, _j, _1); \ + Two_Sum(_l, _j, _m, _2); \ + Two_Product_2Presplit(a1, a1hi, a1lo, b1, bhi, blo, _j, _0); \ + Two_Sum(_i, _0, _n, _0); \ + Two_Sum(_1, _0, _i, x2); \ + Two_Sum(_2, _i, _k, _1); \ + Two_Sum(_m, _k, _l, _2); \ + Two_Sum(_j, _n, _k, _0); \ + Two_Sum(_1, _0, _j, x3); \ + Two_Sum(_2, _j, _i, _1); \ + Two_Sum(_l, _i, _m, _2); \ + Two_Sum(_1, _k, _i, x4); \ + Two_Sum(_2, _i, _k, x5); \ + Two_Sum(_m, _k, x7, x6) + +/* An expansion of length two can be squared more quickly than finding the */ +/* product of two different expansions of length two, and the result is */ +/* guaranteed to have no more than six (rather than eight) components. */ + +#define Two_Square(a1, a0, x5, x4, x3, x2, x1, x0) \ + Square(a0, _j, x0); \ + _0 = a0 + a0; \ + Two_Product(a1, _0, _k, _1); \ + Two_One_Sum(_k, _1, _j, _l, _2, x1); \ + Square(a1, _j, _1); \ + Two_Two_Sum(_j, _1, _l, _2, x5, x4, x3, x2) + +/* splitter = 2^ceiling(p / 2) + 1. Used to split floats in half. */ +static REAL splitter; +static REAL epsilon; /* = 2^(-p). Used to estimate roundoff errors. */ +/* A set of coefficients used to calculate maximum roundoff errors. */ +static REAL resulterrbound; +static REAL ccwerrboundA, ccwerrboundB, ccwerrboundC; +static REAL o3derrboundA, o3derrboundB, o3derrboundC; +static REAL iccerrboundA, iccerrboundB, iccerrboundC; +static REAL isperrboundA, isperrboundB, isperrboundC; + +// Options to choose types of geometric computtaions. +// Added by H. Si, 2012-08-23. +static int _use_inexact_arith; // -X option. +static int _use_static_filter; // Default option, disable it by -X1 + +// Static filters for orient3d() and insphere(). +// They are pre-calcualted and set in exactinit(). +// Added by H. Si, 2012-08-23. +static REAL o3dstaticfilter; +static REAL ispstaticfilter; + + + +// The following codes were part of "IEEE 754 floating-point test software" +// http://www.math.utah.edu/~beebe/software/ieee/ +// The original program was "fpinfo2.c". + +static double fppow2(int n) +{ + double x, power; + x = (n < 0) ? ((double)1.0/(double)2.0) : (double)2.0; + n = (n < 0) ? -n : n; + power = (double)1.0; + while (n-- > 0) + power *= x; + return (power); +} + +#ifdef SINGLE + +static float fstore(float x) +{ + return (x); +} + +static int test_float(int verbose) +{ + float x; + int pass = 1; + + //(void)printf("float:\n"); + + if (verbose) { + (void)printf(" sizeof(float) = %2u\n", (unsigned int)sizeof(float)); +#ifdef CPU86 // + (void)printf(" FLT_MANT_DIG = %2d\n", FLT_MANT_DIG); +#endif + } + + x = (float)1.0; + while (fstore((float)1.0 + x/(float)2.0) != (float)1.0) + x /= (float)2.0; + if (verbose) + (void)printf(" machine epsilon = %13.5e ", x); + + if (x == (float)fppow2(-23)) { + if (verbose) + (void)printf("[IEEE 754 32-bit macheps]\n"); + } else { + (void)printf("[not IEEE 754 conformant] !!\n"); + pass = 0; + } + + x = (float)1.0; + while (fstore(x / (float)2.0) != (float)0.0) + x /= (float)2.0; + if (verbose) + (void)printf(" smallest positive number = %13.5e ", x); + + if (x == (float)fppow2(-149)) { + if (verbose) + (void)printf("[smallest 32-bit subnormal]\n"); + } else if (x == (float)fppow2(-126)) { + if (verbose) + (void)printf("[smallest 32-bit normal]\n"); + } else { + (void)printf("[not IEEE 754 conformant] !!\n"); + pass = 0; + } + + return pass; +} + +# else + +static double dstore(double x) +{ + return (x); +} + +static int test_double(int verbose) +{ + double x; + int pass = 1; + + // (void)printf("double:\n"); + if (verbose) { + (void)printf(" sizeof(double) = %2u\n", (unsigned int)sizeof(double)); +#ifdef CPU86 // + (void)printf(" DBL_MANT_DIG = %2d\n", DBL_MANT_DIG); +#endif + } + + x = 1.0; + while (dstore(1.0 + x/2.0) != 1.0) + x /= 2.0; + if (verbose) + (void)printf(" machine epsilon = %13.5le ", x); + + if (x == (double)fppow2(-52)) { + if (verbose) + (void)printf("[IEEE 754 64-bit macheps]\n"); + } else { + (void)printf("[not IEEE 754 conformant] !!\n"); + pass = 0; + } + + x = 1.0; + while (dstore(x / 2.0) != 0.0) + x /= 2.0; + //if (verbose) + // (void)printf(" smallest positive number = %13.5le ", x); + + if (x == (double)fppow2(-1074)) { + //if (verbose) + // (void)printf("[smallest 64-bit subnormal]\n"); + } else if (x == (double)fppow2(-1022)) { + //if (verbose) + // (void)printf("[smallest 64-bit normal]\n"); + } else { + (void)printf("[not IEEE 754 conformant] !!\n"); + pass = 0; + } + + return pass; +} + +#endif + +/*****************************************************************************/ +/* */ +/* exactinit() Initialize the variables used for exact arithmetic. */ +/* */ +/* `epsilon' is the largest power of two such that 1.0 + epsilon = 1.0 in */ +/* floating-point arithmetic. `epsilon' bounds the relative roundoff */ +/* error. It is used for floating-point error analysis. */ +/* */ +/* `splitter' is used to split floating-point numbers into two half- */ +/* length significands for exact multiplication. */ +/* */ +/* I imagine that a highly optimizing compiler might be too smart for its */ +/* own good, and somehow cause this routine to fail, if it pretends that */ +/* floating-point arithmetic is too much like real arithmetic. */ +/* */ +/* Don't change this routine unless you fully understand it. */ +/* */ +/*****************************************************************************/ + +void exactinit(int verbose, int noexact, int nofilter, REAL maxx, REAL maxy, + REAL maxz) +{ + REAL half; + REAL check, lastcheck; + int every_other; +#ifdef LINUX + int cword; +#endif /* LINUX */ + +#ifdef CPU86 +#ifdef SINGLE + _control87(_PC_24, _MCW_PC); /* Set FPU control word for single precision. */ +#else /* not SINGLE */ + _control87(_PC_53, _MCW_PC); /* Set FPU control word for double precision. */ +#endif /* not SINGLE */ +#endif /* CPU86 */ +#ifdef LINUX +#ifdef SINGLE + /* cword = 4223; */ + cword = 4210; /* set FPU control word for single precision */ +#else /* not SINGLE */ + /* cword = 4735; */ + cword = 4722; /* set FPU control word for double precision */ +#endif /* not SINGLE */ + _FPU_SETCW(cword); +#endif /* LINUX */ + + if (verbose) { + printf(" Initializing robust predicates.\n"); + } + +#ifdef USE_CGAL_PREDICATES + if (cgal_pred_obj.Has_static_filters) { + printf(" Use static filter.\n"); + } else { + printf(" No static filter.\n"); + } +#endif // USE_CGAL_PREDICATES + +#ifdef SINGLE + test_float(verbose); +#else + test_double(verbose); +#endif + + every_other = 1; + half = 0.5; + epsilon = 1.0; + splitter = 1.0; + check = 1.0; + /* Repeatedly divide `epsilon' by two until it is too small to add to */ + /* one without causing roundoff. (Also check if the sum is equal to */ + /* the previous sum, for machines that round up instead of using exact */ + /* rounding. Not that this library will work on such machines anyway. */ + do { + lastcheck = check; + epsilon *= half; + if (every_other) { + splitter *= 2.0; + } + every_other = !every_other; + check = 1.0 + epsilon; + } while ((check != 1.0) && (check != lastcheck)); + splitter += 1.0; + + /* Error bounds for orientation and incircle tests. */ + resulterrbound = (3.0 + 8.0 * epsilon) * epsilon; + ccwerrboundA = (3.0 + 16.0 * epsilon) * epsilon; + ccwerrboundB = (2.0 + 12.0 * epsilon) * epsilon; + ccwerrboundC = (9.0 + 64.0 * epsilon) * epsilon * epsilon; + o3derrboundA = (7.0 + 56.0 * epsilon) * epsilon; + o3derrboundB = (3.0 + 28.0 * epsilon) * epsilon; + o3derrboundC = (26.0 + 288.0 * epsilon) * epsilon * epsilon; + iccerrboundA = (10.0 + 96.0 * epsilon) * epsilon; + iccerrboundB = (4.0 + 48.0 * epsilon) * epsilon; + iccerrboundC = (44.0 + 576.0 * epsilon) * epsilon * epsilon; + isperrboundA = (16.0 + 224.0 * epsilon) * epsilon; + isperrboundB = (5.0 + 72.0 * epsilon) * epsilon; + isperrboundC = (71.0 + 1408.0 * epsilon) * epsilon * epsilon; + + // Set TetGen options. Added by H. Si, 2012-08-23. + _use_inexact_arith = noexact; + _use_static_filter = !nofilter; + + // Calculate the two static filters for orient3d() and insphere() tests. + // Added by H. Si, 2012-08-23. + + // Sort maxx < maxy < maxz. Re-use 'half' for swapping. + if (maxx > maxz) { + half = maxx; maxx = maxz; maxz = half; + } + if (maxy > maxz) { + half = maxy; maxy = maxz; maxz = half; + } + else if (maxy < maxx) { + half = maxy; maxy = maxx; maxx = half; + } + + o3dstaticfilter = 5.1107127829973299e-15 * maxx * maxy * maxz; + ispstaticfilter = 1.2466136531027298e-13 * maxx * maxy * maxz * (maxz * maxz); + +} + +/*****************************************************************************/ +/* */ +/* grow_expansion() Add a scalar to an expansion. */ +/* */ +/* Sets h = e + b. See the long version of my paper for details. */ +/* */ +/* Maintains the nonoverlapping property. If round-to-even is used (as */ +/* with IEEE 754), maintains the strongly nonoverlapping and nonadjacent */ +/* properties as well. (That is, if e has one of these properties, so */ +/* will h.) */ +/* */ +/*****************************************************************************/ + +int grow_expansion(int elen, REAL *e, REAL b, REAL *h) +/* e and h can be the same. */ +{ + REAL Q; + INEXACT REAL Qnew; + int eindex; + REAL enow; + INEXACT REAL bvirt; + REAL avirt, bround, around; + + Q = b; + for (eindex = 0; eindex < elen; eindex++) { + enow = e[eindex]; + Two_Sum(Q, enow, Qnew, h[eindex]); + Q = Qnew; + } + h[eindex] = Q; + return eindex + 1; +} + +/*****************************************************************************/ +/* */ +/* grow_expansion_zeroelim() Add a scalar to an expansion, eliminating */ +/* zero components from the output expansion. */ +/* */ +/* Sets h = e + b. See the long version of my paper for details. */ +/* */ +/* Maintains the nonoverlapping property. If round-to-even is used (as */ +/* with IEEE 754), maintains the strongly nonoverlapping and nonadjacent */ +/* properties as well. (That is, if e has one of these properties, so */ +/* will h.) */ +/* */ +/*****************************************************************************/ + +int grow_expansion_zeroelim(int elen, REAL *e, REAL b, REAL *h) +/* e and h can be the same. */ +{ + REAL Q, hh; + INEXACT REAL Qnew; + int eindex, hindex; + REAL enow; + INEXACT REAL bvirt; + REAL avirt, bround, around; + + hindex = 0; + Q = b; + for (eindex = 0; eindex < elen; eindex++) { + enow = e[eindex]; + Two_Sum(Q, enow, Qnew, hh); + Q = Qnew; + if (hh != 0.0) { + h[hindex++] = hh; + } + } + if ((Q != 0.0) || (hindex == 0)) { + h[hindex++] = Q; + } + return hindex; +} + +/*****************************************************************************/ +/* */ +/* expansion_sum() Sum two expansions. */ +/* */ +/* Sets h = e + f. See the long version of my paper for details. */ +/* */ +/* Maintains the nonoverlapping property. If round-to-even is used (as */ +/* with IEEE 754), maintains the nonadjacent property as well. (That is, */ +/* if e has one of these properties, so will h.) Does NOT maintain the */ +/* strongly nonoverlapping property. */ +/* */ +/*****************************************************************************/ + +int expansion_sum(int elen, REAL *e, int flen, REAL *f, REAL *h) +/* e and h can be the same, but f and h cannot. */ +{ + REAL Q; + INEXACT REAL Qnew; + int findex, hindex, hlast; + REAL hnow; + INEXACT REAL bvirt; + REAL avirt, bround, around; + + Q = f[0]; + for (hindex = 0; hindex < elen; hindex++) { + hnow = e[hindex]; + Two_Sum(Q, hnow, Qnew, h[hindex]); + Q = Qnew; + } + h[hindex] = Q; + hlast = hindex; + for (findex = 1; findex < flen; findex++) { + Q = f[findex]; + for (hindex = findex; hindex <= hlast; hindex++) { + hnow = h[hindex]; + Two_Sum(Q, hnow, Qnew, h[hindex]); + Q = Qnew; + } + h[++hlast] = Q; + } + return hlast + 1; +} + +/*****************************************************************************/ +/* */ +/* expansion_sum_zeroelim1() Sum two expansions, eliminating zero */ +/* components from the output expansion. */ +/* */ +/* Sets h = e + f. See the long version of my paper for details. */ +/* */ +/* Maintains the nonoverlapping property. If round-to-even is used (as */ +/* with IEEE 754), maintains the nonadjacent property as well. (That is, */ +/* if e has one of these properties, so will h.) Does NOT maintain the */ +/* strongly nonoverlapping property. */ +/* */ +/*****************************************************************************/ + +int expansion_sum_zeroelim1(int elen, REAL *e, int flen, REAL *f, REAL *h) +/* e and h can be the same, but f and h cannot. */ +{ + REAL Q; + INEXACT REAL Qnew; + int index, findex, hindex, hlast; + REAL hnow; + INEXACT REAL bvirt; + REAL avirt, bround, around; + + Q = f[0]; + for (hindex = 0; hindex < elen; hindex++) { + hnow = e[hindex]; + Two_Sum(Q, hnow, Qnew, h[hindex]); + Q = Qnew; + } + h[hindex] = Q; + hlast = hindex; + for (findex = 1; findex < flen; findex++) { + Q = f[findex]; + for (hindex = findex; hindex <= hlast; hindex++) { + hnow = h[hindex]; + Two_Sum(Q, hnow, Qnew, h[hindex]); + Q = Qnew; + } + h[++hlast] = Q; + } + hindex = -1; + for (index = 0; index <= hlast; index++) { + hnow = h[index]; + if (hnow != 0.0) { + h[++hindex] = hnow; + } + } + if (hindex == -1) { + return 1; + } else { + return hindex + 1; + } +} + +/*****************************************************************************/ +/* */ +/* expansion_sum_zeroelim2() Sum two expansions, eliminating zero */ +/* components from the output expansion. */ +/* */ +/* Sets h = e + f. See the long version of my paper for details. */ +/* */ +/* Maintains the nonoverlapping property. If round-to-even is used (as */ +/* with IEEE 754), maintains the nonadjacent property as well. (That is, */ +/* if e has one of these properties, so will h.) Does NOT maintain the */ +/* strongly nonoverlapping property. */ +/* */ +/*****************************************************************************/ + +int expansion_sum_zeroelim2(int elen, REAL *e, int flen, REAL *f, REAL *h) +/* e and h can be the same, but f and h cannot. */ +{ + REAL Q, hh; + INEXACT REAL Qnew; + int eindex, findex, hindex, hlast; + REAL enow; + INEXACT REAL bvirt; + REAL avirt, bround, around; + + hindex = 0; + Q = f[0]; + for (eindex = 0; eindex < elen; eindex++) { + enow = e[eindex]; + Two_Sum(Q, enow, Qnew, hh); + Q = Qnew; + if (hh != 0.0) { + h[hindex++] = hh; + } + } + h[hindex] = Q; + hlast = hindex; + for (findex = 1; findex < flen; findex++) { + hindex = 0; + Q = f[findex]; + for (eindex = 0; eindex <= hlast; eindex++) { + enow = h[eindex]; + Two_Sum(Q, enow, Qnew, hh); + Q = Qnew; + if (hh != 0) { + h[hindex++] = hh; + } + } + h[hindex] = Q; + hlast = hindex; + } + return hlast + 1; +} + +/*****************************************************************************/ +/* */ +/* fast_expansion_sum() Sum two expansions. */ +/* */ +/* Sets h = e + f. See the long version of my paper for details. */ +/* */ +/* If round-to-even is used (as with IEEE 754), maintains the strongly */ +/* nonoverlapping property. (That is, if e is strongly nonoverlapping, h */ +/* will be also.) Does NOT maintain the nonoverlapping or nonadjacent */ +/* properties. */ +/* */ +/*****************************************************************************/ + +static int fast_expansion_sum(int elen, REAL *e, int flen, REAL *f, REAL *h) +/* h cannot be e or f. */ +{ + REAL Q; + INEXACT REAL Qnew; + INEXACT REAL bvirt; + REAL avirt, bround, around; + int eindex, findex, hindex; + REAL enow, fnow; + + enow = e[0]; + fnow = f[0]; + eindex = findex = 0; + if ((fnow > enow) == (fnow > -enow)) { + Q = enow; + enow = e[++eindex]; + } else { + Q = fnow; + fnow = f[++findex]; + } + hindex = 0; + if ((eindex < elen) && (findex < flen)) { + if ((fnow > enow) == (fnow > -enow)) { + Fast_Two_Sum(enow, Q, Qnew, h[0]); + enow = e[++eindex]; + } else { + Fast_Two_Sum(fnow, Q, Qnew, h[0]); + fnow = f[++findex]; + } + Q = Qnew; + hindex = 1; + while ((eindex < elen) && (findex < flen)) { + if ((fnow > enow) == (fnow > -enow)) { + Two_Sum(Q, enow, Qnew, h[hindex]); + enow = e[++eindex]; + } else { + Two_Sum(Q, fnow, Qnew, h[hindex]); + fnow = f[++findex]; + } + Q = Qnew; + hindex++; + } + } + while (eindex < elen) { + Two_Sum(Q, enow, Qnew, h[hindex]); + enow = e[++eindex]; + Q = Qnew; + hindex++; + } + while (findex < flen) { + Two_Sum(Q, fnow, Qnew, h[hindex]); + fnow = f[++findex]; + Q = Qnew; + hindex++; + } + h[hindex] = Q; + return hindex + 1; +} + +/*****************************************************************************/ +/* */ +/* fast_expansion_sum_zeroelim() Sum two expansions, eliminating zero */ +/* components from the output expansion. */ +/* */ +/* Sets h = e + f. See the long version of my paper for details. */ +/* */ +/* If round-to-even is used (as with IEEE 754), maintains the strongly */ +/* nonoverlapping property. (That is, if e is strongly nonoverlapping, h */ +/* will be also.) Does NOT maintain the nonoverlapping or nonadjacent */ +/* properties. */ +/* */ +/*****************************************************************************/ +static +int fast_expansion_sum_zeroelim(int elen, REAL *e, int flen, REAL *f, REAL *h) +/* h cannot be e or f. */ +{ + REAL Q; + INEXACT REAL Qnew; + INEXACT REAL hh; + INEXACT REAL bvirt; + REAL avirt, bround, around; + int eindex, findex, hindex; + REAL enow, fnow; + + enow = e[0]; + fnow = f[0]; + eindex = findex = 0; + if ((fnow > enow) == (fnow > -enow)) { + Q = enow; + enow = e[++eindex]; + } else { + Q = fnow; + fnow = f[++findex]; + } + hindex = 0; + if ((eindex < elen) && (findex < flen)) { + if ((fnow > enow) == (fnow > -enow)) { + Fast_Two_Sum(enow, Q, Qnew, hh); + enow = e[++eindex]; + } else { + Fast_Two_Sum(fnow, Q, Qnew, hh); + fnow = f[++findex]; + } + Q = Qnew; + if (hh != 0.0) { + h[hindex++] = hh; + } + while ((eindex < elen) && (findex < flen)) { + if ((fnow > enow) == (fnow > -enow)) { + Two_Sum(Q, enow, Qnew, hh); + enow = e[++eindex]; + } else { + Two_Sum(Q, fnow, Qnew, hh); + fnow = f[++findex]; + } + Q = Qnew; + if (hh != 0.0) { + h[hindex++] = hh; + } + } + } + while (eindex < elen) { + Two_Sum(Q, enow, Qnew, hh); + enow = e[++eindex]; + Q = Qnew; + if (hh != 0.0) { + h[hindex++] = hh; + } + } + while (findex < flen) { + Two_Sum(Q, fnow, Qnew, hh); + fnow = f[++findex]; + Q = Qnew; + if (hh != 0.0) { + h[hindex++] = hh; + } + } + if ((Q != 0.0) || (hindex == 0)) { + h[hindex++] = Q; + } + return hindex; +} + +/*****************************************************************************/ +/* */ +/* linear_expansion_sum() Sum two expansions. */ +/* */ +/* Sets h = e + f. See either version of my paper for details. */ +/* */ +/* Maintains the nonoverlapping property. (That is, if e is */ +/* nonoverlapping, h will be also.) */ +/* */ +/*****************************************************************************/ + +int linear_expansion_sum(int elen, REAL *e, int flen, REAL *f, REAL *h) +/* h cannot be e or f. */ +{ + REAL Q, q; + INEXACT REAL Qnew; + INEXACT REAL R; + INEXACT REAL bvirt; + REAL avirt, bround, around; + int eindex, findex, hindex; + REAL enow, fnow; + REAL g0; + + enow = e[0]; + fnow = f[0]; + eindex = findex = 0; + if ((fnow > enow) == (fnow > -enow)) { + g0 = enow; + enow = e[++eindex]; + } else { + g0 = fnow; + fnow = f[++findex]; + } + if ((eindex < elen) && ((findex >= flen) + || ((fnow > enow) == (fnow > -enow)))) { + Fast_Two_Sum(enow, g0, Qnew, q); + enow = e[++eindex]; + } else { + Fast_Two_Sum(fnow, g0, Qnew, q); + fnow = f[++findex]; + } + Q = Qnew; + for (hindex = 0; hindex < elen + flen - 2; hindex++) { + if ((eindex < elen) && ((findex >= flen) + || ((fnow > enow) == (fnow > -enow)))) { + Fast_Two_Sum(enow, q, R, h[hindex]); + enow = e[++eindex]; + } else { + Fast_Two_Sum(fnow, q, R, h[hindex]); + fnow = f[++findex]; + } + Two_Sum(Q, R, Qnew, q); + Q = Qnew; + } + h[hindex] = q; + h[hindex + 1] = Q; + return hindex + 2; +} + +/*****************************************************************************/ +/* */ +/* linear_expansion_sum_zeroelim() Sum two expansions, eliminating zero */ +/* components from the output expansion. */ +/* */ +/* Sets h = e + f. See either version of my paper for details. */ +/* */ +/* Maintains the nonoverlapping property. (That is, if e is */ +/* nonoverlapping, h will be also.) */ +/* */ +/*****************************************************************************/ + +int linear_expansion_sum_zeroelim(int elen, REAL *e, int flen, REAL *f, + REAL *h) +/* h cannot be e or f. */ +{ + REAL Q, q, hh; + INEXACT REAL Qnew; + INEXACT REAL R; + INEXACT REAL bvirt; + REAL avirt, bround, around; + int eindex, findex, hindex; + int count; + REAL enow, fnow; + REAL g0; + + enow = e[0]; + fnow = f[0]; + eindex = findex = 0; + hindex = 0; + if ((fnow > enow) == (fnow > -enow)) { + g0 = enow; + enow = e[++eindex]; + } else { + g0 = fnow; + fnow = f[++findex]; + } + if ((eindex < elen) && ((findex >= flen) + || ((fnow > enow) == (fnow > -enow)))) { + Fast_Two_Sum(enow, g0, Qnew, q); + enow = e[++eindex]; + } else { + Fast_Two_Sum(fnow, g0, Qnew, q); + fnow = f[++findex]; + } + Q = Qnew; + for (count = 2; count < elen + flen; count++) { + if ((eindex < elen) && ((findex >= flen) + || ((fnow > enow) == (fnow > -enow)))) { + Fast_Two_Sum(enow, q, R, hh); + enow = e[++eindex]; + } else { + Fast_Two_Sum(fnow, q, R, hh); + fnow = f[++findex]; + } + Two_Sum(Q, R, Qnew, q); + Q = Qnew; + if (hh != 0) { + h[hindex++] = hh; + } + } + if (q != 0) { + h[hindex++] = q; + } + if ((Q != 0.0) || (hindex == 0)) { + h[hindex++] = Q; + } + return hindex; +} + +/*****************************************************************************/ +/* */ +/* scale_expansion() Multiply an expansion by a scalar. */ +/* */ +/* Sets h = be. See either version of my paper for details. */ +/* */ +/* Maintains the nonoverlapping property. If round-to-even is used (as */ +/* with IEEE 754), maintains the strongly nonoverlapping and nonadjacent */ +/* properties as well. (That is, if e has one of these properties, so */ +/* will h.) */ +/* */ +/*****************************************************************************/ +static +int scale_expansion(int elen, REAL *e, REAL b, REAL *h) +/* e and h cannot be the same. */ +{ + INEXACT REAL Q; + INEXACT REAL sum; + INEXACT REAL product1; + REAL product0; + int eindex, hindex; + REAL enow; + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + + Split(b, bhi, blo); + Two_Product_Presplit(e[0], b, bhi, blo, Q, h[0]); + hindex = 1; + for (eindex = 1; eindex < elen; eindex++) { + enow = e[eindex]; + Two_Product_Presplit(enow, b, bhi, blo, product1, product0); + Two_Sum(Q, product0, sum, h[hindex]); + hindex++; + Two_Sum(product1, sum, Q, h[hindex]); + hindex++; + } + h[hindex] = Q; + return elen + elen; +} + +/*****************************************************************************/ +/* */ +/* scale_expansion_zeroelim() Multiply an expansion by a scalar, */ +/* eliminating zero components from the */ +/* output expansion. */ +/* */ +/* Sets h = be. See either version of my paper for details. */ +/* */ +/* Maintains the nonoverlapping property. If round-to-even is used (as */ +/* with IEEE 754), maintains the strongly nonoverlapping and nonadjacent */ +/* properties as well. (That is, if e has one of these properties, so */ +/* will h.) */ +/* */ +/*****************************************************************************/ +static +int scale_expansion_zeroelim(int elen, REAL *e, REAL b, REAL *h) +/* e and h cannot be the same. */ +{ + INEXACT REAL Q, sum; + REAL hh; + INEXACT REAL product1; + REAL product0; + int eindex, hindex; + REAL enow; + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + + Split(b, bhi, blo); + Two_Product_Presplit(e[0], b, bhi, blo, Q, hh); + hindex = 0; + if (hh != 0) { + h[hindex++] = hh; + } + for (eindex = 1; eindex < elen; eindex++) { + enow = e[eindex]; + Two_Product_Presplit(enow, b, bhi, blo, product1, product0); + Two_Sum(Q, product0, sum, hh); + if (hh != 0) { + h[hindex++] = hh; + } + Fast_Two_Sum(product1, sum, Q, hh); + if (hh != 0) { + h[hindex++] = hh; + } + } + if ((Q != 0.0) || (hindex == 0)) { + h[hindex++] = Q; + } + return hindex; +} + +/*****************************************************************************/ +/* */ +/* compress() Compress an expansion. */ +/* */ +/* See the long version of my paper for details. */ +/* */ +/* Maintains the nonoverlapping property. If round-to-even is used (as */ +/* with IEEE 754), then any nonoverlapping expansion is converted to a */ +/* nonadjacent expansion. */ +/* */ +/*****************************************************************************/ +static +int compress(int elen, REAL *e, REAL *h) +/* e and h may be the same. */ +{ + REAL Q, q; + INEXACT REAL Qnew; + int eindex, hindex; + INEXACT REAL bvirt; + REAL enow, hnow; + int top, bottom; + + bottom = elen - 1; + Q = e[bottom]; + for (eindex = elen - 2; eindex >= 0; eindex--) { + enow = e[eindex]; + Fast_Two_Sum(Q, enow, Qnew, q); + if (q != 0) { + h[bottom--] = Qnew; + Q = q; + } else { + Q = Qnew; + } + } + top = 0; + for (hindex = bottom + 1; hindex < elen; hindex++) { + hnow = h[hindex]; + Fast_Two_Sum(hnow, Q, Qnew, q); + if (q != 0) { + h[top++] = q; + } + Q = Qnew; + } + h[top] = Q; + return top + 1; +} + +/*****************************************************************************/ +/* */ +/* estimate() Produce a one-word estimate of an expansion's value. */ +/* */ +/* See either version of my paper for details. */ +/* */ +/*****************************************************************************/ +static +REAL estimate(int elen, REAL *e) +{ + REAL Q; + int eindex; + + Q = e[0]; + for (eindex = 1; eindex < elen; eindex++) { + Q += e[eindex]; + } + return Q; +} + +/*****************************************************************************/ +/* */ +/* orient2dfast() Approximate 2D orientation test. Nonrobust. */ +/* orient2dexact() Exact 2D orientation test. Robust. */ +/* orient2dslow() Another exact 2D orientation test. Robust. */ +/* orient2d() Adaptive exact 2D orientation test. Robust. */ +/* */ +/* Return a positive value if the points pa, pb, and pc occur */ +/* in counterclockwise order; a negative value if they occur */ +/* in clockwise order; and zero if they are collinear. The */ +/* result is also a rough approximation of twice the signed */ +/* area of the triangle defined by the three points. */ +/* */ +/* Only the first and last routine should be used; the middle two are for */ +/* timings. */ +/* */ +/* The last three use exact arithmetic to ensure a correct answer. The */ +/* result returned is the determinant of a matrix. In orient2d() only, */ +/* this determinant is computed adaptively, in the sense that exact */ +/* arithmetic is used only to the degree it is needed to ensure that the */ +/* returned value has the correct sign. Hence, orient2d() is usually quite */ +/* fast, but will run more slowly when the input points are collinear or */ +/* nearly so. */ +/* */ +/*****************************************************************************/ +static +REAL orient2dfast(REAL *pa, REAL *pb, REAL *pc) +{ + REAL acx, bcx, acy, bcy; + + acx = pa[0] - pc[0]; + bcx = pb[0] - pc[0]; + acy = pa[1] - pc[1]; + bcy = pb[1] - pc[1]; + return acx * bcy - acy * bcx; +} + +//static +REAL orient2dexact(REAL *pa, REAL *pb, REAL *pc) +{ + INEXACT REAL axby1, axcy1, bxcy1, bxay1, cxay1, cxby1; + REAL axby0, axcy0, bxcy0, bxay0, cxay0, cxby0; + REAL aterms[4], bterms[4], cterms[4]; + INEXACT REAL aterms3, bterms3, cterms3; + REAL v[8], w[12]; + int vlength, wlength; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j; + REAL _0; + + Two_Product(pa[0], pb[1], axby1, axby0); + Two_Product(pa[0], pc[1], axcy1, axcy0); + Two_Two_Diff(axby1, axby0, axcy1, axcy0, + aterms3, aterms[2], aterms[1], aterms[0]); + aterms[3] = aterms3; + + Two_Product(pb[0], pc[1], bxcy1, bxcy0); + Two_Product(pb[0], pa[1], bxay1, bxay0); + Two_Two_Diff(bxcy1, bxcy0, bxay1, bxay0, + bterms3, bterms[2], bterms[1], bterms[0]); + bterms[3] = bterms3; + + Two_Product(pc[0], pa[1], cxay1, cxay0); + Two_Product(pc[0], pb[1], cxby1, cxby0); + Two_Two_Diff(cxay1, cxay0, cxby1, cxby0, + cterms3, cterms[2], cterms[1], cterms[0]); + cterms[3] = cterms3; + + vlength = fast_expansion_sum_zeroelim(4, aterms, 4, bterms, v); + wlength = fast_expansion_sum_zeroelim(vlength, v, 4, cterms, w); + + return w[wlength - 1]; +} + +REAL orient2dslow(REAL *pa, REAL *pb, REAL *pc) +{ + INEXACT REAL acx, acy, bcx, bcy; + REAL acxtail, acytail; + REAL bcxtail, bcytail; + REAL negate, negatetail; + REAL axby[8], bxay[8]; + INEXACT REAL axby7, bxay7; + REAL deter[16]; + int deterlen; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL a0hi, a0lo, a1hi, a1lo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j, _k, _l, _m, _n; + REAL _0, _1, _2; + + Two_Diff(pa[0], pc[0], acx, acxtail); + Two_Diff(pa[1], pc[1], acy, acytail); + Two_Diff(pb[0], pc[0], bcx, bcxtail); + Two_Diff(pb[1], pc[1], bcy, bcytail); + + Two_Two_Product(acx, acxtail, bcy, bcytail, + axby7, axby[6], axby[5], axby[4], + axby[3], axby[2], axby[1], axby[0]); + axby[7] = axby7; + negate = -acy; + negatetail = -acytail; + Two_Two_Product(bcx, bcxtail, negate, negatetail, + bxay7, bxay[6], bxay[5], bxay[4], + bxay[3], bxay[2], bxay[1], bxay[0]); + bxay[7] = bxay7; + + deterlen = fast_expansion_sum_zeroelim(8, axby, 8, bxay, deter); + + return deter[deterlen - 1]; +} + +REAL orient2dadapt(REAL *pa, REAL *pb, REAL *pc, REAL detsum) +{ + INEXACT REAL acx, acy, bcx, bcy; + REAL acxtail, acytail, bcxtail, bcytail; + INEXACT REAL detleft, detright; + REAL detlefttail, detrighttail; + REAL det, errbound; + REAL B[4], C1[8], C2[12], D[16]; + INEXACT REAL B3; + int C1length, C2length, Dlength; + REAL u[4]; + INEXACT REAL u3; + INEXACT REAL s1, t1; + REAL s0, t0; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j; + REAL _0; + + acx = (REAL) (pa[0] - pc[0]); + bcx = (REAL) (pb[0] - pc[0]); + acy = (REAL) (pa[1] - pc[1]); + bcy = (REAL) (pb[1] - pc[1]); + + Two_Product(acx, bcy, detleft, detlefttail); + Two_Product(acy, bcx, detright, detrighttail); + + Two_Two_Diff(detleft, detlefttail, detright, detrighttail, + B3, B[2], B[1], B[0]); + B[3] = B3; + + det = estimate(4, B); + errbound = ccwerrboundB * detsum; + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + Two_Diff_Tail(pa[0], pc[0], acx, acxtail); + Two_Diff_Tail(pb[0], pc[0], bcx, bcxtail); + Two_Diff_Tail(pa[1], pc[1], acy, acytail); + Two_Diff_Tail(pb[1], pc[1], bcy, bcytail); + + if ((acxtail == 0.0) && (acytail == 0.0) + && (bcxtail == 0.0) && (bcytail == 0.0)) { + return det; + } + + errbound = ccwerrboundC * detsum + resulterrbound * Absolute(det); + det += (acx * bcytail + bcy * acxtail) + - (acy * bcxtail + bcx * acytail); + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + Two_Product(acxtail, bcy, s1, s0); + Two_Product(acytail, bcx, t1, t0); + Two_Two_Diff(s1, s0, t1, t0, u3, u[2], u[1], u[0]); + u[3] = u3; + C1length = fast_expansion_sum_zeroelim(4, B, 4, u, C1); + + Two_Product(acx, bcytail, s1, s0); + Two_Product(acy, bcxtail, t1, t0); + Two_Two_Diff(s1, s0, t1, t0, u3, u[2], u[1], u[0]); + u[3] = u3; + C2length = fast_expansion_sum_zeroelim(C1length, C1, 4, u, C2); + + Two_Product(acxtail, bcytail, s1, s0); + Two_Product(acytail, bcxtail, t1, t0); + Two_Two_Diff(s1, s0, t1, t0, u3, u[2], u[1], u[0]); + u[3] = u3; + Dlength = fast_expansion_sum_zeroelim(C2length, C2, 4, u, D); + + return(D[Dlength - 1]); +} + +REAL orient2d(REAL *pa, REAL *pb, REAL *pc) +{ + REAL detleft, detright, det; + REAL detsum, errbound; + + detleft = (pa[0] - pc[0]) * (pb[1] - pc[1]); + detright = (pa[1] - pc[1]) * (pb[0] - pc[0]); + det = detleft - detright; + + if (detleft > 0.0) { + if (detright <= 0.0) { + return det; + } else { + detsum = detleft + detright; + } + } else if (detleft < 0.0) { + if (detright >= 0.0) { + return det; + } else { + detsum = -detleft - detright; + } + } else { + return det; + } + + errbound = ccwerrboundA * detsum; + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + return orient2dadapt(pa, pb, pc, detsum); +} + +/*****************************************************************************/ +/* */ +/* orient3dfast() Approximate 3D orientation test. Nonrobust. */ +/* orient3dexact() Exact 3D orientation test. Robust. */ +/* orient3dslow() Another exact 3D orientation test. Robust. */ +/* orient3d() Adaptive exact 3D orientation test. Robust. */ +/* */ +/* Return a positive value if the point pd lies below the */ +/* plane passing through pa, pb, and pc; "below" is defined so */ +/* that pa, pb, and pc appear in counterclockwise order when */ +/* viewed from above the plane. Returns a negative value if */ +/* pd lies above the plane. Returns zero if the points are */ +/* coplanar. The result is also a rough approximation of six */ +/* times the signed volume of the tetrahedron defined by the */ +/* four points. */ +/* */ +/* Only the first and last routine should be used; the middle two are for */ +/* timings. */ +/* */ +/* The last three use exact arithmetic to ensure a correct answer. The */ +/* result returned is the determinant of a matrix. In orient3d() only, */ +/* this determinant is computed adaptively, in the sense that exact */ +/* arithmetic is used only to the degree it is needed to ensure that the */ +/* returned value has the correct sign. Hence, orient3d() is usually quite */ +/* fast, but will run more slowly when the input points are coplanar or */ +/* nearly so. */ +/* */ +/*****************************************************************************/ + +REAL orient3dfast(REAL *pa, REAL *pb, REAL *pc, REAL *pd) +{ + REAL adx, bdx, cdx; + REAL ady, bdy, cdy; + REAL adz, bdz, cdz; + + adx = pa[0] - pd[0]; + bdx = pb[0] - pd[0]; + cdx = pc[0] - pd[0]; + ady = pa[1] - pd[1]; + bdy = pb[1] - pd[1]; + cdy = pc[1] - pd[1]; + adz = pa[2] - pd[2]; + bdz = pb[2] - pd[2]; + cdz = pc[2] - pd[2]; + + return adx * (bdy * cdz - bdz * cdy) + + bdx * (cdy * adz - cdz * ady) + + cdx * (ady * bdz - adz * bdy); +} + +REAL orient3dexact(REAL *pa, REAL *pb, REAL *pc, REAL *pd) +{ + INEXACT REAL axby1, bxcy1, cxdy1, dxay1, axcy1, bxdy1; + INEXACT REAL bxay1, cxby1, dxcy1, axdy1, cxay1, dxby1; + REAL axby0, bxcy0, cxdy0, dxay0, axcy0, bxdy0; + REAL bxay0, cxby0, dxcy0, axdy0, cxay0, dxby0; + REAL ab[4], bc[4], cd[4], da[4], ac[4], bd[4]; + REAL temp8[8]; + int templen; + REAL abc[12], bcd[12], cda[12], dab[12]; + int abclen, bcdlen, cdalen, dablen; + REAL adet[24], bdet[24], cdet[24], ddet[24]; + int alen, blen, clen, dlen; + REAL abdet[48], cddet[48]; + int ablen, cdlen; + REAL deter[96]; + int deterlen; + int i; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j; + REAL _0; + + Two_Product(pa[0], pb[1], axby1, axby0); + Two_Product(pb[0], pa[1], bxay1, bxay0); + Two_Two_Diff(axby1, axby0, bxay1, bxay0, ab[3], ab[2], ab[1], ab[0]); + + Two_Product(pb[0], pc[1], bxcy1, bxcy0); + Two_Product(pc[0], pb[1], cxby1, cxby0); + Two_Two_Diff(bxcy1, bxcy0, cxby1, cxby0, bc[3], bc[2], bc[1], bc[0]); + + Two_Product(pc[0], pd[1], cxdy1, cxdy0); + Two_Product(pd[0], pc[1], dxcy1, dxcy0); + Two_Two_Diff(cxdy1, cxdy0, dxcy1, dxcy0, cd[3], cd[2], cd[1], cd[0]); + + Two_Product(pd[0], pa[1], dxay1, dxay0); + Two_Product(pa[0], pd[1], axdy1, axdy0); + Two_Two_Diff(dxay1, dxay0, axdy1, axdy0, da[3], da[2], da[1], da[0]); + + Two_Product(pa[0], pc[1], axcy1, axcy0); + Two_Product(pc[0], pa[1], cxay1, cxay0); + Two_Two_Diff(axcy1, axcy0, cxay1, cxay0, ac[3], ac[2], ac[1], ac[0]); + + Two_Product(pb[0], pd[1], bxdy1, bxdy0); + Two_Product(pd[0], pb[1], dxby1, dxby0); + Two_Two_Diff(bxdy1, bxdy0, dxby1, dxby0, bd[3], bd[2], bd[1], bd[0]); + + templen = fast_expansion_sum_zeroelim(4, cd, 4, da, temp8); + cdalen = fast_expansion_sum_zeroelim(templen, temp8, 4, ac, cda); + templen = fast_expansion_sum_zeroelim(4, da, 4, ab, temp8); + dablen = fast_expansion_sum_zeroelim(templen, temp8, 4, bd, dab); + for (i = 0; i < 4; i++) { + bd[i] = -bd[i]; + ac[i] = -ac[i]; + } + templen = fast_expansion_sum_zeroelim(4, ab, 4, bc, temp8); + abclen = fast_expansion_sum_zeroelim(templen, temp8, 4, ac, abc); + templen = fast_expansion_sum_zeroelim(4, bc, 4, cd, temp8); + bcdlen = fast_expansion_sum_zeroelim(templen, temp8, 4, bd, bcd); + + alen = scale_expansion_zeroelim(bcdlen, bcd, pa[2], adet); + blen = scale_expansion_zeroelim(cdalen, cda, -pb[2], bdet); + clen = scale_expansion_zeroelim(dablen, dab, pc[2], cdet); + dlen = scale_expansion_zeroelim(abclen, abc, -pd[2], ddet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + cdlen = fast_expansion_sum_zeroelim(clen, cdet, dlen, ddet, cddet); + deterlen = fast_expansion_sum_zeroelim(ablen, abdet, cdlen, cddet, deter); + + return deter[deterlen - 1]; +} + +REAL orient3dslow(REAL *pa, REAL *pb, REAL *pc, REAL *pd) +{ + INEXACT REAL adx, ady, adz, bdx, bdy, bdz, cdx, cdy, cdz; + REAL adxtail, adytail, adztail; + REAL bdxtail, bdytail, bdztail; + REAL cdxtail, cdytail, cdztail; + REAL negate, negatetail; + INEXACT REAL axby7, bxcy7, axcy7, bxay7, cxby7, cxay7; + REAL axby[8], bxcy[8], axcy[8], bxay[8], cxby[8], cxay[8]; + REAL temp16[16], temp32[32], temp32t[32]; + int temp16len, temp32len, temp32tlen; + REAL adet[64], bdet[64], cdet[64]; + int alen, blen, clen; + REAL abdet[128]; + int ablen; + REAL deter[192]; + int deterlen; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL a0hi, a0lo, a1hi, a1lo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j, _k, _l, _m, _n; + REAL _0, _1, _2; + + Two_Diff(pa[0], pd[0], adx, adxtail); + Two_Diff(pa[1], pd[1], ady, adytail); + Two_Diff(pa[2], pd[2], adz, adztail); + Two_Diff(pb[0], pd[0], bdx, bdxtail); + Two_Diff(pb[1], pd[1], bdy, bdytail); + Two_Diff(pb[2], pd[2], bdz, bdztail); + Two_Diff(pc[0], pd[0], cdx, cdxtail); + Two_Diff(pc[1], pd[1], cdy, cdytail); + Two_Diff(pc[2], pd[2], cdz, cdztail); + + Two_Two_Product(adx, adxtail, bdy, bdytail, + axby7, axby[6], axby[5], axby[4], + axby[3], axby[2], axby[1], axby[0]); + axby[7] = axby7; + negate = -ady; + negatetail = -adytail; + Two_Two_Product(bdx, bdxtail, negate, negatetail, + bxay7, bxay[6], bxay[5], bxay[4], + bxay[3], bxay[2], bxay[1], bxay[0]); + bxay[7] = bxay7; + Two_Two_Product(bdx, bdxtail, cdy, cdytail, + bxcy7, bxcy[6], bxcy[5], bxcy[4], + bxcy[3], bxcy[2], bxcy[1], bxcy[0]); + bxcy[7] = bxcy7; + negate = -bdy; + negatetail = -bdytail; + Two_Two_Product(cdx, cdxtail, negate, negatetail, + cxby7, cxby[6], cxby[5], cxby[4], + cxby[3], cxby[2], cxby[1], cxby[0]); + cxby[7] = cxby7; + Two_Two_Product(cdx, cdxtail, ady, adytail, + cxay7, cxay[6], cxay[5], cxay[4], + cxay[3], cxay[2], cxay[1], cxay[0]); + cxay[7] = cxay7; + negate = -cdy; + negatetail = -cdytail; + Two_Two_Product(adx, adxtail, negate, negatetail, + axcy7, axcy[6], axcy[5], axcy[4], + axcy[3], axcy[2], axcy[1], axcy[0]); + axcy[7] = axcy7; + + temp16len = fast_expansion_sum_zeroelim(8, bxcy, 8, cxby, temp16); + temp32len = scale_expansion_zeroelim(temp16len, temp16, adz, temp32); + temp32tlen = scale_expansion_zeroelim(temp16len, temp16, adztail, temp32t); + alen = fast_expansion_sum_zeroelim(temp32len, temp32, temp32tlen, temp32t, + adet); + + temp16len = fast_expansion_sum_zeroelim(8, cxay, 8, axcy, temp16); + temp32len = scale_expansion_zeroelim(temp16len, temp16, bdz, temp32); + temp32tlen = scale_expansion_zeroelim(temp16len, temp16, bdztail, temp32t); + blen = fast_expansion_sum_zeroelim(temp32len, temp32, temp32tlen, temp32t, + bdet); + + temp16len = fast_expansion_sum_zeroelim(8, axby, 8, bxay, temp16); + temp32len = scale_expansion_zeroelim(temp16len, temp16, cdz, temp32); + temp32tlen = scale_expansion_zeroelim(temp16len, temp16, cdztail, temp32t); + clen = fast_expansion_sum_zeroelim(temp32len, temp32, temp32tlen, temp32t, + cdet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + deterlen = fast_expansion_sum_zeroelim(ablen, abdet, clen, cdet, deter); + + return deter[deterlen - 1]; +} + +REAL orient3dadapt(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL permanent) +{ + INEXACT REAL adx, bdx, cdx, ady, bdy, cdy, adz, bdz, cdz; + REAL det, errbound; + + INEXACT REAL bdxcdy1, cdxbdy1, cdxady1, adxcdy1, adxbdy1, bdxady1; + REAL bdxcdy0, cdxbdy0, cdxady0, adxcdy0, adxbdy0, bdxady0; + REAL bc[4], ca[4], ab[4]; + INEXACT REAL bc3, ca3, ab3; + REAL adet[8], bdet[8], cdet[8]; + int alen, blen, clen; + REAL abdet[16]; + int ablen; + REAL *finnow, *finother, *finswap; + REAL fin1[192], fin2[192]; + int finlength; + + + REAL adxtail, bdxtail, cdxtail; + REAL adytail, bdytail, cdytail; + REAL adztail, bdztail, cdztail; + INEXACT REAL at_blarge, at_clarge; + INEXACT REAL bt_clarge, bt_alarge; + INEXACT REAL ct_alarge, ct_blarge; + REAL at_b[4], at_c[4], bt_c[4], bt_a[4], ct_a[4], ct_b[4]; + int at_blen, at_clen, bt_clen, bt_alen, ct_alen, ct_blen; + INEXACT REAL bdxt_cdy1, cdxt_bdy1, cdxt_ady1; + INEXACT REAL adxt_cdy1, adxt_bdy1, bdxt_ady1; + REAL bdxt_cdy0, cdxt_bdy0, cdxt_ady0; + REAL adxt_cdy0, adxt_bdy0, bdxt_ady0; + INEXACT REAL bdyt_cdx1, cdyt_bdx1, cdyt_adx1; + INEXACT REAL adyt_cdx1, adyt_bdx1, bdyt_adx1; + REAL bdyt_cdx0, cdyt_bdx0, cdyt_adx0; + REAL adyt_cdx0, adyt_bdx0, bdyt_adx0; + REAL bct[8], cat[8], abt[8]; + int bctlen, catlen, abtlen; + INEXACT REAL bdxt_cdyt1, cdxt_bdyt1, cdxt_adyt1; + INEXACT REAL adxt_cdyt1, adxt_bdyt1, bdxt_adyt1; + REAL bdxt_cdyt0, cdxt_bdyt0, cdxt_adyt0; + REAL adxt_cdyt0, adxt_bdyt0, bdxt_adyt0; + REAL u[4], v[12], w[16]; + INEXACT REAL u3; + int vlength, wlength; + REAL negate; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j, _k; + REAL _0; + + + adx = (REAL) (pa[0] - pd[0]); + bdx = (REAL) (pb[0] - pd[0]); + cdx = (REAL) (pc[0] - pd[0]); + ady = (REAL) (pa[1] - pd[1]); + bdy = (REAL) (pb[1] - pd[1]); + cdy = (REAL) (pc[1] - pd[1]); + adz = (REAL) (pa[2] - pd[2]); + bdz = (REAL) (pb[2] - pd[2]); + cdz = (REAL) (pc[2] - pd[2]); + + Two_Product(bdx, cdy, bdxcdy1, bdxcdy0); + Two_Product(cdx, bdy, cdxbdy1, cdxbdy0); + Two_Two_Diff(bdxcdy1, bdxcdy0, cdxbdy1, cdxbdy0, bc3, bc[2], bc[1], bc[0]); + bc[3] = bc3; + alen = scale_expansion_zeroelim(4, bc, adz, adet); + + Two_Product(cdx, ady, cdxady1, cdxady0); + Two_Product(adx, cdy, adxcdy1, adxcdy0); + Two_Two_Diff(cdxady1, cdxady0, adxcdy1, adxcdy0, ca3, ca[2], ca[1], ca[0]); + ca[3] = ca3; + blen = scale_expansion_zeroelim(4, ca, bdz, bdet); + + Two_Product(adx, bdy, adxbdy1, adxbdy0); + Two_Product(bdx, ady, bdxady1, bdxady0); + Two_Two_Diff(adxbdy1, adxbdy0, bdxady1, bdxady0, ab3, ab[2], ab[1], ab[0]); + ab[3] = ab3; + clen = scale_expansion_zeroelim(4, ab, cdz, cdet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + finlength = fast_expansion_sum_zeroelim(ablen, abdet, clen, cdet, fin1); + + det = estimate(finlength, fin1); + errbound = o3derrboundB * permanent; + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + Two_Diff_Tail(pa[0], pd[0], adx, adxtail); + Two_Diff_Tail(pb[0], pd[0], bdx, bdxtail); + Two_Diff_Tail(pc[0], pd[0], cdx, cdxtail); + Two_Diff_Tail(pa[1], pd[1], ady, adytail); + Two_Diff_Tail(pb[1], pd[1], bdy, bdytail); + Two_Diff_Tail(pc[1], pd[1], cdy, cdytail); + Two_Diff_Tail(pa[2], pd[2], adz, adztail); + Two_Diff_Tail(pb[2], pd[2], bdz, bdztail); + Two_Diff_Tail(pc[2], pd[2], cdz, cdztail); + + if ((adxtail == 0.0) && (bdxtail == 0.0) && (cdxtail == 0.0) + && (adytail == 0.0) && (bdytail == 0.0) && (cdytail == 0.0) + && (adztail == 0.0) && (bdztail == 0.0) && (cdztail == 0.0)) { + return det; + } + + errbound = o3derrboundC * permanent + resulterrbound * Absolute(det); + det += (adz * ((bdx * cdytail + cdy * bdxtail) + - (bdy * cdxtail + cdx * bdytail)) + + adztail * (bdx * cdy - bdy * cdx)) + + (bdz * ((cdx * adytail + ady * cdxtail) + - (cdy * adxtail + adx * cdytail)) + + bdztail * (cdx * ady - cdy * adx)) + + (cdz * ((adx * bdytail + bdy * adxtail) + - (ady * bdxtail + bdx * adytail)) + + cdztail * (adx * bdy - ady * bdx)); + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + finnow = fin1; + finother = fin2; + + if (adxtail == 0.0) { + if (adytail == 0.0) { + at_b[0] = 0.0; + at_blen = 1; + at_c[0] = 0.0; + at_clen = 1; + } else { + negate = -adytail; + Two_Product(negate, bdx, at_blarge, at_b[0]); + at_b[1] = at_blarge; + at_blen = 2; + Two_Product(adytail, cdx, at_clarge, at_c[0]); + at_c[1] = at_clarge; + at_clen = 2; + } + } else { + if (adytail == 0.0) { + Two_Product(adxtail, bdy, at_blarge, at_b[0]); + at_b[1] = at_blarge; + at_blen = 2; + negate = -adxtail; + Two_Product(negate, cdy, at_clarge, at_c[0]); + at_c[1] = at_clarge; + at_clen = 2; + } else { + Two_Product(adxtail, bdy, adxt_bdy1, adxt_bdy0); + Two_Product(adytail, bdx, adyt_bdx1, adyt_bdx0); + Two_Two_Diff(adxt_bdy1, adxt_bdy0, adyt_bdx1, adyt_bdx0, + at_blarge, at_b[2], at_b[1], at_b[0]); + at_b[3] = at_blarge; + at_blen = 4; + Two_Product(adytail, cdx, adyt_cdx1, adyt_cdx0); + Two_Product(adxtail, cdy, adxt_cdy1, adxt_cdy0); + Two_Two_Diff(adyt_cdx1, adyt_cdx0, adxt_cdy1, adxt_cdy0, + at_clarge, at_c[2], at_c[1], at_c[0]); + at_c[3] = at_clarge; + at_clen = 4; + } + } + if (bdxtail == 0.0) { + if (bdytail == 0.0) { + bt_c[0] = 0.0; + bt_clen = 1; + bt_a[0] = 0.0; + bt_alen = 1; + } else { + negate = -bdytail; + Two_Product(negate, cdx, bt_clarge, bt_c[0]); + bt_c[1] = bt_clarge; + bt_clen = 2; + Two_Product(bdytail, adx, bt_alarge, bt_a[0]); + bt_a[1] = bt_alarge; + bt_alen = 2; + } + } else { + if (bdytail == 0.0) { + Two_Product(bdxtail, cdy, bt_clarge, bt_c[0]); + bt_c[1] = bt_clarge; + bt_clen = 2; + negate = -bdxtail; + Two_Product(negate, ady, bt_alarge, bt_a[0]); + bt_a[1] = bt_alarge; + bt_alen = 2; + } else { + Two_Product(bdxtail, cdy, bdxt_cdy1, bdxt_cdy0); + Two_Product(bdytail, cdx, bdyt_cdx1, bdyt_cdx0); + Two_Two_Diff(bdxt_cdy1, bdxt_cdy0, bdyt_cdx1, bdyt_cdx0, + bt_clarge, bt_c[2], bt_c[1], bt_c[0]); + bt_c[3] = bt_clarge; + bt_clen = 4; + Two_Product(bdytail, adx, bdyt_adx1, bdyt_adx0); + Two_Product(bdxtail, ady, bdxt_ady1, bdxt_ady0); + Two_Two_Diff(bdyt_adx1, bdyt_adx0, bdxt_ady1, bdxt_ady0, + bt_alarge, bt_a[2], bt_a[1], bt_a[0]); + bt_a[3] = bt_alarge; + bt_alen = 4; + } + } + if (cdxtail == 0.0) { + if (cdytail == 0.0) { + ct_a[0] = 0.0; + ct_alen = 1; + ct_b[0] = 0.0; + ct_blen = 1; + } else { + negate = -cdytail; + Two_Product(negate, adx, ct_alarge, ct_a[0]); + ct_a[1] = ct_alarge; + ct_alen = 2; + Two_Product(cdytail, bdx, ct_blarge, ct_b[0]); + ct_b[1] = ct_blarge; + ct_blen = 2; + } + } else { + if (cdytail == 0.0) { + Two_Product(cdxtail, ady, ct_alarge, ct_a[0]); + ct_a[1] = ct_alarge; + ct_alen = 2; + negate = -cdxtail; + Two_Product(negate, bdy, ct_blarge, ct_b[0]); + ct_b[1] = ct_blarge; + ct_blen = 2; + } else { + Two_Product(cdxtail, ady, cdxt_ady1, cdxt_ady0); + Two_Product(cdytail, adx, cdyt_adx1, cdyt_adx0); + Two_Two_Diff(cdxt_ady1, cdxt_ady0, cdyt_adx1, cdyt_adx0, + ct_alarge, ct_a[2], ct_a[1], ct_a[0]); + ct_a[3] = ct_alarge; + ct_alen = 4; + Two_Product(cdytail, bdx, cdyt_bdx1, cdyt_bdx0); + Two_Product(cdxtail, bdy, cdxt_bdy1, cdxt_bdy0); + Two_Two_Diff(cdyt_bdx1, cdyt_bdx0, cdxt_bdy1, cdxt_bdy0, + ct_blarge, ct_b[2], ct_b[1], ct_b[0]); + ct_b[3] = ct_blarge; + ct_blen = 4; + } + } + + bctlen = fast_expansion_sum_zeroelim(bt_clen, bt_c, ct_blen, ct_b, bct); + wlength = scale_expansion_zeroelim(bctlen, bct, adz, w); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, wlength, w, + finother); + finswap = finnow; finnow = finother; finother = finswap; + + catlen = fast_expansion_sum_zeroelim(ct_alen, ct_a, at_clen, at_c, cat); + wlength = scale_expansion_zeroelim(catlen, cat, bdz, w); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, wlength, w, + finother); + finswap = finnow; finnow = finother; finother = finswap; + + abtlen = fast_expansion_sum_zeroelim(at_blen, at_b, bt_alen, bt_a, abt); + wlength = scale_expansion_zeroelim(abtlen, abt, cdz, w); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, wlength, w, + finother); + finswap = finnow; finnow = finother; finother = finswap; + + if (adztail != 0.0) { + vlength = scale_expansion_zeroelim(4, bc, adztail, v); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, vlength, v, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (bdztail != 0.0) { + vlength = scale_expansion_zeroelim(4, ca, bdztail, v); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, vlength, v, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (cdztail != 0.0) { + vlength = scale_expansion_zeroelim(4, ab, cdztail, v); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, vlength, v, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + + if (adxtail != 0.0) { + if (bdytail != 0.0) { + Two_Product(adxtail, bdytail, adxt_bdyt1, adxt_bdyt0); + Two_One_Product(adxt_bdyt1, adxt_bdyt0, cdz, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + if (cdztail != 0.0) { + Two_One_Product(adxt_bdyt1, adxt_bdyt0, cdztail, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + } + if (cdytail != 0.0) { + negate = -adxtail; + Two_Product(negate, cdytail, adxt_cdyt1, adxt_cdyt0); + Two_One_Product(adxt_cdyt1, adxt_cdyt0, bdz, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + if (bdztail != 0.0) { + Two_One_Product(adxt_cdyt1, adxt_cdyt0, bdztail, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + } + } + if (bdxtail != 0.0) { + if (cdytail != 0.0) { + Two_Product(bdxtail, cdytail, bdxt_cdyt1, bdxt_cdyt0); + Two_One_Product(bdxt_cdyt1, bdxt_cdyt0, adz, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + if (adztail != 0.0) { + Two_One_Product(bdxt_cdyt1, bdxt_cdyt0, adztail, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + } + if (adytail != 0.0) { + negate = -bdxtail; + Two_Product(negate, adytail, bdxt_adyt1, bdxt_adyt0); + Two_One_Product(bdxt_adyt1, bdxt_adyt0, cdz, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + if (cdztail != 0.0) { + Two_One_Product(bdxt_adyt1, bdxt_adyt0, cdztail, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + } + } + if (cdxtail != 0.0) { + if (adytail != 0.0) { + Two_Product(cdxtail, adytail, cdxt_adyt1, cdxt_adyt0); + Two_One_Product(cdxt_adyt1, cdxt_adyt0, bdz, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + if (bdztail != 0.0) { + Two_One_Product(cdxt_adyt1, cdxt_adyt0, bdztail, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + } + if (bdytail != 0.0) { + negate = -cdxtail; + Two_Product(negate, bdytail, cdxt_bdyt1, cdxt_bdyt0); + Two_One_Product(cdxt_bdyt1, cdxt_bdyt0, adz, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + if (adztail != 0.0) { + Two_One_Product(cdxt_bdyt1, cdxt_bdyt0, adztail, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + } + } + + if (adztail != 0.0) { + wlength = scale_expansion_zeroelim(bctlen, bct, adztail, w); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, wlength, w, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (bdztail != 0.0) { + wlength = scale_expansion_zeroelim(catlen, cat, bdztail, w); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, wlength, w, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (cdztail != 0.0) { + wlength = scale_expansion_zeroelim(abtlen, abt, cdztail, w); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, wlength, w, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + + return finnow[finlength - 1]; +} + +#ifdef USE_CGAL_PREDICATES + +REAL orient3d(REAL *pa, REAL *pb, REAL *pc, REAL *pd) +{ + return (REAL) + - cgal_pred_obj.orientation_3_object() + (Point(pa[0], pa[1], pa[2]), + Point(pb[0], pb[1], pb[2]), + Point(pc[0], pc[1], pc[2]), + Point(pd[0], pd[1], pd[2])); +} + +#else + +REAL orient3d(REAL *pa, REAL *pb, REAL *pc, REAL *pd) +{ + REAL adx, bdx, cdx, ady, bdy, cdy, adz, bdz, cdz; + REAL bdxcdy, cdxbdy, cdxady, adxcdy, adxbdy, bdxady; + REAL det; + + + adx = pa[0] - pd[0]; + ady = pa[1] - pd[1]; + adz = pa[2] - pd[2]; + bdx = pb[0] - pd[0]; + bdy = pb[1] - pd[1]; + bdz = pb[2] - pd[2]; + cdx = pc[0] - pd[0]; + cdy = pc[1] - pd[1]; + cdz = pc[2] - pd[2]; + + bdxcdy = bdx * cdy; + cdxbdy = cdx * bdy; + + cdxady = cdx * ady; + adxcdy = adx * cdy; + + adxbdy = adx * bdy; + bdxady = bdx * ady; + + det = adz * (bdxcdy - cdxbdy) + + bdz * (cdxady - adxcdy) + + cdz * (adxbdy - bdxady); + + if (_use_inexact_arith) { + return det; + } + + if (_use_static_filter) { + //if (fabs(det) > o3dstaticfilter) return det; + if (det > o3dstaticfilter) return det; + if (det < -o3dstaticfilter) return det; + } + + + REAL permanent, errbound; + + permanent = (Absolute(bdxcdy) + Absolute(cdxbdy)) * Absolute(adz) + + (Absolute(cdxady) + Absolute(adxcdy)) * Absolute(bdz) + + (Absolute(adxbdy) + Absolute(bdxady)) * Absolute(cdz); + errbound = o3derrboundA * permanent; + if ((det > errbound) || (-det > errbound)) { + return det; + } + + return orient3dadapt(pa, pb, pc, pd, permanent); +} + +#endif // #ifdef USE_CGAL_PREDICATES + +/*****************************************************************************/ +/* */ +/* incirclefast() Approximate 2D incircle test. Nonrobust. */ +/* incircleexact() Exact 2D incircle test. Robust. */ +/* incircleslow() Another exact 2D incircle test. Robust. */ +/* incircle() Adaptive exact 2D incircle test. Robust. */ +/* */ +/* Return a positive value if the point pd lies inside the */ +/* circle passing through pa, pb, and pc; a negative value if */ +/* it lies outside; and zero if the four points are cocircular.*/ +/* The points pa, pb, and pc must be in counterclockwise */ +/* order, or the sign of the result will be reversed. */ +/* */ +/* Only the first and last routine should be used; the middle two are for */ +/* timings. */ +/* */ +/* The last three use exact arithmetic to ensure a correct answer. The */ +/* result returned is the determinant of a matrix. In incircle() only, */ +/* this determinant is computed adaptively, in the sense that exact */ +/* arithmetic is used only to the degree it is needed to ensure that the */ +/* returned value has the correct sign. Hence, incircle() is usually quite */ +/* fast, but will run more slowly when the input points are cocircular or */ +/* nearly so. */ +/* */ +/*****************************************************************************/ + +REAL incirclefast(REAL *pa, REAL *pb, REAL *pc, REAL *pd) +{ + REAL adx, ady, bdx, bdy, cdx, cdy; + REAL abdet, bcdet, cadet; + REAL alift, blift, clift; + + adx = pa[0] - pd[0]; + ady = pa[1] - pd[1]; + bdx = pb[0] - pd[0]; + bdy = pb[1] - pd[1]; + cdx = pc[0] - pd[0]; + cdy = pc[1] - pd[1]; + + abdet = adx * bdy - bdx * ady; + bcdet = bdx * cdy - cdx * bdy; + cadet = cdx * ady - adx * cdy; + alift = adx * adx + ady * ady; + blift = bdx * bdx + bdy * bdy; + clift = cdx * cdx + cdy * cdy; + + return alift * bcdet + blift * cadet + clift * abdet; +} + +REAL incircleexact(REAL *pa, REAL *pb, REAL *pc, REAL *pd) +{ + INEXACT REAL axby1, bxcy1, cxdy1, dxay1, axcy1, bxdy1; + INEXACT REAL bxay1, cxby1, dxcy1, axdy1, cxay1, dxby1; + REAL axby0, bxcy0, cxdy0, dxay0, axcy0, bxdy0; + REAL bxay0, cxby0, dxcy0, axdy0, cxay0, dxby0; + REAL ab[4], bc[4], cd[4], da[4], ac[4], bd[4]; + REAL temp8[8]; + int templen; + REAL abc[12], bcd[12], cda[12], dab[12]; + int abclen, bcdlen, cdalen, dablen; + REAL det24x[24], det24y[24], det48x[48], det48y[48]; + int xlen, ylen; + REAL adet[96], bdet[96], cdet[96], ddet[96]; + int alen, blen, clen, dlen; + REAL abdet[192], cddet[192]; + int ablen, cdlen; + REAL deter[384]; + int deterlen; + int i; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j; + REAL _0; + + Two_Product(pa[0], pb[1], axby1, axby0); + Two_Product(pb[0], pa[1], bxay1, bxay0); + Two_Two_Diff(axby1, axby0, bxay1, bxay0, ab[3], ab[2], ab[1], ab[0]); + + Two_Product(pb[0], pc[1], bxcy1, bxcy0); + Two_Product(pc[0], pb[1], cxby1, cxby0); + Two_Two_Diff(bxcy1, bxcy0, cxby1, cxby0, bc[3], bc[2], bc[1], bc[0]); + + Two_Product(pc[0], pd[1], cxdy1, cxdy0); + Two_Product(pd[0], pc[1], dxcy1, dxcy0); + Two_Two_Diff(cxdy1, cxdy0, dxcy1, dxcy0, cd[3], cd[2], cd[1], cd[0]); + + Two_Product(pd[0], pa[1], dxay1, dxay0); + Two_Product(pa[0], pd[1], axdy1, axdy0); + Two_Two_Diff(dxay1, dxay0, axdy1, axdy0, da[3], da[2], da[1], da[0]); + + Two_Product(pa[0], pc[1], axcy1, axcy0); + Two_Product(pc[0], pa[1], cxay1, cxay0); + Two_Two_Diff(axcy1, axcy0, cxay1, cxay0, ac[3], ac[2], ac[1], ac[0]); + + Two_Product(pb[0], pd[1], bxdy1, bxdy0); + Two_Product(pd[0], pb[1], dxby1, dxby0); + Two_Two_Diff(bxdy1, bxdy0, dxby1, dxby0, bd[3], bd[2], bd[1], bd[0]); + + templen = fast_expansion_sum_zeroelim(4, cd, 4, da, temp8); + cdalen = fast_expansion_sum_zeroelim(templen, temp8, 4, ac, cda); + templen = fast_expansion_sum_zeroelim(4, da, 4, ab, temp8); + dablen = fast_expansion_sum_zeroelim(templen, temp8, 4, bd, dab); + for (i = 0; i < 4; i++) { + bd[i] = -bd[i]; + ac[i] = -ac[i]; + } + templen = fast_expansion_sum_zeroelim(4, ab, 4, bc, temp8); + abclen = fast_expansion_sum_zeroelim(templen, temp8, 4, ac, abc); + templen = fast_expansion_sum_zeroelim(4, bc, 4, cd, temp8); + bcdlen = fast_expansion_sum_zeroelim(templen, temp8, 4, bd, bcd); + + xlen = scale_expansion_zeroelim(bcdlen, bcd, pa[0], det24x); + xlen = scale_expansion_zeroelim(xlen, det24x, pa[0], det48x); + ylen = scale_expansion_zeroelim(bcdlen, bcd, pa[1], det24y); + ylen = scale_expansion_zeroelim(ylen, det24y, pa[1], det48y); + alen = fast_expansion_sum_zeroelim(xlen, det48x, ylen, det48y, adet); + + xlen = scale_expansion_zeroelim(cdalen, cda, pb[0], det24x); + xlen = scale_expansion_zeroelim(xlen, det24x, -pb[0], det48x); + ylen = scale_expansion_zeroelim(cdalen, cda, pb[1], det24y); + ylen = scale_expansion_zeroelim(ylen, det24y, -pb[1], det48y); + blen = fast_expansion_sum_zeroelim(xlen, det48x, ylen, det48y, bdet); + + xlen = scale_expansion_zeroelim(dablen, dab, pc[0], det24x); + xlen = scale_expansion_zeroelim(xlen, det24x, pc[0], det48x); + ylen = scale_expansion_zeroelim(dablen, dab, pc[1], det24y); + ylen = scale_expansion_zeroelim(ylen, det24y, pc[1], det48y); + clen = fast_expansion_sum_zeroelim(xlen, det48x, ylen, det48y, cdet); + + xlen = scale_expansion_zeroelim(abclen, abc, pd[0], det24x); + xlen = scale_expansion_zeroelim(xlen, det24x, -pd[0], det48x); + ylen = scale_expansion_zeroelim(abclen, abc, pd[1], det24y); + ylen = scale_expansion_zeroelim(ylen, det24y, -pd[1], det48y); + dlen = fast_expansion_sum_zeroelim(xlen, det48x, ylen, det48y, ddet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + cdlen = fast_expansion_sum_zeroelim(clen, cdet, dlen, ddet, cddet); + deterlen = fast_expansion_sum_zeroelim(ablen, abdet, cdlen, cddet, deter); + + return deter[deterlen - 1]; +} + +REAL incircleslow(REAL *pa, REAL *pb, REAL *pc, REAL *pd) +{ + INEXACT REAL adx, bdx, cdx, ady, bdy, cdy; + REAL adxtail, bdxtail, cdxtail; + REAL adytail, bdytail, cdytail; + REAL negate, negatetail; + INEXACT REAL axby7, bxcy7, axcy7, bxay7, cxby7, cxay7; + REAL axby[8], bxcy[8], axcy[8], bxay[8], cxby[8], cxay[8]; + REAL temp16[16]; + int temp16len; + REAL detx[32], detxx[64], detxt[32], detxxt[64], detxtxt[64]; + int xlen, xxlen, xtlen, xxtlen, xtxtlen; + REAL x1[128], x2[192]; + int x1len, x2len; + REAL dety[32], detyy[64], detyt[32], detyyt[64], detytyt[64]; + int ylen, yylen, ytlen, yytlen, ytytlen; + REAL y1[128], y2[192]; + int y1len, y2len; + REAL adet[384], bdet[384], cdet[384], abdet[768], deter[1152]; + int alen, blen, clen, ablen, deterlen; + int i; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL a0hi, a0lo, a1hi, a1lo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j, _k, _l, _m, _n; + REAL _0, _1, _2; + + Two_Diff(pa[0], pd[0], adx, adxtail); + Two_Diff(pa[1], pd[1], ady, adytail); + Two_Diff(pb[0], pd[0], bdx, bdxtail); + Two_Diff(pb[1], pd[1], bdy, bdytail); + Two_Diff(pc[0], pd[0], cdx, cdxtail); + Two_Diff(pc[1], pd[1], cdy, cdytail); + + Two_Two_Product(adx, adxtail, bdy, bdytail, + axby7, axby[6], axby[5], axby[4], + axby[3], axby[2], axby[1], axby[0]); + axby[7] = axby7; + negate = -ady; + negatetail = -adytail; + Two_Two_Product(bdx, bdxtail, negate, negatetail, + bxay7, bxay[6], bxay[5], bxay[4], + bxay[3], bxay[2], bxay[1], bxay[0]); + bxay[7] = bxay7; + Two_Two_Product(bdx, bdxtail, cdy, cdytail, + bxcy7, bxcy[6], bxcy[5], bxcy[4], + bxcy[3], bxcy[2], bxcy[1], bxcy[0]); + bxcy[7] = bxcy7; + negate = -bdy; + negatetail = -bdytail; + Two_Two_Product(cdx, cdxtail, negate, negatetail, + cxby7, cxby[6], cxby[5], cxby[4], + cxby[3], cxby[2], cxby[1], cxby[0]); + cxby[7] = cxby7; + Two_Two_Product(cdx, cdxtail, ady, adytail, + cxay7, cxay[6], cxay[5], cxay[4], + cxay[3], cxay[2], cxay[1], cxay[0]); + cxay[7] = cxay7; + negate = -cdy; + negatetail = -cdytail; + Two_Two_Product(adx, adxtail, negate, negatetail, + axcy7, axcy[6], axcy[5], axcy[4], + axcy[3], axcy[2], axcy[1], axcy[0]); + axcy[7] = axcy7; + + + temp16len = fast_expansion_sum_zeroelim(8, bxcy, 8, cxby, temp16); + + xlen = scale_expansion_zeroelim(temp16len, temp16, adx, detx); + xxlen = scale_expansion_zeroelim(xlen, detx, adx, detxx); + xtlen = scale_expansion_zeroelim(temp16len, temp16, adxtail, detxt); + xxtlen = scale_expansion_zeroelim(xtlen, detxt, adx, detxxt); + for (i = 0; i < xxtlen; i++) { + detxxt[i] *= 2.0; + } + xtxtlen = scale_expansion_zeroelim(xtlen, detxt, adxtail, detxtxt); + x1len = fast_expansion_sum_zeroelim(xxlen, detxx, xxtlen, detxxt, x1); + x2len = fast_expansion_sum_zeroelim(x1len, x1, xtxtlen, detxtxt, x2); + + ylen = scale_expansion_zeroelim(temp16len, temp16, ady, dety); + yylen = scale_expansion_zeroelim(ylen, dety, ady, detyy); + ytlen = scale_expansion_zeroelim(temp16len, temp16, adytail, detyt); + yytlen = scale_expansion_zeroelim(ytlen, detyt, ady, detyyt); + for (i = 0; i < yytlen; i++) { + detyyt[i] *= 2.0; + } + ytytlen = scale_expansion_zeroelim(ytlen, detyt, adytail, detytyt); + y1len = fast_expansion_sum_zeroelim(yylen, detyy, yytlen, detyyt, y1); + y2len = fast_expansion_sum_zeroelim(y1len, y1, ytytlen, detytyt, y2); + + alen = fast_expansion_sum_zeroelim(x2len, x2, y2len, y2, adet); + + + temp16len = fast_expansion_sum_zeroelim(8, cxay, 8, axcy, temp16); + + xlen = scale_expansion_zeroelim(temp16len, temp16, bdx, detx); + xxlen = scale_expansion_zeroelim(xlen, detx, bdx, detxx); + xtlen = scale_expansion_zeroelim(temp16len, temp16, bdxtail, detxt); + xxtlen = scale_expansion_zeroelim(xtlen, detxt, bdx, detxxt); + for (i = 0; i < xxtlen; i++) { + detxxt[i] *= 2.0; + } + xtxtlen = scale_expansion_zeroelim(xtlen, detxt, bdxtail, detxtxt); + x1len = fast_expansion_sum_zeroelim(xxlen, detxx, xxtlen, detxxt, x1); + x2len = fast_expansion_sum_zeroelim(x1len, x1, xtxtlen, detxtxt, x2); + + ylen = scale_expansion_zeroelim(temp16len, temp16, bdy, dety); + yylen = scale_expansion_zeroelim(ylen, dety, bdy, detyy); + ytlen = scale_expansion_zeroelim(temp16len, temp16, bdytail, detyt); + yytlen = scale_expansion_zeroelim(ytlen, detyt, bdy, detyyt); + for (i = 0; i < yytlen; i++) { + detyyt[i] *= 2.0; + } + ytytlen = scale_expansion_zeroelim(ytlen, detyt, bdytail, detytyt); + y1len = fast_expansion_sum_zeroelim(yylen, detyy, yytlen, detyyt, y1); + y2len = fast_expansion_sum_zeroelim(y1len, y1, ytytlen, detytyt, y2); + + blen = fast_expansion_sum_zeroelim(x2len, x2, y2len, y2, bdet); + + + temp16len = fast_expansion_sum_zeroelim(8, axby, 8, bxay, temp16); + + xlen = scale_expansion_zeroelim(temp16len, temp16, cdx, detx); + xxlen = scale_expansion_zeroelim(xlen, detx, cdx, detxx); + xtlen = scale_expansion_zeroelim(temp16len, temp16, cdxtail, detxt); + xxtlen = scale_expansion_zeroelim(xtlen, detxt, cdx, detxxt); + for (i = 0; i < xxtlen; i++) { + detxxt[i] *= 2.0; + } + xtxtlen = scale_expansion_zeroelim(xtlen, detxt, cdxtail, detxtxt); + x1len = fast_expansion_sum_zeroelim(xxlen, detxx, xxtlen, detxxt, x1); + x2len = fast_expansion_sum_zeroelim(x1len, x1, xtxtlen, detxtxt, x2); + + ylen = scale_expansion_zeroelim(temp16len, temp16, cdy, dety); + yylen = scale_expansion_zeroelim(ylen, dety, cdy, detyy); + ytlen = scale_expansion_zeroelim(temp16len, temp16, cdytail, detyt); + yytlen = scale_expansion_zeroelim(ytlen, detyt, cdy, detyyt); + for (i = 0; i < yytlen; i++) { + detyyt[i] *= 2.0; + } + ytytlen = scale_expansion_zeroelim(ytlen, detyt, cdytail, detytyt); + y1len = fast_expansion_sum_zeroelim(yylen, detyy, yytlen, detyyt, y1); + y2len = fast_expansion_sum_zeroelim(y1len, y1, ytytlen, detytyt, y2); + + clen = fast_expansion_sum_zeroelim(x2len, x2, y2len, y2, cdet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + deterlen = fast_expansion_sum_zeroelim(ablen, abdet, clen, cdet, deter); + + return deter[deterlen - 1]; +} + +REAL incircleadapt(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL permanent) +{ + INEXACT REAL adx, bdx, cdx, ady, bdy, cdy; + REAL det, errbound; + + INEXACT REAL bdxcdy1, cdxbdy1, cdxady1, adxcdy1, adxbdy1, bdxady1; + REAL bdxcdy0, cdxbdy0, cdxady0, adxcdy0, adxbdy0, bdxady0; + REAL bc[4], ca[4], ab[4]; + INEXACT REAL bc3, ca3, ab3; + REAL axbc[8], axxbc[16], aybc[8], ayybc[16], adet[32]; + int axbclen, axxbclen, aybclen, ayybclen, alen; + REAL bxca[8], bxxca[16], byca[8], byyca[16], bdet[32]; + int bxcalen, bxxcalen, bycalen, byycalen, blen; + REAL cxab[8], cxxab[16], cyab[8], cyyab[16], cdet[32]; + int cxablen, cxxablen, cyablen, cyyablen, clen; + REAL abdet[64]; + int ablen; + REAL fin1[1152], fin2[1152]; + REAL *finnow, *finother, *finswap; + int finlength; + + REAL adxtail, bdxtail, cdxtail, adytail, bdytail, cdytail; + INEXACT REAL adxadx1, adyady1, bdxbdx1, bdybdy1, cdxcdx1, cdycdy1; + REAL adxadx0, adyady0, bdxbdx0, bdybdy0, cdxcdx0, cdycdy0; + REAL aa[4], bb[4], cc[4]; + INEXACT REAL aa3, bb3, cc3; + INEXACT REAL ti1, tj1; + REAL ti0, tj0; + REAL u[4], v[4]; + INEXACT REAL u3, v3; + REAL temp8[8], temp16a[16], temp16b[16], temp16c[16]; + REAL temp32a[32], temp32b[32], temp48[48], temp64[64]; + int temp8len, temp16alen, temp16blen, temp16clen; + int temp32alen, temp32blen, temp48len, temp64len; + REAL axtbb[8], axtcc[8], aytbb[8], aytcc[8]; + int axtbblen, axtcclen, aytbblen, aytcclen; + REAL bxtaa[8], bxtcc[8], bytaa[8], bytcc[8]; + int bxtaalen, bxtcclen, bytaalen, bytcclen; + REAL cxtaa[8], cxtbb[8], cytaa[8], cytbb[8]; + int cxtaalen, cxtbblen, cytaalen, cytbblen; + REAL axtbc[8], aytbc[8], bxtca[8], bytca[8], cxtab[8], cytab[8]; + int axtbclen, aytbclen, bxtcalen, bytcalen, cxtablen, cytablen; + REAL axtbct[16], aytbct[16], bxtcat[16], bytcat[16], cxtabt[16], cytabt[16]; + int axtbctlen, aytbctlen, bxtcatlen, bytcatlen, cxtabtlen, cytabtlen; + REAL axtbctt[8], aytbctt[8], bxtcatt[8]; + REAL bytcatt[8], cxtabtt[8], cytabtt[8]; + int axtbcttlen, aytbcttlen, bxtcattlen, bytcattlen, cxtabttlen, cytabttlen; + REAL abt[8], bct[8], cat[8]; + int abtlen, bctlen, catlen; + REAL abtt[4], bctt[4], catt[4]; + int abttlen, bcttlen, cattlen; + INEXACT REAL abtt3, bctt3, catt3; + REAL negate; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j; + REAL _0; + + // Avoid compiler warnings. H. Si, 2012-02-16. + axtbclen = aytbclen = bxtcalen = bytcalen = cxtablen = cytablen = 0; + + adx = (REAL) (pa[0] - pd[0]); + bdx = (REAL) (pb[0] - pd[0]); + cdx = (REAL) (pc[0] - pd[0]); + ady = (REAL) (pa[1] - pd[1]); + bdy = (REAL) (pb[1] - pd[1]); + cdy = (REAL) (pc[1] - pd[1]); + + Two_Product(bdx, cdy, bdxcdy1, bdxcdy0); + Two_Product(cdx, bdy, cdxbdy1, cdxbdy0); + Two_Two_Diff(bdxcdy1, bdxcdy0, cdxbdy1, cdxbdy0, bc3, bc[2], bc[1], bc[0]); + bc[3] = bc3; + axbclen = scale_expansion_zeroelim(4, bc, adx, axbc); + axxbclen = scale_expansion_zeroelim(axbclen, axbc, adx, axxbc); + aybclen = scale_expansion_zeroelim(4, bc, ady, aybc); + ayybclen = scale_expansion_zeroelim(aybclen, aybc, ady, ayybc); + alen = fast_expansion_sum_zeroelim(axxbclen, axxbc, ayybclen, ayybc, adet); + + Two_Product(cdx, ady, cdxady1, cdxady0); + Two_Product(adx, cdy, adxcdy1, adxcdy0); + Two_Two_Diff(cdxady1, cdxady0, adxcdy1, adxcdy0, ca3, ca[2], ca[1], ca[0]); + ca[3] = ca3; + bxcalen = scale_expansion_zeroelim(4, ca, bdx, bxca); + bxxcalen = scale_expansion_zeroelim(bxcalen, bxca, bdx, bxxca); + bycalen = scale_expansion_zeroelim(4, ca, bdy, byca); + byycalen = scale_expansion_zeroelim(bycalen, byca, bdy, byyca); + blen = fast_expansion_sum_zeroelim(bxxcalen, bxxca, byycalen, byyca, bdet); + + Two_Product(adx, bdy, adxbdy1, adxbdy0); + Two_Product(bdx, ady, bdxady1, bdxady0); + Two_Two_Diff(adxbdy1, adxbdy0, bdxady1, bdxady0, ab3, ab[2], ab[1], ab[0]); + ab[3] = ab3; + cxablen = scale_expansion_zeroelim(4, ab, cdx, cxab); + cxxablen = scale_expansion_zeroelim(cxablen, cxab, cdx, cxxab); + cyablen = scale_expansion_zeroelim(4, ab, cdy, cyab); + cyyablen = scale_expansion_zeroelim(cyablen, cyab, cdy, cyyab); + clen = fast_expansion_sum_zeroelim(cxxablen, cxxab, cyyablen, cyyab, cdet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + finlength = fast_expansion_sum_zeroelim(ablen, abdet, clen, cdet, fin1); + + det = estimate(finlength, fin1); + errbound = iccerrboundB * permanent; + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + Two_Diff_Tail(pa[0], pd[0], adx, adxtail); + Two_Diff_Tail(pa[1], pd[1], ady, adytail); + Two_Diff_Tail(pb[0], pd[0], bdx, bdxtail); + Two_Diff_Tail(pb[1], pd[1], bdy, bdytail); + Two_Diff_Tail(pc[0], pd[0], cdx, cdxtail); + Two_Diff_Tail(pc[1], pd[1], cdy, cdytail); + if ((adxtail == 0.0) && (bdxtail == 0.0) && (cdxtail == 0.0) + && (adytail == 0.0) && (bdytail == 0.0) && (cdytail == 0.0)) { + return det; + } + + errbound = iccerrboundC * permanent + resulterrbound * Absolute(det); + det += ((adx * adx + ady * ady) * ((bdx * cdytail + cdy * bdxtail) + - (bdy * cdxtail + cdx * bdytail)) + + 2.0 * (adx * adxtail + ady * adytail) * (bdx * cdy - bdy * cdx)) + + ((bdx * bdx + bdy * bdy) * ((cdx * adytail + ady * cdxtail) + - (cdy * adxtail + adx * cdytail)) + + 2.0 * (bdx * bdxtail + bdy * bdytail) * (cdx * ady - cdy * adx)) + + ((cdx * cdx + cdy * cdy) * ((adx * bdytail + bdy * adxtail) + - (ady * bdxtail + bdx * adytail)) + + 2.0 * (cdx * cdxtail + cdy * cdytail) * (adx * bdy - ady * bdx)); + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + finnow = fin1; + finother = fin2; + + if ((bdxtail != 0.0) || (bdytail != 0.0) + || (cdxtail != 0.0) || (cdytail != 0.0)) { + Square(adx, adxadx1, adxadx0); + Square(ady, adyady1, adyady0); + Two_Two_Sum(adxadx1, adxadx0, adyady1, adyady0, aa3, aa[2], aa[1], aa[0]); + aa[3] = aa3; + } + if ((cdxtail != 0.0) || (cdytail != 0.0) + || (adxtail != 0.0) || (adytail != 0.0)) { + Square(bdx, bdxbdx1, bdxbdx0); + Square(bdy, bdybdy1, bdybdy0); + Two_Two_Sum(bdxbdx1, bdxbdx0, bdybdy1, bdybdy0, bb3, bb[2], bb[1], bb[0]); + bb[3] = bb3; + } + if ((adxtail != 0.0) || (adytail != 0.0) + || (bdxtail != 0.0) || (bdytail != 0.0)) { + Square(cdx, cdxcdx1, cdxcdx0); + Square(cdy, cdycdy1, cdycdy0); + Two_Two_Sum(cdxcdx1, cdxcdx0, cdycdy1, cdycdy0, cc3, cc[2], cc[1], cc[0]); + cc[3] = cc3; + } + + if (adxtail != 0.0) { + axtbclen = scale_expansion_zeroelim(4, bc, adxtail, axtbc); + temp16alen = scale_expansion_zeroelim(axtbclen, axtbc, 2.0 * adx, + temp16a); + + axtcclen = scale_expansion_zeroelim(4, cc, adxtail, axtcc); + temp16blen = scale_expansion_zeroelim(axtcclen, axtcc, bdy, temp16b); + + axtbblen = scale_expansion_zeroelim(4, bb, adxtail, axtbb); + temp16clen = scale_expansion_zeroelim(axtbblen, axtbb, -cdy, temp16c); + + temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (adytail != 0.0) { + aytbclen = scale_expansion_zeroelim(4, bc, adytail, aytbc); + temp16alen = scale_expansion_zeroelim(aytbclen, aytbc, 2.0 * ady, + temp16a); + + aytbblen = scale_expansion_zeroelim(4, bb, adytail, aytbb); + temp16blen = scale_expansion_zeroelim(aytbblen, aytbb, cdx, temp16b); + + aytcclen = scale_expansion_zeroelim(4, cc, adytail, aytcc); + temp16clen = scale_expansion_zeroelim(aytcclen, aytcc, -bdx, temp16c); + + temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (bdxtail != 0.0) { + bxtcalen = scale_expansion_zeroelim(4, ca, bdxtail, bxtca); + temp16alen = scale_expansion_zeroelim(bxtcalen, bxtca, 2.0 * bdx, + temp16a); + + bxtaalen = scale_expansion_zeroelim(4, aa, bdxtail, bxtaa); + temp16blen = scale_expansion_zeroelim(bxtaalen, bxtaa, cdy, temp16b); + + bxtcclen = scale_expansion_zeroelim(4, cc, bdxtail, bxtcc); + temp16clen = scale_expansion_zeroelim(bxtcclen, bxtcc, -ady, temp16c); + + temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (bdytail != 0.0) { + bytcalen = scale_expansion_zeroelim(4, ca, bdytail, bytca); + temp16alen = scale_expansion_zeroelim(bytcalen, bytca, 2.0 * bdy, + temp16a); + + bytcclen = scale_expansion_zeroelim(4, cc, bdytail, bytcc); + temp16blen = scale_expansion_zeroelim(bytcclen, bytcc, adx, temp16b); + + bytaalen = scale_expansion_zeroelim(4, aa, bdytail, bytaa); + temp16clen = scale_expansion_zeroelim(bytaalen, bytaa, -cdx, temp16c); + + temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (cdxtail != 0.0) { + cxtablen = scale_expansion_zeroelim(4, ab, cdxtail, cxtab); + temp16alen = scale_expansion_zeroelim(cxtablen, cxtab, 2.0 * cdx, + temp16a); + + cxtbblen = scale_expansion_zeroelim(4, bb, cdxtail, cxtbb); + temp16blen = scale_expansion_zeroelim(cxtbblen, cxtbb, ady, temp16b); + + cxtaalen = scale_expansion_zeroelim(4, aa, cdxtail, cxtaa); + temp16clen = scale_expansion_zeroelim(cxtaalen, cxtaa, -bdy, temp16c); + + temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (cdytail != 0.0) { + cytablen = scale_expansion_zeroelim(4, ab, cdytail, cytab); + temp16alen = scale_expansion_zeroelim(cytablen, cytab, 2.0 * cdy, + temp16a); + + cytaalen = scale_expansion_zeroelim(4, aa, cdytail, cytaa); + temp16blen = scale_expansion_zeroelim(cytaalen, cytaa, bdx, temp16b); + + cytbblen = scale_expansion_zeroelim(4, bb, cdytail, cytbb); + temp16clen = scale_expansion_zeroelim(cytbblen, cytbb, -adx, temp16c); + + temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + + if ((adxtail != 0.0) || (adytail != 0.0)) { + if ((bdxtail != 0.0) || (bdytail != 0.0) + || (cdxtail != 0.0) || (cdytail != 0.0)) { + Two_Product(bdxtail, cdy, ti1, ti0); + Two_Product(bdx, cdytail, tj1, tj0); + Two_Two_Sum(ti1, ti0, tj1, tj0, u3, u[2], u[1], u[0]); + u[3] = u3; + negate = -bdy; + Two_Product(cdxtail, negate, ti1, ti0); + negate = -bdytail; + Two_Product(cdx, negate, tj1, tj0); + Two_Two_Sum(ti1, ti0, tj1, tj0, v3, v[2], v[1], v[0]); + v[3] = v3; + bctlen = fast_expansion_sum_zeroelim(4, u, 4, v, bct); + + Two_Product(bdxtail, cdytail, ti1, ti0); + Two_Product(cdxtail, bdytail, tj1, tj0); + Two_Two_Diff(ti1, ti0, tj1, tj0, bctt3, bctt[2], bctt[1], bctt[0]); + bctt[3] = bctt3; + bcttlen = 4; + } else { + bct[0] = 0.0; + bctlen = 1; + bctt[0] = 0.0; + bcttlen = 1; + } + + if (adxtail != 0.0) { + temp16alen = scale_expansion_zeroelim(axtbclen, axtbc, adxtail, temp16a); + axtbctlen = scale_expansion_zeroelim(bctlen, bct, adxtail, axtbct); + temp32alen = scale_expansion_zeroelim(axtbctlen, axtbct, 2.0 * adx, + temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + if (bdytail != 0.0) { + temp8len = scale_expansion_zeroelim(4, cc, adxtail, temp8); + temp16alen = scale_expansion_zeroelim(temp8len, temp8, bdytail, + temp16a); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, + temp16a, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (cdytail != 0.0) { + temp8len = scale_expansion_zeroelim(4, bb, -adxtail, temp8); + temp16alen = scale_expansion_zeroelim(temp8len, temp8, cdytail, + temp16a); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, + temp16a, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + + temp32alen = scale_expansion_zeroelim(axtbctlen, axtbct, adxtail, + temp32a); + axtbcttlen = scale_expansion_zeroelim(bcttlen, bctt, adxtail, axtbctt); + temp16alen = scale_expansion_zeroelim(axtbcttlen, axtbctt, 2.0 * adx, + temp16a); + temp16blen = scale_expansion_zeroelim(axtbcttlen, axtbctt, adxtail, + temp16b); + temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32b); + temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, + temp64, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (adytail != 0.0) { + temp16alen = scale_expansion_zeroelim(aytbclen, aytbc, adytail, temp16a); + aytbctlen = scale_expansion_zeroelim(bctlen, bct, adytail, aytbct); + temp32alen = scale_expansion_zeroelim(aytbctlen, aytbct, 2.0 * ady, + temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + + + temp32alen = scale_expansion_zeroelim(aytbctlen, aytbct, adytail, + temp32a); + aytbcttlen = scale_expansion_zeroelim(bcttlen, bctt, adytail, aytbctt); + temp16alen = scale_expansion_zeroelim(aytbcttlen, aytbctt, 2.0 * ady, + temp16a); + temp16blen = scale_expansion_zeroelim(aytbcttlen, aytbctt, adytail, + temp16b); + temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32b); + temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, + temp64, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + } + if ((bdxtail != 0.0) || (bdytail != 0.0)) { + if ((cdxtail != 0.0) || (cdytail != 0.0) + || (adxtail != 0.0) || (adytail != 0.0)) { + Two_Product(cdxtail, ady, ti1, ti0); + Two_Product(cdx, adytail, tj1, tj0); + Two_Two_Sum(ti1, ti0, tj1, tj0, u3, u[2], u[1], u[0]); + u[3] = u3; + negate = -cdy; + Two_Product(adxtail, negate, ti1, ti0); + negate = -cdytail; + Two_Product(adx, negate, tj1, tj0); + Two_Two_Sum(ti1, ti0, tj1, tj0, v3, v[2], v[1], v[0]); + v[3] = v3; + catlen = fast_expansion_sum_zeroelim(4, u, 4, v, cat); + + Two_Product(cdxtail, adytail, ti1, ti0); + Two_Product(adxtail, cdytail, tj1, tj0); + Two_Two_Diff(ti1, ti0, tj1, tj0, catt3, catt[2], catt[1], catt[0]); + catt[3] = catt3; + cattlen = 4; + } else { + cat[0] = 0.0; + catlen = 1; + catt[0] = 0.0; + cattlen = 1; + } + + if (bdxtail != 0.0) { + temp16alen = scale_expansion_zeroelim(bxtcalen, bxtca, bdxtail, temp16a); + bxtcatlen = scale_expansion_zeroelim(catlen, cat, bdxtail, bxtcat); + temp32alen = scale_expansion_zeroelim(bxtcatlen, bxtcat, 2.0 * bdx, + temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + if (cdytail != 0.0) { + temp8len = scale_expansion_zeroelim(4, aa, bdxtail, temp8); + temp16alen = scale_expansion_zeroelim(temp8len, temp8, cdytail, + temp16a); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, + temp16a, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (adytail != 0.0) { + temp8len = scale_expansion_zeroelim(4, cc, -bdxtail, temp8); + temp16alen = scale_expansion_zeroelim(temp8len, temp8, adytail, + temp16a); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, + temp16a, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + + temp32alen = scale_expansion_zeroelim(bxtcatlen, bxtcat, bdxtail, + temp32a); + bxtcattlen = scale_expansion_zeroelim(cattlen, catt, bdxtail, bxtcatt); + temp16alen = scale_expansion_zeroelim(bxtcattlen, bxtcatt, 2.0 * bdx, + temp16a); + temp16blen = scale_expansion_zeroelim(bxtcattlen, bxtcatt, bdxtail, + temp16b); + temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32b); + temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, + temp64, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (bdytail != 0.0) { + temp16alen = scale_expansion_zeroelim(bytcalen, bytca, bdytail, temp16a); + bytcatlen = scale_expansion_zeroelim(catlen, cat, bdytail, bytcat); + temp32alen = scale_expansion_zeroelim(bytcatlen, bytcat, 2.0 * bdy, + temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + + + temp32alen = scale_expansion_zeroelim(bytcatlen, bytcat, bdytail, + temp32a); + bytcattlen = scale_expansion_zeroelim(cattlen, catt, bdytail, bytcatt); + temp16alen = scale_expansion_zeroelim(bytcattlen, bytcatt, 2.0 * bdy, + temp16a); + temp16blen = scale_expansion_zeroelim(bytcattlen, bytcatt, bdytail, + temp16b); + temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32b); + temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, + temp64, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + } + if ((cdxtail != 0.0) || (cdytail != 0.0)) { + if ((adxtail != 0.0) || (adytail != 0.0) + || (bdxtail != 0.0) || (bdytail != 0.0)) { + Two_Product(adxtail, bdy, ti1, ti0); + Two_Product(adx, bdytail, tj1, tj0); + Two_Two_Sum(ti1, ti0, tj1, tj0, u3, u[2], u[1], u[0]); + u[3] = u3; + negate = -ady; + Two_Product(bdxtail, negate, ti1, ti0); + negate = -adytail; + Two_Product(bdx, negate, tj1, tj0); + Two_Two_Sum(ti1, ti0, tj1, tj0, v3, v[2], v[1], v[0]); + v[3] = v3; + abtlen = fast_expansion_sum_zeroelim(4, u, 4, v, abt); + + Two_Product(adxtail, bdytail, ti1, ti0); + Two_Product(bdxtail, adytail, tj1, tj0); + Two_Two_Diff(ti1, ti0, tj1, tj0, abtt3, abtt[2], abtt[1], abtt[0]); + abtt[3] = abtt3; + abttlen = 4; + } else { + abt[0] = 0.0; + abtlen = 1; + abtt[0] = 0.0; + abttlen = 1; + } + + if (cdxtail != 0.0) { + temp16alen = scale_expansion_zeroelim(cxtablen, cxtab, cdxtail, temp16a); + cxtabtlen = scale_expansion_zeroelim(abtlen, abt, cdxtail, cxtabt); + temp32alen = scale_expansion_zeroelim(cxtabtlen, cxtabt, 2.0 * cdx, + temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + if (adytail != 0.0) { + temp8len = scale_expansion_zeroelim(4, bb, cdxtail, temp8); + temp16alen = scale_expansion_zeroelim(temp8len, temp8, adytail, + temp16a); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, + temp16a, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (bdytail != 0.0) { + temp8len = scale_expansion_zeroelim(4, aa, -cdxtail, temp8); + temp16alen = scale_expansion_zeroelim(temp8len, temp8, bdytail, + temp16a); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, + temp16a, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + + temp32alen = scale_expansion_zeroelim(cxtabtlen, cxtabt, cdxtail, + temp32a); + cxtabttlen = scale_expansion_zeroelim(abttlen, abtt, cdxtail, cxtabtt); + temp16alen = scale_expansion_zeroelim(cxtabttlen, cxtabtt, 2.0 * cdx, + temp16a); + temp16blen = scale_expansion_zeroelim(cxtabttlen, cxtabtt, cdxtail, + temp16b); + temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32b); + temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, + temp64, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (cdytail != 0.0) { + temp16alen = scale_expansion_zeroelim(cytablen, cytab, cdytail, temp16a); + cytabtlen = scale_expansion_zeroelim(abtlen, abt, cdytail, cytabt); + temp32alen = scale_expansion_zeroelim(cytabtlen, cytabt, 2.0 * cdy, + temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + + + temp32alen = scale_expansion_zeroelim(cytabtlen, cytabt, cdytail, + temp32a); + cytabttlen = scale_expansion_zeroelim(abttlen, abtt, cdytail, cytabtt); + temp16alen = scale_expansion_zeroelim(cytabttlen, cytabtt, 2.0 * cdy, + temp16a); + temp16blen = scale_expansion_zeroelim(cytabttlen, cytabtt, cdytail, + temp16b); + temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32b); + temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, + temp64, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + } + + return finnow[finlength - 1]; +} + +REAL incircle(REAL *pa, REAL *pb, REAL *pc, REAL *pd) +{ + REAL adx, bdx, cdx, ady, bdy, cdy; + REAL bdxcdy, cdxbdy, cdxady, adxcdy, adxbdy, bdxady; + REAL alift, blift, clift; + REAL det; + REAL permanent, errbound; + + adx = pa[0] - pd[0]; + bdx = pb[0] - pd[0]; + cdx = pc[0] - pd[0]; + ady = pa[1] - pd[1]; + bdy = pb[1] - pd[1]; + cdy = pc[1] - pd[1]; + + bdxcdy = bdx * cdy; + cdxbdy = cdx * bdy; + alift = adx * adx + ady * ady; + + cdxady = cdx * ady; + adxcdy = adx * cdy; + blift = bdx * bdx + bdy * bdy; + + adxbdy = adx * bdy; + bdxady = bdx * ady; + clift = cdx * cdx + cdy * cdy; + + det = alift * (bdxcdy - cdxbdy) + + blift * (cdxady - adxcdy) + + clift * (adxbdy - bdxady); + + permanent = (Absolute(bdxcdy) + Absolute(cdxbdy)) * alift + + (Absolute(cdxady) + Absolute(adxcdy)) * blift + + (Absolute(adxbdy) + Absolute(bdxady)) * clift; + errbound = iccerrboundA * permanent; + if ((det > errbound) || (-det > errbound)) { + return det; + } + + return incircleadapt(pa, pb, pc, pd, permanent); +} + +/*****************************************************************************/ +/* */ +/* inspherefast() Approximate 3D insphere test. Nonrobust. */ +/* insphereexact() Exact 3D insphere test. Robust. */ +/* insphereslow() Another exact 3D insphere test. Robust. */ +/* insphere() Adaptive exact 3D insphere test. Robust. */ +/* */ +/* Return a positive value if the point pe lies inside the */ +/* sphere passing through pa, pb, pc, and pd; a negative value */ +/* if it lies outside; and zero if the five points are */ +/* cospherical. The points pa, pb, pc, and pd must be ordered */ +/* so that they have a positive orientation (as defined by */ +/* orient3d()), or the sign of the result will be reversed. */ +/* */ +/* Only the first and last routine should be used; the middle two are for */ +/* timings. */ +/* */ +/* The last three use exact arithmetic to ensure a correct answer. The */ +/* result returned is the determinant of a matrix. In insphere() only, */ +/* this determinant is computed adaptively, in the sense that exact */ +/* arithmetic is used only to the degree it is needed to ensure that the */ +/* returned value has the correct sign. Hence, insphere() is usually quite */ +/* fast, but will run more slowly when the input points are cospherical or */ +/* nearly so. */ +/* */ +/*****************************************************************************/ + +REAL inspherefast(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL *pe) +{ + REAL aex, bex, cex, dex; + REAL aey, bey, cey, dey; + REAL aez, bez, cez, dez; + REAL alift, blift, clift, dlift; + REAL ab, bc, cd, da, ac, bd; + REAL abc, bcd, cda, dab; + + aex = pa[0] - pe[0]; + bex = pb[0] - pe[0]; + cex = pc[0] - pe[0]; + dex = pd[0] - pe[0]; + aey = pa[1] - pe[1]; + bey = pb[1] - pe[1]; + cey = pc[1] - pe[1]; + dey = pd[1] - pe[1]; + aez = pa[2] - pe[2]; + bez = pb[2] - pe[2]; + cez = pc[2] - pe[2]; + dez = pd[2] - pe[2]; + + ab = aex * bey - bex * aey; + bc = bex * cey - cex * bey; + cd = cex * dey - dex * cey; + da = dex * aey - aex * dey; + + ac = aex * cey - cex * aey; + bd = bex * dey - dex * bey; + + abc = aez * bc - bez * ac + cez * ab; + bcd = bez * cd - cez * bd + dez * bc; + cda = cez * da + dez * ac + aez * cd; + dab = dez * ab + aez * bd + bez * da; + + alift = aex * aex + aey * aey + aez * aez; + blift = bex * bex + bey * bey + bez * bez; + clift = cex * cex + cey * cey + cez * cez; + dlift = dex * dex + dey * dey + dez * dez; + + return (dlift * abc - clift * dab) + (blift * cda - alift * bcd); +} + +REAL insphereexact(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL *pe) +{ + INEXACT REAL axby1, bxcy1, cxdy1, dxey1, exay1; + INEXACT REAL bxay1, cxby1, dxcy1, exdy1, axey1; + INEXACT REAL axcy1, bxdy1, cxey1, dxay1, exby1; + INEXACT REAL cxay1, dxby1, excy1, axdy1, bxey1; + REAL axby0, bxcy0, cxdy0, dxey0, exay0; + REAL bxay0, cxby0, dxcy0, exdy0, axey0; + REAL axcy0, bxdy0, cxey0, dxay0, exby0; + REAL cxay0, dxby0, excy0, axdy0, bxey0; + REAL ab[4], bc[4], cd[4], de[4], ea[4]; + REAL ac[4], bd[4], ce[4], da[4], eb[4]; + REAL temp8a[8], temp8b[8], temp16[16]; + int temp8alen, temp8blen, temp16len; + REAL abc[24], bcd[24], cde[24], dea[24], eab[24]; + REAL abd[24], bce[24], cda[24], deb[24], eac[24]; + int abclen, bcdlen, cdelen, dealen, eablen; + int abdlen, bcelen, cdalen, deblen, eaclen; + REAL temp48a[48], temp48b[48]; + int temp48alen, temp48blen; + REAL abcd[96], bcde[96], cdea[96], deab[96], eabc[96]; + int abcdlen, bcdelen, cdealen, deablen, eabclen; + REAL temp192[192]; + REAL det384x[384], det384y[384], det384z[384]; + int xlen, ylen, zlen; + REAL detxy[768]; + int xylen; + REAL adet[1152], bdet[1152], cdet[1152], ddet[1152], edet[1152]; + int alen, blen, clen, dlen, elen; + REAL abdet[2304], cddet[2304], cdedet[3456]; + int ablen, cdlen; + REAL deter[5760]; + int deterlen; + int i; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j; + REAL _0; + + + Two_Product(pa[0], pb[1], axby1, axby0); + Two_Product(pb[0], pa[1], bxay1, bxay0); + Two_Two_Diff(axby1, axby0, bxay1, bxay0, ab[3], ab[2], ab[1], ab[0]); + + Two_Product(pb[0], pc[1], bxcy1, bxcy0); + Two_Product(pc[0], pb[1], cxby1, cxby0); + Two_Two_Diff(bxcy1, bxcy0, cxby1, cxby0, bc[3], bc[2], bc[1], bc[0]); + + Two_Product(pc[0], pd[1], cxdy1, cxdy0); + Two_Product(pd[0], pc[1], dxcy1, dxcy0); + Two_Two_Diff(cxdy1, cxdy0, dxcy1, dxcy0, cd[3], cd[2], cd[1], cd[0]); + + Two_Product(pd[0], pe[1], dxey1, dxey0); + Two_Product(pe[0], pd[1], exdy1, exdy0); + Two_Two_Diff(dxey1, dxey0, exdy1, exdy0, de[3], de[2], de[1], de[0]); + + Two_Product(pe[0], pa[1], exay1, exay0); + Two_Product(pa[0], pe[1], axey1, axey0); + Two_Two_Diff(exay1, exay0, axey1, axey0, ea[3], ea[2], ea[1], ea[0]); + + Two_Product(pa[0], pc[1], axcy1, axcy0); + Two_Product(pc[0], pa[1], cxay1, cxay0); + Two_Two_Diff(axcy1, axcy0, cxay1, cxay0, ac[3], ac[2], ac[1], ac[0]); + + Two_Product(pb[0], pd[1], bxdy1, bxdy0); + Two_Product(pd[0], pb[1], dxby1, dxby0); + Two_Two_Diff(bxdy1, bxdy0, dxby1, dxby0, bd[3], bd[2], bd[1], bd[0]); + + Two_Product(pc[0], pe[1], cxey1, cxey0); + Two_Product(pe[0], pc[1], excy1, excy0); + Two_Two_Diff(cxey1, cxey0, excy1, excy0, ce[3], ce[2], ce[1], ce[0]); + + Two_Product(pd[0], pa[1], dxay1, dxay0); + Two_Product(pa[0], pd[1], axdy1, axdy0); + Two_Two_Diff(dxay1, dxay0, axdy1, axdy0, da[3], da[2], da[1], da[0]); + + Two_Product(pe[0], pb[1], exby1, exby0); + Two_Product(pb[0], pe[1], bxey1, bxey0); + Two_Two_Diff(exby1, exby0, bxey1, bxey0, eb[3], eb[2], eb[1], eb[0]); + + temp8alen = scale_expansion_zeroelim(4, bc, pa[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, ac, -pb[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, ab, pc[2], temp8a); + abclen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + abc); + + temp8alen = scale_expansion_zeroelim(4, cd, pb[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, bd, -pc[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, bc, pd[2], temp8a); + bcdlen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + bcd); + + temp8alen = scale_expansion_zeroelim(4, de, pc[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, ce, -pd[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, cd, pe[2], temp8a); + cdelen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + cde); + + temp8alen = scale_expansion_zeroelim(4, ea, pd[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, da, -pe[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, de, pa[2], temp8a); + dealen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + dea); + + temp8alen = scale_expansion_zeroelim(4, ab, pe[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, eb, -pa[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, ea, pb[2], temp8a); + eablen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + eab); + + temp8alen = scale_expansion_zeroelim(4, bd, pa[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, da, pb[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, ab, pd[2], temp8a); + abdlen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + abd); + + temp8alen = scale_expansion_zeroelim(4, ce, pb[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, eb, pc[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, bc, pe[2], temp8a); + bcelen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + bce); + + temp8alen = scale_expansion_zeroelim(4, da, pc[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, ac, pd[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, cd, pa[2], temp8a); + cdalen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + cda); + + temp8alen = scale_expansion_zeroelim(4, eb, pd[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, bd, pe[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, de, pb[2], temp8a); + deblen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + deb); + + temp8alen = scale_expansion_zeroelim(4, ac, pe[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, ce, pa[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, ea, pc[2], temp8a); + eaclen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + eac); + + temp48alen = fast_expansion_sum_zeroelim(cdelen, cde, bcelen, bce, temp48a); + temp48blen = fast_expansion_sum_zeroelim(deblen, deb, bcdlen, bcd, temp48b); + for (i = 0; i < temp48blen; i++) { + temp48b[i] = -temp48b[i]; + } + bcdelen = fast_expansion_sum_zeroelim(temp48alen, temp48a, + temp48blen, temp48b, bcde); + xlen = scale_expansion_zeroelim(bcdelen, bcde, pa[0], temp192); + xlen = scale_expansion_zeroelim(xlen, temp192, pa[0], det384x); + ylen = scale_expansion_zeroelim(bcdelen, bcde, pa[1], temp192); + ylen = scale_expansion_zeroelim(ylen, temp192, pa[1], det384y); + zlen = scale_expansion_zeroelim(bcdelen, bcde, pa[2], temp192); + zlen = scale_expansion_zeroelim(zlen, temp192, pa[2], det384z); + xylen = fast_expansion_sum_zeroelim(xlen, det384x, ylen, det384y, detxy); + alen = fast_expansion_sum_zeroelim(xylen, detxy, zlen, det384z, adet); + + temp48alen = fast_expansion_sum_zeroelim(dealen, dea, cdalen, cda, temp48a); + temp48blen = fast_expansion_sum_zeroelim(eaclen, eac, cdelen, cde, temp48b); + for (i = 0; i < temp48blen; i++) { + temp48b[i] = -temp48b[i]; + } + cdealen = fast_expansion_sum_zeroelim(temp48alen, temp48a, + temp48blen, temp48b, cdea); + xlen = scale_expansion_zeroelim(cdealen, cdea, pb[0], temp192); + xlen = scale_expansion_zeroelim(xlen, temp192, pb[0], det384x); + ylen = scale_expansion_zeroelim(cdealen, cdea, pb[1], temp192); + ylen = scale_expansion_zeroelim(ylen, temp192, pb[1], det384y); + zlen = scale_expansion_zeroelim(cdealen, cdea, pb[2], temp192); + zlen = scale_expansion_zeroelim(zlen, temp192, pb[2], det384z); + xylen = fast_expansion_sum_zeroelim(xlen, det384x, ylen, det384y, detxy); + blen = fast_expansion_sum_zeroelim(xylen, detxy, zlen, det384z, bdet); + + temp48alen = fast_expansion_sum_zeroelim(eablen, eab, deblen, deb, temp48a); + temp48blen = fast_expansion_sum_zeroelim(abdlen, abd, dealen, dea, temp48b); + for (i = 0; i < temp48blen; i++) { + temp48b[i] = -temp48b[i]; + } + deablen = fast_expansion_sum_zeroelim(temp48alen, temp48a, + temp48blen, temp48b, deab); + xlen = scale_expansion_zeroelim(deablen, deab, pc[0], temp192); + xlen = scale_expansion_zeroelim(xlen, temp192, pc[0], det384x); + ylen = scale_expansion_zeroelim(deablen, deab, pc[1], temp192); + ylen = scale_expansion_zeroelim(ylen, temp192, pc[1], det384y); + zlen = scale_expansion_zeroelim(deablen, deab, pc[2], temp192); + zlen = scale_expansion_zeroelim(zlen, temp192, pc[2], det384z); + xylen = fast_expansion_sum_zeroelim(xlen, det384x, ylen, det384y, detxy); + clen = fast_expansion_sum_zeroelim(xylen, detxy, zlen, det384z, cdet); + + temp48alen = fast_expansion_sum_zeroelim(abclen, abc, eaclen, eac, temp48a); + temp48blen = fast_expansion_sum_zeroelim(bcelen, bce, eablen, eab, temp48b); + for (i = 0; i < temp48blen; i++) { + temp48b[i] = -temp48b[i]; + } + eabclen = fast_expansion_sum_zeroelim(temp48alen, temp48a, + temp48blen, temp48b, eabc); + xlen = scale_expansion_zeroelim(eabclen, eabc, pd[0], temp192); + xlen = scale_expansion_zeroelim(xlen, temp192, pd[0], det384x); + ylen = scale_expansion_zeroelim(eabclen, eabc, pd[1], temp192); + ylen = scale_expansion_zeroelim(ylen, temp192, pd[1], det384y); + zlen = scale_expansion_zeroelim(eabclen, eabc, pd[2], temp192); + zlen = scale_expansion_zeroelim(zlen, temp192, pd[2], det384z); + xylen = fast_expansion_sum_zeroelim(xlen, det384x, ylen, det384y, detxy); + dlen = fast_expansion_sum_zeroelim(xylen, detxy, zlen, det384z, ddet); + + temp48alen = fast_expansion_sum_zeroelim(bcdlen, bcd, abdlen, abd, temp48a); + temp48blen = fast_expansion_sum_zeroelim(cdalen, cda, abclen, abc, temp48b); + for (i = 0; i < temp48blen; i++) { + temp48b[i] = -temp48b[i]; + } + abcdlen = fast_expansion_sum_zeroelim(temp48alen, temp48a, + temp48blen, temp48b, abcd); + xlen = scale_expansion_zeroelim(abcdlen, abcd, pe[0], temp192); + xlen = scale_expansion_zeroelim(xlen, temp192, pe[0], det384x); + ylen = scale_expansion_zeroelim(abcdlen, abcd, pe[1], temp192); + ylen = scale_expansion_zeroelim(ylen, temp192, pe[1], det384y); + zlen = scale_expansion_zeroelim(abcdlen, abcd, pe[2], temp192); + zlen = scale_expansion_zeroelim(zlen, temp192, pe[2], det384z); + xylen = fast_expansion_sum_zeroelim(xlen, det384x, ylen, det384y, detxy); + elen = fast_expansion_sum_zeroelim(xylen, detxy, zlen, det384z, edet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + cdlen = fast_expansion_sum_zeroelim(clen, cdet, dlen, ddet, cddet); + cdelen = fast_expansion_sum_zeroelim(cdlen, cddet, elen, edet, cdedet); + deterlen = fast_expansion_sum_zeroelim(ablen, abdet, cdelen, cdedet, deter); + + return deter[deterlen - 1]; +} + +REAL insphereslow(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL *pe) +{ + INEXACT REAL aex, bex, cex, dex, aey, bey, cey, dey, aez, bez, cez, dez; + REAL aextail, bextail, cextail, dextail; + REAL aeytail, beytail, ceytail, deytail; + REAL aeztail, beztail, ceztail, deztail; + REAL negate, negatetail; + INEXACT REAL axby7, bxcy7, cxdy7, dxay7, axcy7, bxdy7; + INEXACT REAL bxay7, cxby7, dxcy7, axdy7, cxay7, dxby7; + REAL axby[8], bxcy[8], cxdy[8], dxay[8], axcy[8], bxdy[8]; + REAL bxay[8], cxby[8], dxcy[8], axdy[8], cxay[8], dxby[8]; + REAL ab[16], bc[16], cd[16], da[16], ac[16], bd[16]; + int ablen, bclen, cdlen, dalen, aclen, bdlen; + REAL temp32a[32], temp32b[32], temp64a[64], temp64b[64], temp64c[64]; + int temp32alen, temp32blen, temp64alen, temp64blen, temp64clen; + REAL temp128[128], temp192[192]; + int temp128len, temp192len; + REAL detx[384], detxx[768], detxt[384], detxxt[768], detxtxt[768]; + int xlen, xxlen, xtlen, xxtlen, xtxtlen; + REAL x1[1536], x2[2304]; + int x1len, x2len; + REAL dety[384], detyy[768], detyt[384], detyyt[768], detytyt[768]; + int ylen, yylen, ytlen, yytlen, ytytlen; + REAL y1[1536], y2[2304]; + int y1len, y2len; + REAL detz[384], detzz[768], detzt[384], detzzt[768], detztzt[768]; + int zlen, zzlen, ztlen, zztlen, ztztlen; + REAL z1[1536], z2[2304]; + int z1len, z2len; + REAL detxy[4608]; + int xylen; + REAL adet[6912], bdet[6912], cdet[6912], ddet[6912]; + int alen, blen, clen, dlen; + REAL abdet[13824], cddet[13824], deter[27648]; + int deterlen; + int i; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL a0hi, a0lo, a1hi, a1lo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j, _k, _l, _m, _n; + REAL _0, _1, _2; + + Two_Diff(pa[0], pe[0], aex, aextail); + Two_Diff(pa[1], pe[1], aey, aeytail); + Two_Diff(pa[2], pe[2], aez, aeztail); + Two_Diff(pb[0], pe[0], bex, bextail); + Two_Diff(pb[1], pe[1], bey, beytail); + Two_Diff(pb[2], pe[2], bez, beztail); + Two_Diff(pc[0], pe[0], cex, cextail); + Two_Diff(pc[1], pe[1], cey, ceytail); + Two_Diff(pc[2], pe[2], cez, ceztail); + Two_Diff(pd[0], pe[0], dex, dextail); + Two_Diff(pd[1], pe[1], dey, deytail); + Two_Diff(pd[2], pe[2], dez, deztail); + + Two_Two_Product(aex, aextail, bey, beytail, + axby7, axby[6], axby[5], axby[4], + axby[3], axby[2], axby[1], axby[0]); + axby[7] = axby7; + negate = -aey; + negatetail = -aeytail; + Two_Two_Product(bex, bextail, negate, negatetail, + bxay7, bxay[6], bxay[5], bxay[4], + bxay[3], bxay[2], bxay[1], bxay[0]); + bxay[7] = bxay7; + ablen = fast_expansion_sum_zeroelim(8, axby, 8, bxay, ab); + Two_Two_Product(bex, bextail, cey, ceytail, + bxcy7, bxcy[6], bxcy[5], bxcy[4], + bxcy[3], bxcy[2], bxcy[1], bxcy[0]); + bxcy[7] = bxcy7; + negate = -bey; + negatetail = -beytail; + Two_Two_Product(cex, cextail, negate, negatetail, + cxby7, cxby[6], cxby[5], cxby[4], + cxby[3], cxby[2], cxby[1], cxby[0]); + cxby[7] = cxby7; + bclen = fast_expansion_sum_zeroelim(8, bxcy, 8, cxby, bc); + Two_Two_Product(cex, cextail, dey, deytail, + cxdy7, cxdy[6], cxdy[5], cxdy[4], + cxdy[3], cxdy[2], cxdy[1], cxdy[0]); + cxdy[7] = cxdy7; + negate = -cey; + negatetail = -ceytail; + Two_Two_Product(dex, dextail, negate, negatetail, + dxcy7, dxcy[6], dxcy[5], dxcy[4], + dxcy[3], dxcy[2], dxcy[1], dxcy[0]); + dxcy[7] = dxcy7; + cdlen = fast_expansion_sum_zeroelim(8, cxdy, 8, dxcy, cd); + Two_Two_Product(dex, dextail, aey, aeytail, + dxay7, dxay[6], dxay[5], dxay[4], + dxay[3], dxay[2], dxay[1], dxay[0]); + dxay[7] = dxay7; + negate = -dey; + negatetail = -deytail; + Two_Two_Product(aex, aextail, negate, negatetail, + axdy7, axdy[6], axdy[5], axdy[4], + axdy[3], axdy[2], axdy[1], axdy[0]); + axdy[7] = axdy7; + dalen = fast_expansion_sum_zeroelim(8, dxay, 8, axdy, da); + Two_Two_Product(aex, aextail, cey, ceytail, + axcy7, axcy[6], axcy[5], axcy[4], + axcy[3], axcy[2], axcy[1], axcy[0]); + axcy[7] = axcy7; + negate = -aey; + negatetail = -aeytail; + Two_Two_Product(cex, cextail, negate, negatetail, + cxay7, cxay[6], cxay[5], cxay[4], + cxay[3], cxay[2], cxay[1], cxay[0]); + cxay[7] = cxay7; + aclen = fast_expansion_sum_zeroelim(8, axcy, 8, cxay, ac); + Two_Two_Product(bex, bextail, dey, deytail, + bxdy7, bxdy[6], bxdy[5], bxdy[4], + bxdy[3], bxdy[2], bxdy[1], bxdy[0]); + bxdy[7] = bxdy7; + negate = -bey; + negatetail = -beytail; + Two_Two_Product(dex, dextail, negate, negatetail, + dxby7, dxby[6], dxby[5], dxby[4], + dxby[3], dxby[2], dxby[1], dxby[0]); + dxby[7] = dxby7; + bdlen = fast_expansion_sum_zeroelim(8, bxdy, 8, dxby, bd); + + temp32alen = scale_expansion_zeroelim(cdlen, cd, -bez, temp32a); + temp32blen = scale_expansion_zeroelim(cdlen, cd, -beztail, temp32b); + temp64alen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64a); + temp32alen = scale_expansion_zeroelim(bdlen, bd, cez, temp32a); + temp32blen = scale_expansion_zeroelim(bdlen, bd, ceztail, temp32b); + temp64blen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64b); + temp32alen = scale_expansion_zeroelim(bclen, bc, -dez, temp32a); + temp32blen = scale_expansion_zeroelim(bclen, bc, -deztail, temp32b); + temp64clen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64c); + temp128len = fast_expansion_sum_zeroelim(temp64alen, temp64a, + temp64blen, temp64b, temp128); + temp192len = fast_expansion_sum_zeroelim(temp64clen, temp64c, + temp128len, temp128, temp192); + xlen = scale_expansion_zeroelim(temp192len, temp192, aex, detx); + xxlen = scale_expansion_zeroelim(xlen, detx, aex, detxx); + xtlen = scale_expansion_zeroelim(temp192len, temp192, aextail, detxt); + xxtlen = scale_expansion_zeroelim(xtlen, detxt, aex, detxxt); + for (i = 0; i < xxtlen; i++) { + detxxt[i] *= 2.0; + } + xtxtlen = scale_expansion_zeroelim(xtlen, detxt, aextail, detxtxt); + x1len = fast_expansion_sum_zeroelim(xxlen, detxx, xxtlen, detxxt, x1); + x2len = fast_expansion_sum_zeroelim(x1len, x1, xtxtlen, detxtxt, x2); + ylen = scale_expansion_zeroelim(temp192len, temp192, aey, dety); + yylen = scale_expansion_zeroelim(ylen, dety, aey, detyy); + ytlen = scale_expansion_zeroelim(temp192len, temp192, aeytail, detyt); + yytlen = scale_expansion_zeroelim(ytlen, detyt, aey, detyyt); + for (i = 0; i < yytlen; i++) { + detyyt[i] *= 2.0; + } + ytytlen = scale_expansion_zeroelim(ytlen, detyt, aeytail, detytyt); + y1len = fast_expansion_sum_zeroelim(yylen, detyy, yytlen, detyyt, y1); + y2len = fast_expansion_sum_zeroelim(y1len, y1, ytytlen, detytyt, y2); + zlen = scale_expansion_zeroelim(temp192len, temp192, aez, detz); + zzlen = scale_expansion_zeroelim(zlen, detz, aez, detzz); + ztlen = scale_expansion_zeroelim(temp192len, temp192, aeztail, detzt); + zztlen = scale_expansion_zeroelim(ztlen, detzt, aez, detzzt); + for (i = 0; i < zztlen; i++) { + detzzt[i] *= 2.0; + } + ztztlen = scale_expansion_zeroelim(ztlen, detzt, aeztail, detztzt); + z1len = fast_expansion_sum_zeroelim(zzlen, detzz, zztlen, detzzt, z1); + z2len = fast_expansion_sum_zeroelim(z1len, z1, ztztlen, detztzt, z2); + xylen = fast_expansion_sum_zeroelim(x2len, x2, y2len, y2, detxy); + alen = fast_expansion_sum_zeroelim(z2len, z2, xylen, detxy, adet); + + temp32alen = scale_expansion_zeroelim(dalen, da, cez, temp32a); + temp32blen = scale_expansion_zeroelim(dalen, da, ceztail, temp32b); + temp64alen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64a); + temp32alen = scale_expansion_zeroelim(aclen, ac, dez, temp32a); + temp32blen = scale_expansion_zeroelim(aclen, ac, deztail, temp32b); + temp64blen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64b); + temp32alen = scale_expansion_zeroelim(cdlen, cd, aez, temp32a); + temp32blen = scale_expansion_zeroelim(cdlen, cd, aeztail, temp32b); + temp64clen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64c); + temp128len = fast_expansion_sum_zeroelim(temp64alen, temp64a, + temp64blen, temp64b, temp128); + temp192len = fast_expansion_sum_zeroelim(temp64clen, temp64c, + temp128len, temp128, temp192); + xlen = scale_expansion_zeroelim(temp192len, temp192, bex, detx); + xxlen = scale_expansion_zeroelim(xlen, detx, bex, detxx); + xtlen = scale_expansion_zeroelim(temp192len, temp192, bextail, detxt); + xxtlen = scale_expansion_zeroelim(xtlen, detxt, bex, detxxt); + for (i = 0; i < xxtlen; i++) { + detxxt[i] *= 2.0; + } + xtxtlen = scale_expansion_zeroelim(xtlen, detxt, bextail, detxtxt); + x1len = fast_expansion_sum_zeroelim(xxlen, detxx, xxtlen, detxxt, x1); + x2len = fast_expansion_sum_zeroelim(x1len, x1, xtxtlen, detxtxt, x2); + ylen = scale_expansion_zeroelim(temp192len, temp192, bey, dety); + yylen = scale_expansion_zeroelim(ylen, dety, bey, detyy); + ytlen = scale_expansion_zeroelim(temp192len, temp192, beytail, detyt); + yytlen = scale_expansion_zeroelim(ytlen, detyt, bey, detyyt); + for (i = 0; i < yytlen; i++) { + detyyt[i] *= 2.0; + } + ytytlen = scale_expansion_zeroelim(ytlen, detyt, beytail, detytyt); + y1len = fast_expansion_sum_zeroelim(yylen, detyy, yytlen, detyyt, y1); + y2len = fast_expansion_sum_zeroelim(y1len, y1, ytytlen, detytyt, y2); + zlen = scale_expansion_zeroelim(temp192len, temp192, bez, detz); + zzlen = scale_expansion_zeroelim(zlen, detz, bez, detzz); + ztlen = scale_expansion_zeroelim(temp192len, temp192, beztail, detzt); + zztlen = scale_expansion_zeroelim(ztlen, detzt, bez, detzzt); + for (i = 0; i < zztlen; i++) { + detzzt[i] *= 2.0; + } + ztztlen = scale_expansion_zeroelim(ztlen, detzt, beztail, detztzt); + z1len = fast_expansion_sum_zeroelim(zzlen, detzz, zztlen, detzzt, z1); + z2len = fast_expansion_sum_zeroelim(z1len, z1, ztztlen, detztzt, z2); + xylen = fast_expansion_sum_zeroelim(x2len, x2, y2len, y2, detxy); + blen = fast_expansion_sum_zeroelim(z2len, z2, xylen, detxy, bdet); + + temp32alen = scale_expansion_zeroelim(ablen, ab, -dez, temp32a); + temp32blen = scale_expansion_zeroelim(ablen, ab, -deztail, temp32b); + temp64alen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64a); + temp32alen = scale_expansion_zeroelim(bdlen, bd, -aez, temp32a); + temp32blen = scale_expansion_zeroelim(bdlen, bd, -aeztail, temp32b); + temp64blen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64b); + temp32alen = scale_expansion_zeroelim(dalen, da, -bez, temp32a); + temp32blen = scale_expansion_zeroelim(dalen, da, -beztail, temp32b); + temp64clen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64c); + temp128len = fast_expansion_sum_zeroelim(temp64alen, temp64a, + temp64blen, temp64b, temp128); + temp192len = fast_expansion_sum_zeroelim(temp64clen, temp64c, + temp128len, temp128, temp192); + xlen = scale_expansion_zeroelim(temp192len, temp192, cex, detx); + xxlen = scale_expansion_zeroelim(xlen, detx, cex, detxx); + xtlen = scale_expansion_zeroelim(temp192len, temp192, cextail, detxt); + xxtlen = scale_expansion_zeroelim(xtlen, detxt, cex, detxxt); + for (i = 0; i < xxtlen; i++) { + detxxt[i] *= 2.0; + } + xtxtlen = scale_expansion_zeroelim(xtlen, detxt, cextail, detxtxt); + x1len = fast_expansion_sum_zeroelim(xxlen, detxx, xxtlen, detxxt, x1); + x2len = fast_expansion_sum_zeroelim(x1len, x1, xtxtlen, detxtxt, x2); + ylen = scale_expansion_zeroelim(temp192len, temp192, cey, dety); + yylen = scale_expansion_zeroelim(ylen, dety, cey, detyy); + ytlen = scale_expansion_zeroelim(temp192len, temp192, ceytail, detyt); + yytlen = scale_expansion_zeroelim(ytlen, detyt, cey, detyyt); + for (i = 0; i < yytlen; i++) { + detyyt[i] *= 2.0; + } + ytytlen = scale_expansion_zeroelim(ytlen, detyt, ceytail, detytyt); + y1len = fast_expansion_sum_zeroelim(yylen, detyy, yytlen, detyyt, y1); + y2len = fast_expansion_sum_zeroelim(y1len, y1, ytytlen, detytyt, y2); + zlen = scale_expansion_zeroelim(temp192len, temp192, cez, detz); + zzlen = scale_expansion_zeroelim(zlen, detz, cez, detzz); + ztlen = scale_expansion_zeroelim(temp192len, temp192, ceztail, detzt); + zztlen = scale_expansion_zeroelim(ztlen, detzt, cez, detzzt); + for (i = 0; i < zztlen; i++) { + detzzt[i] *= 2.0; + } + ztztlen = scale_expansion_zeroelim(ztlen, detzt, ceztail, detztzt); + z1len = fast_expansion_sum_zeroelim(zzlen, detzz, zztlen, detzzt, z1); + z2len = fast_expansion_sum_zeroelim(z1len, z1, ztztlen, detztzt, z2); + xylen = fast_expansion_sum_zeroelim(x2len, x2, y2len, y2, detxy); + clen = fast_expansion_sum_zeroelim(z2len, z2, xylen, detxy, cdet); + + temp32alen = scale_expansion_zeroelim(bclen, bc, aez, temp32a); + temp32blen = scale_expansion_zeroelim(bclen, bc, aeztail, temp32b); + temp64alen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64a); + temp32alen = scale_expansion_zeroelim(aclen, ac, -bez, temp32a); + temp32blen = scale_expansion_zeroelim(aclen, ac, -beztail, temp32b); + temp64blen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64b); + temp32alen = scale_expansion_zeroelim(ablen, ab, cez, temp32a); + temp32blen = scale_expansion_zeroelim(ablen, ab, ceztail, temp32b); + temp64clen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64c); + temp128len = fast_expansion_sum_zeroelim(temp64alen, temp64a, + temp64blen, temp64b, temp128); + temp192len = fast_expansion_sum_zeroelim(temp64clen, temp64c, + temp128len, temp128, temp192); + xlen = scale_expansion_zeroelim(temp192len, temp192, dex, detx); + xxlen = scale_expansion_zeroelim(xlen, detx, dex, detxx); + xtlen = scale_expansion_zeroelim(temp192len, temp192, dextail, detxt); + xxtlen = scale_expansion_zeroelim(xtlen, detxt, dex, detxxt); + for (i = 0; i < xxtlen; i++) { + detxxt[i] *= 2.0; + } + xtxtlen = scale_expansion_zeroelim(xtlen, detxt, dextail, detxtxt); + x1len = fast_expansion_sum_zeroelim(xxlen, detxx, xxtlen, detxxt, x1); + x2len = fast_expansion_sum_zeroelim(x1len, x1, xtxtlen, detxtxt, x2); + ylen = scale_expansion_zeroelim(temp192len, temp192, dey, dety); + yylen = scale_expansion_zeroelim(ylen, dety, dey, detyy); + ytlen = scale_expansion_zeroelim(temp192len, temp192, deytail, detyt); + yytlen = scale_expansion_zeroelim(ytlen, detyt, dey, detyyt); + for (i = 0; i < yytlen; i++) { + detyyt[i] *= 2.0; + } + ytytlen = scale_expansion_zeroelim(ytlen, detyt, deytail, detytyt); + y1len = fast_expansion_sum_zeroelim(yylen, detyy, yytlen, detyyt, y1); + y2len = fast_expansion_sum_zeroelim(y1len, y1, ytytlen, detytyt, y2); + zlen = scale_expansion_zeroelim(temp192len, temp192, dez, detz); + zzlen = scale_expansion_zeroelim(zlen, detz, dez, detzz); + ztlen = scale_expansion_zeroelim(temp192len, temp192, deztail, detzt); + zztlen = scale_expansion_zeroelim(ztlen, detzt, dez, detzzt); + for (i = 0; i < zztlen; i++) { + detzzt[i] *= 2.0; + } + ztztlen = scale_expansion_zeroelim(ztlen, detzt, deztail, detztzt); + z1len = fast_expansion_sum_zeroelim(zzlen, detzz, zztlen, detzzt, z1); + z2len = fast_expansion_sum_zeroelim(z1len, z1, ztztlen, detztzt, z2); + xylen = fast_expansion_sum_zeroelim(x2len, x2, y2len, y2, detxy); + dlen = fast_expansion_sum_zeroelim(z2len, z2, xylen, detxy, ddet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + cdlen = fast_expansion_sum_zeroelim(clen, cdet, dlen, ddet, cddet); + deterlen = fast_expansion_sum_zeroelim(ablen, abdet, cdlen, cddet, deter); + + return deter[deterlen - 1]; +} + +REAL insphereadapt(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL *pe, + REAL permanent) +{ + INEXACT REAL aex, bex, cex, dex, aey, bey, cey, dey, aez, bez, cez, dez; + REAL det, errbound; + + INEXACT REAL aexbey1, bexaey1, bexcey1, cexbey1; + INEXACT REAL cexdey1, dexcey1, dexaey1, aexdey1; + INEXACT REAL aexcey1, cexaey1, bexdey1, dexbey1; + REAL aexbey0, bexaey0, bexcey0, cexbey0; + REAL cexdey0, dexcey0, dexaey0, aexdey0; + REAL aexcey0, cexaey0, bexdey0, dexbey0; + REAL ab[4], bc[4], cd[4], da[4], ac[4], bd[4]; + INEXACT REAL ab3, bc3, cd3, da3, ac3, bd3; + REAL abeps, bceps, cdeps, daeps, aceps, bdeps; + REAL temp8a[8], temp8b[8], temp8c[8], temp16[16], temp24[24], temp48[48]; + int temp8alen, temp8blen, temp8clen, temp16len, temp24len, temp48len; + REAL xdet[96], ydet[96], zdet[96], xydet[192]; + int xlen, ylen, zlen, xylen; + REAL adet[288], bdet[288], cdet[288], ddet[288]; + int alen, blen, clen, dlen; + REAL abdet[576], cddet[576]; + int ablen, cdlen; + REAL fin1[1152]; + int finlength; + + REAL aextail, bextail, cextail, dextail; + REAL aeytail, beytail, ceytail, deytail; + REAL aeztail, beztail, ceztail, deztail; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j; + REAL _0; + + + aex = (REAL) (pa[0] - pe[0]); + bex = (REAL) (pb[0] - pe[0]); + cex = (REAL) (pc[0] - pe[0]); + dex = (REAL) (pd[0] - pe[0]); + aey = (REAL) (pa[1] - pe[1]); + bey = (REAL) (pb[1] - pe[1]); + cey = (REAL) (pc[1] - pe[1]); + dey = (REAL) (pd[1] - pe[1]); + aez = (REAL) (pa[2] - pe[2]); + bez = (REAL) (pb[2] - pe[2]); + cez = (REAL) (pc[2] - pe[2]); + dez = (REAL) (pd[2] - pe[2]); + + Two_Product(aex, bey, aexbey1, aexbey0); + Two_Product(bex, aey, bexaey1, bexaey0); + Two_Two_Diff(aexbey1, aexbey0, bexaey1, bexaey0, ab3, ab[2], ab[1], ab[0]); + ab[3] = ab3; + + Two_Product(bex, cey, bexcey1, bexcey0); + Two_Product(cex, bey, cexbey1, cexbey0); + Two_Two_Diff(bexcey1, bexcey0, cexbey1, cexbey0, bc3, bc[2], bc[1], bc[0]); + bc[3] = bc3; + + Two_Product(cex, dey, cexdey1, cexdey0); + Two_Product(dex, cey, dexcey1, dexcey0); + Two_Two_Diff(cexdey1, cexdey0, dexcey1, dexcey0, cd3, cd[2], cd[1], cd[0]); + cd[3] = cd3; + + Two_Product(dex, aey, dexaey1, dexaey0); + Two_Product(aex, dey, aexdey1, aexdey0); + Two_Two_Diff(dexaey1, dexaey0, aexdey1, aexdey0, da3, da[2], da[1], da[0]); + da[3] = da3; + + Two_Product(aex, cey, aexcey1, aexcey0); + Two_Product(cex, aey, cexaey1, cexaey0); + Two_Two_Diff(aexcey1, aexcey0, cexaey1, cexaey0, ac3, ac[2], ac[1], ac[0]); + ac[3] = ac3; + + Two_Product(bex, dey, bexdey1, bexdey0); + Two_Product(dex, bey, dexbey1, dexbey0); + Two_Two_Diff(bexdey1, bexdey0, dexbey1, dexbey0, bd3, bd[2], bd[1], bd[0]); + bd[3] = bd3; + + temp8alen = scale_expansion_zeroelim(4, cd, bez, temp8a); + temp8blen = scale_expansion_zeroelim(4, bd, -cez, temp8b); + temp8clen = scale_expansion_zeroelim(4, bc, dez, temp8c); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, + temp8blen, temp8b, temp16); + temp24len = fast_expansion_sum_zeroelim(temp8clen, temp8c, + temp16len, temp16, temp24); + temp48len = scale_expansion_zeroelim(temp24len, temp24, aex, temp48); + xlen = scale_expansion_zeroelim(temp48len, temp48, -aex, xdet); + temp48len = scale_expansion_zeroelim(temp24len, temp24, aey, temp48); + ylen = scale_expansion_zeroelim(temp48len, temp48, -aey, ydet); + temp48len = scale_expansion_zeroelim(temp24len, temp24, aez, temp48); + zlen = scale_expansion_zeroelim(temp48len, temp48, -aez, zdet); + xylen = fast_expansion_sum_zeroelim(xlen, xdet, ylen, ydet, xydet); + alen = fast_expansion_sum_zeroelim(xylen, xydet, zlen, zdet, adet); + + temp8alen = scale_expansion_zeroelim(4, da, cez, temp8a); + temp8blen = scale_expansion_zeroelim(4, ac, dez, temp8b); + temp8clen = scale_expansion_zeroelim(4, cd, aez, temp8c); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, + temp8blen, temp8b, temp16); + temp24len = fast_expansion_sum_zeroelim(temp8clen, temp8c, + temp16len, temp16, temp24); + temp48len = scale_expansion_zeroelim(temp24len, temp24, bex, temp48); + xlen = scale_expansion_zeroelim(temp48len, temp48, bex, xdet); + temp48len = scale_expansion_zeroelim(temp24len, temp24, bey, temp48); + ylen = scale_expansion_zeroelim(temp48len, temp48, bey, ydet); + temp48len = scale_expansion_zeroelim(temp24len, temp24, bez, temp48); + zlen = scale_expansion_zeroelim(temp48len, temp48, bez, zdet); + xylen = fast_expansion_sum_zeroelim(xlen, xdet, ylen, ydet, xydet); + blen = fast_expansion_sum_zeroelim(xylen, xydet, zlen, zdet, bdet); + + temp8alen = scale_expansion_zeroelim(4, ab, dez, temp8a); + temp8blen = scale_expansion_zeroelim(4, bd, aez, temp8b); + temp8clen = scale_expansion_zeroelim(4, da, bez, temp8c); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, + temp8blen, temp8b, temp16); + temp24len = fast_expansion_sum_zeroelim(temp8clen, temp8c, + temp16len, temp16, temp24); + temp48len = scale_expansion_zeroelim(temp24len, temp24, cex, temp48); + xlen = scale_expansion_zeroelim(temp48len, temp48, -cex, xdet); + temp48len = scale_expansion_zeroelim(temp24len, temp24, cey, temp48); + ylen = scale_expansion_zeroelim(temp48len, temp48, -cey, ydet); + temp48len = scale_expansion_zeroelim(temp24len, temp24, cez, temp48); + zlen = scale_expansion_zeroelim(temp48len, temp48, -cez, zdet); + xylen = fast_expansion_sum_zeroelim(xlen, xdet, ylen, ydet, xydet); + clen = fast_expansion_sum_zeroelim(xylen, xydet, zlen, zdet, cdet); + + temp8alen = scale_expansion_zeroelim(4, bc, aez, temp8a); + temp8blen = scale_expansion_zeroelim(4, ac, -bez, temp8b); + temp8clen = scale_expansion_zeroelim(4, ab, cez, temp8c); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, + temp8blen, temp8b, temp16); + temp24len = fast_expansion_sum_zeroelim(temp8clen, temp8c, + temp16len, temp16, temp24); + temp48len = scale_expansion_zeroelim(temp24len, temp24, dex, temp48); + xlen = scale_expansion_zeroelim(temp48len, temp48, dex, xdet); + temp48len = scale_expansion_zeroelim(temp24len, temp24, dey, temp48); + ylen = scale_expansion_zeroelim(temp48len, temp48, dey, ydet); + temp48len = scale_expansion_zeroelim(temp24len, temp24, dez, temp48); + zlen = scale_expansion_zeroelim(temp48len, temp48, dez, zdet); + xylen = fast_expansion_sum_zeroelim(xlen, xdet, ylen, ydet, xydet); + dlen = fast_expansion_sum_zeroelim(xylen, xydet, zlen, zdet, ddet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + cdlen = fast_expansion_sum_zeroelim(clen, cdet, dlen, ddet, cddet); + finlength = fast_expansion_sum_zeroelim(ablen, abdet, cdlen, cddet, fin1); + + det = estimate(finlength, fin1); + errbound = isperrboundB * permanent; + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + Two_Diff_Tail(pa[0], pe[0], aex, aextail); + Two_Diff_Tail(pa[1], pe[1], aey, aeytail); + Two_Diff_Tail(pa[2], pe[2], aez, aeztail); + Two_Diff_Tail(pb[0], pe[0], bex, bextail); + Two_Diff_Tail(pb[1], pe[1], bey, beytail); + Two_Diff_Tail(pb[2], pe[2], bez, beztail); + Two_Diff_Tail(pc[0], pe[0], cex, cextail); + Two_Diff_Tail(pc[1], pe[1], cey, ceytail); + Two_Diff_Tail(pc[2], pe[2], cez, ceztail); + Two_Diff_Tail(pd[0], pe[0], dex, dextail); + Two_Diff_Tail(pd[1], pe[1], dey, deytail); + Two_Diff_Tail(pd[2], pe[2], dez, deztail); + if ((aextail == 0.0) && (aeytail == 0.0) && (aeztail == 0.0) + && (bextail == 0.0) && (beytail == 0.0) && (beztail == 0.0) + && (cextail == 0.0) && (ceytail == 0.0) && (ceztail == 0.0) + && (dextail == 0.0) && (deytail == 0.0) && (deztail == 0.0)) { + return det; + } + + errbound = isperrboundC * permanent + resulterrbound * Absolute(det); + abeps = (aex * beytail + bey * aextail) + - (aey * bextail + bex * aeytail); + bceps = (bex * ceytail + cey * bextail) + - (bey * cextail + cex * beytail); + cdeps = (cex * deytail + dey * cextail) + - (cey * dextail + dex * ceytail); + daeps = (dex * aeytail + aey * dextail) + - (dey * aextail + aex * deytail); + aceps = (aex * ceytail + cey * aextail) + - (aey * cextail + cex * aeytail); + bdeps = (bex * deytail + dey * bextail) + - (bey * dextail + dex * beytail); + det += (((bex * bex + bey * bey + bez * bez) + * ((cez * daeps + dez * aceps + aez * cdeps) + + (ceztail * da3 + deztail * ac3 + aeztail * cd3)) + + (dex * dex + dey * dey + dez * dez) + * ((aez * bceps - bez * aceps + cez * abeps) + + (aeztail * bc3 - beztail * ac3 + ceztail * ab3))) + - ((aex * aex + aey * aey + aez * aez) + * ((bez * cdeps - cez * bdeps + dez * bceps) + + (beztail * cd3 - ceztail * bd3 + deztail * bc3)) + + (cex * cex + cey * cey + cez * cez) + * ((dez * abeps + aez * bdeps + bez * daeps) + + (deztail * ab3 + aeztail * bd3 + beztail * da3)))) + + 2.0 * (((bex * bextail + bey * beytail + bez * beztail) + * (cez * da3 + dez * ac3 + aez * cd3) + + (dex * dextail + dey * deytail + dez * deztail) + * (aez * bc3 - bez * ac3 + cez * ab3)) + - ((aex * aextail + aey * aeytail + aez * aeztail) + * (bez * cd3 - cez * bd3 + dez * bc3) + + (cex * cextail + cey * ceytail + cez * ceztail) + * (dez * ab3 + aez * bd3 + bez * da3))); + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + return insphereexact(pa, pb, pc, pd, pe); +} + +#ifdef USE_CGAL_PREDICATES + +REAL insphere(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL *pe) +{ + return (REAL) + - cgal_pred_obj.side_of_oriented_sphere_3_object() + (Point(pa[0], pa[1], pa[2]), + Point(pb[0], pb[1], pb[2]), + Point(pc[0], pc[1], pc[2]), + Point(pd[0], pd[1], pd[2]), + Point(pe[0], pe[1], pe[2])); +} + +#else + +REAL insphere(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL *pe) +{ + REAL aex, bex, cex, dex; + REAL aey, bey, cey, dey; + REAL aez, bez, cez, dez; + REAL aexbey, bexaey, bexcey, cexbey, cexdey, dexcey, dexaey, aexdey; + REAL aexcey, cexaey, bexdey, dexbey; + REAL alift, blift, clift, dlift; + REAL ab, bc, cd, da, ac, bd; + REAL abc, bcd, cda, dab; + REAL det; + + + aex = pa[0] - pe[0]; + bex = pb[0] - pe[0]; + cex = pc[0] - pe[0]; + dex = pd[0] - pe[0]; + aey = pa[1] - pe[1]; + bey = pb[1] - pe[1]; + cey = pc[1] - pe[1]; + dey = pd[1] - pe[1]; + aez = pa[2] - pe[2]; + bez = pb[2] - pe[2]; + cez = pc[2] - pe[2]; + dez = pd[2] - pe[2]; + + aexbey = aex * bey; + bexaey = bex * aey; + ab = aexbey - bexaey; + bexcey = bex * cey; + cexbey = cex * bey; + bc = bexcey - cexbey; + cexdey = cex * dey; + dexcey = dex * cey; + cd = cexdey - dexcey; + dexaey = dex * aey; + aexdey = aex * dey; + da = dexaey - aexdey; + + aexcey = aex * cey; + cexaey = cex * aey; + ac = aexcey - cexaey; + bexdey = bex * dey; + dexbey = dex * bey; + bd = bexdey - dexbey; + + abc = aez * bc - bez * ac + cez * ab; + bcd = bez * cd - cez * bd + dez * bc; + cda = cez * da + dez * ac + aez * cd; + dab = dez * ab + aez * bd + bez * da; + + alift = aex * aex + aey * aey + aez * aez; + blift = bex * bex + bey * bey + bez * bez; + clift = cex * cex + cey * cey + cez * cez; + dlift = dex * dex + dey * dey + dez * dez; + + det = (dlift * abc - clift * dab) + (blift * cda - alift * bcd); + + if (_use_inexact_arith) { + return det; + } + + if (_use_static_filter) { + if (fabs(det) > ispstaticfilter) return det; + //if (det > ispstaticfilter) return det; + //if (det < minus_ispstaticfilter) return det; + + } + + REAL aezplus, bezplus, cezplus, dezplus; + REAL aexbeyplus, bexaeyplus, bexceyplus, cexbeyplus; + REAL cexdeyplus, dexceyplus, dexaeyplus, aexdeyplus; + REAL aexceyplus, cexaeyplus, bexdeyplus, dexbeyplus; + REAL permanent, errbound; + + aezplus = Absolute(aez); + bezplus = Absolute(bez); + cezplus = Absolute(cez); + dezplus = Absolute(dez); + aexbeyplus = Absolute(aexbey); + bexaeyplus = Absolute(bexaey); + bexceyplus = Absolute(bexcey); + cexbeyplus = Absolute(cexbey); + cexdeyplus = Absolute(cexdey); + dexceyplus = Absolute(dexcey); + dexaeyplus = Absolute(dexaey); + aexdeyplus = Absolute(aexdey); + aexceyplus = Absolute(aexcey); + cexaeyplus = Absolute(cexaey); + bexdeyplus = Absolute(bexdey); + dexbeyplus = Absolute(dexbey); + permanent = ((cexdeyplus + dexceyplus) * bezplus + + (dexbeyplus + bexdeyplus) * cezplus + + (bexceyplus + cexbeyplus) * dezplus) + * alift + + ((dexaeyplus + aexdeyplus) * cezplus + + (aexceyplus + cexaeyplus) * dezplus + + (cexdeyplus + dexceyplus) * aezplus) + * blift + + ((aexbeyplus + bexaeyplus) * dezplus + + (bexdeyplus + dexbeyplus) * aezplus + + (dexaeyplus + aexdeyplus) * bezplus) + * clift + + ((bexceyplus + cexbeyplus) * aezplus + + (cexaeyplus + aexceyplus) * bezplus + + (aexbeyplus + bexaeyplus) * cezplus) + * dlift; + errbound = isperrboundA * permanent; + if ((det > errbound) || (-det > errbound)) { + return det; + } + + return insphereadapt(pa, pb, pc, pd, pe, permanent); +} + +#endif // #ifdef USE_CGAL_PREDICATES + +/*****************************************************************************/ +/* */ +/* orient4d() Return a positive value if the point pe lies above the */ +/* hyperplane passing through pa, pb, pc, and pd; "above" is */ +/* defined in a manner best found by trial-and-error. Returns */ +/* a negative value if pe lies below the hyperplane. Returns */ +/* zero if the points are co-hyperplanar (not affinely */ +/* independent). The result is also a rough approximation of */ +/* 24 times the signed volume of the 4-simplex defined by the */ +/* five points. */ +/* */ +/* Uses exact arithmetic if necessary to ensure a correct answer. The */ +/* result returned is the determinant of a matrix. This determinant is */ +/* computed adaptively, in the sense that exact arithmetic is used only to */ +/* the degree it is needed to ensure that the returned value has the */ +/* correct sign. Hence, orient4d() is usually quite fast, but will run */ +/* more slowly when the input points are hyper-coplanar or nearly so. */ +/* */ +/* See my Robust Predicates paper for details. */ +/* */ +/*****************************************************************************/ +//static +REAL orient4dexact(REAL* pa, REAL* pb, REAL* pc, REAL* pd, REAL* pe, + REAL aheight, REAL bheight, REAL cheight, REAL dheight, + REAL eheight) +{ + INEXACT REAL axby1, bxcy1, cxdy1, dxey1, exay1; + INEXACT REAL bxay1, cxby1, dxcy1, exdy1, axey1; + INEXACT REAL axcy1, bxdy1, cxey1, dxay1, exby1; + INEXACT REAL cxay1, dxby1, excy1, axdy1, bxey1; + REAL axby0, bxcy0, cxdy0, dxey0, exay0; + REAL bxay0, cxby0, dxcy0, exdy0, axey0; + REAL axcy0, bxdy0, cxey0, dxay0, exby0; + REAL cxay0, dxby0, excy0, axdy0, bxey0; + REAL ab[4], bc[4], cd[4], de[4], ea[4]; + REAL ac[4], bd[4], ce[4], da[4], eb[4]; + REAL temp8a[8], temp8b[8], temp16[16]; + int temp8alen, temp8blen, temp16len; + REAL abc[24], bcd[24], cde[24], dea[24], eab[24]; + REAL abd[24], bce[24], cda[24], deb[24], eac[24]; + int abclen, bcdlen, cdelen, dealen, eablen; + int abdlen, bcelen, cdalen, deblen, eaclen; + REAL temp48a[48], temp48b[48]; + int temp48alen, temp48blen; + REAL abcd[96], bcde[96], cdea[96], deab[96], eabc[96]; + int abcdlen, bcdelen, cdealen, deablen, eabclen; + REAL adet[192], bdet[192], cdet[192], ddet[192], edet[192]; + int alen, blen, clen, dlen, elen; + REAL abdet[384], cddet[384], cdedet[576]; + int ablen, cdlen; + REAL deter[960]; + int deterlen; + int i; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j; + REAL _0; + + + Two_Product(pa[0], pb[1], axby1, axby0); + Two_Product(pb[0], pa[1], bxay1, bxay0); + Two_Two_Diff(axby1, axby0, bxay1, bxay0, ab[3], ab[2], ab[1], ab[0]); + + Two_Product(pb[0], pc[1], bxcy1, bxcy0); + Two_Product(pc[0], pb[1], cxby1, cxby0); + Two_Two_Diff(bxcy1, bxcy0, cxby1, cxby0, bc[3], bc[2], bc[1], bc[0]); + + Two_Product(pc[0], pd[1], cxdy1, cxdy0); + Two_Product(pd[0], pc[1], dxcy1, dxcy0); + Two_Two_Diff(cxdy1, cxdy0, dxcy1, dxcy0, cd[3], cd[2], cd[1], cd[0]); + + Two_Product(pd[0], pe[1], dxey1, dxey0); + Two_Product(pe[0], pd[1], exdy1, exdy0); + Two_Two_Diff(dxey1, dxey0, exdy1, exdy0, de[3], de[2], de[1], de[0]); + + Two_Product(pe[0], pa[1], exay1, exay0); + Two_Product(pa[0], pe[1], axey1, axey0); + Two_Two_Diff(exay1, exay0, axey1, axey0, ea[3], ea[2], ea[1], ea[0]); + + Two_Product(pa[0], pc[1], axcy1, axcy0); + Two_Product(pc[0], pa[1], cxay1, cxay0); + Two_Two_Diff(axcy1, axcy0, cxay1, cxay0, ac[3], ac[2], ac[1], ac[0]); + + Two_Product(pb[0], pd[1], bxdy1, bxdy0); + Two_Product(pd[0], pb[1], dxby1, dxby0); + Two_Two_Diff(bxdy1, bxdy0, dxby1, dxby0, bd[3], bd[2], bd[1], bd[0]); + + Two_Product(pc[0], pe[1], cxey1, cxey0); + Two_Product(pe[0], pc[1], excy1, excy0); + Two_Two_Diff(cxey1, cxey0, excy1, excy0, ce[3], ce[2], ce[1], ce[0]); + + Two_Product(pd[0], pa[1], dxay1, dxay0); + Two_Product(pa[0], pd[1], axdy1, axdy0); + Two_Two_Diff(dxay1, dxay0, axdy1, axdy0, da[3], da[2], da[1], da[0]); + + Two_Product(pe[0], pb[1], exby1, exby0); + Two_Product(pb[0], pe[1], bxey1, bxey0); + Two_Two_Diff(exby1, exby0, bxey1, bxey0, eb[3], eb[2], eb[1], eb[0]); + + temp8alen = scale_expansion_zeroelim(4, bc, pa[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, ac, -pb[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, ab, pc[2], temp8a); + abclen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + abc); + + temp8alen = scale_expansion_zeroelim(4, cd, pb[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, bd, -pc[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, bc, pd[2], temp8a); + bcdlen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + bcd); + + temp8alen = scale_expansion_zeroelim(4, de, pc[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, ce, -pd[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, cd, pe[2], temp8a); + cdelen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + cde); + + temp8alen = scale_expansion_zeroelim(4, ea, pd[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, da, -pe[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, de, pa[2], temp8a); + dealen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + dea); + + temp8alen = scale_expansion_zeroelim(4, ab, pe[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, eb, -pa[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, ea, pb[2], temp8a); + eablen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + eab); + + temp8alen = scale_expansion_zeroelim(4, bd, pa[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, da, pb[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, ab, pd[2], temp8a); + abdlen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + abd); + + temp8alen = scale_expansion_zeroelim(4, ce, pb[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, eb, pc[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, bc, pe[2], temp8a); + bcelen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + bce); + + temp8alen = scale_expansion_zeroelim(4, da, pc[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, ac, pd[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, cd, pa[2], temp8a); + cdalen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + cda); + + temp8alen = scale_expansion_zeroelim(4, eb, pd[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, bd, pe[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, de, pb[2], temp8a); + deblen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + deb); + + temp8alen = scale_expansion_zeroelim(4, ac, pe[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, ce, pa[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, ea, pc[2], temp8a); + eaclen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + eac); + + temp48alen = fast_expansion_sum_zeroelim(cdelen, cde, bcelen, bce, temp48a); + temp48blen = fast_expansion_sum_zeroelim(deblen, deb, bcdlen, bcd, temp48b); + for (i = 0; i < temp48blen; i++) { + temp48b[i] = -temp48b[i]; + } + bcdelen = fast_expansion_sum_zeroelim(temp48alen, temp48a, + temp48blen, temp48b, bcde); + alen = scale_expansion_zeroelim(bcdelen, bcde, aheight, adet); + + temp48alen = fast_expansion_sum_zeroelim(dealen, dea, cdalen, cda, temp48a); + temp48blen = fast_expansion_sum_zeroelim(eaclen, eac, cdelen, cde, temp48b); + for (i = 0; i < temp48blen; i++) { + temp48b[i] = -temp48b[i]; + } + cdealen = fast_expansion_sum_zeroelim(temp48alen, temp48a, + temp48blen, temp48b, cdea); + blen = scale_expansion_zeroelim(cdealen, cdea, bheight, bdet); + + temp48alen = fast_expansion_sum_zeroelim(eablen, eab, deblen, deb, temp48a); + temp48blen = fast_expansion_sum_zeroelim(abdlen, abd, dealen, dea, temp48b); + for (i = 0; i < temp48blen; i++) { + temp48b[i] = -temp48b[i]; + } + deablen = fast_expansion_sum_zeroelim(temp48alen, temp48a, + temp48blen, temp48b, deab); + clen = scale_expansion_zeroelim(deablen, deab, cheight, cdet); + + temp48alen = fast_expansion_sum_zeroelim(abclen, abc, eaclen, eac, temp48a); + temp48blen = fast_expansion_sum_zeroelim(bcelen, bce, eablen, eab, temp48b); + for (i = 0; i < temp48blen; i++) { + temp48b[i] = -temp48b[i]; + } + eabclen = fast_expansion_sum_zeroelim(temp48alen, temp48a, + temp48blen, temp48b, eabc); + dlen = scale_expansion_zeroelim(eabclen, eabc, dheight, ddet); + + temp48alen = fast_expansion_sum_zeroelim(bcdlen, bcd, abdlen, abd, temp48a); + temp48blen = fast_expansion_sum_zeroelim(cdalen, cda, abclen, abc, temp48b); + for (i = 0; i < temp48blen; i++) { + temp48b[i] = -temp48b[i]; + } + abcdlen = fast_expansion_sum_zeroelim(temp48alen, temp48a, + temp48blen, temp48b, abcd); + elen = scale_expansion_zeroelim(abcdlen, abcd, eheight, edet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + cdlen = fast_expansion_sum_zeroelim(clen, cdet, dlen, ddet, cddet); + cdelen = fast_expansion_sum_zeroelim(cdlen, cddet, elen, edet, cdedet); + deterlen = fast_expansion_sum_zeroelim(ablen, abdet, cdelen, cdedet, deter); + + return deter[deterlen - 1]; +} + +static +REAL orient4dadapt(REAL* pa, REAL* pb, REAL* pc, REAL* pd, REAL* pe, + REAL aheight, REAL bheight, REAL cheight, REAL dheight, + REAL eheight, REAL permanent) +{ + INEXACT REAL aex, bex, cex, dex, aey, bey, cey, dey, aez, bez, cez, dez; + INEXACT REAL aeheight, beheight, ceheight, deheight; + REAL det, errbound; + + INEXACT REAL aexbey1, bexaey1, bexcey1, cexbey1; + INEXACT REAL cexdey1, dexcey1, dexaey1, aexdey1; + INEXACT REAL aexcey1, cexaey1, bexdey1, dexbey1; + REAL aexbey0, bexaey0, bexcey0, cexbey0; + REAL cexdey0, dexcey0, dexaey0, aexdey0; + REAL aexcey0, cexaey0, bexdey0, dexbey0; + REAL ab[4], bc[4], cd[4], da[4], ac[4], bd[4]; + INEXACT REAL ab3, bc3, cd3, da3, ac3, bd3; + REAL abeps, bceps, cdeps, daeps, aceps, bdeps; + REAL temp8a[8], temp8b[8], temp8c[8], temp16[16], temp24[24]; + int temp8alen, temp8blen, temp8clen, temp16len, temp24len; + REAL adet[48], bdet[48], cdet[48], ddet[48]; + int alen, blen, clen, dlen; + REAL abdet[96], cddet[96]; + int ablen, cdlen; + REAL fin1[192]; + int finlength; + + REAL aextail, bextail, cextail, dextail; + REAL aeytail, beytail, ceytail, deytail; + REAL aeztail, beztail, ceztail, deztail; + REAL aeheighttail, beheighttail, ceheighttail, deheighttail; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j; + REAL _0; + + + aex = (REAL) (pa[0] - pe[0]); + bex = (REAL) (pb[0] - pe[0]); + cex = (REAL) (pc[0] - pe[0]); + dex = (REAL) (pd[0] - pe[0]); + aey = (REAL) (pa[1] - pe[1]); + bey = (REAL) (pb[1] - pe[1]); + cey = (REAL) (pc[1] - pe[1]); + dey = (REAL) (pd[1] - pe[1]); + aez = (REAL) (pa[2] - pe[2]); + bez = (REAL) (pb[2] - pe[2]); + cez = (REAL) (pc[2] - pe[2]); + dez = (REAL) (pd[2] - pe[2]); + aeheight = (REAL) (aheight - eheight); + beheight = (REAL) (bheight - eheight); + ceheight = (REAL) (cheight - eheight); + deheight = (REAL) (dheight - eheight); + + Two_Product(aex, bey, aexbey1, aexbey0); + Two_Product(bex, aey, bexaey1, bexaey0); + Two_Two_Diff(aexbey1, aexbey0, bexaey1, bexaey0, ab3, ab[2], ab[1], ab[0]); + ab[3] = ab3; + + Two_Product(bex, cey, bexcey1, bexcey0); + Two_Product(cex, bey, cexbey1, cexbey0); + Two_Two_Diff(bexcey1, bexcey0, cexbey1, cexbey0, bc3, bc[2], bc[1], bc[0]); + bc[3] = bc3; + + Two_Product(cex, dey, cexdey1, cexdey0); + Two_Product(dex, cey, dexcey1, dexcey0); + Two_Two_Diff(cexdey1, cexdey0, dexcey1, dexcey0, cd3, cd[2], cd[1], cd[0]); + cd[3] = cd3; + + Two_Product(dex, aey, dexaey1, dexaey0); + Two_Product(aex, dey, aexdey1, aexdey0); + Two_Two_Diff(dexaey1, dexaey0, aexdey1, aexdey0, da3, da[2], da[1], da[0]); + da[3] = da3; + + Two_Product(aex, cey, aexcey1, aexcey0); + Two_Product(cex, aey, cexaey1, cexaey0); + Two_Two_Diff(aexcey1, aexcey0, cexaey1, cexaey0, ac3, ac[2], ac[1], ac[0]); + ac[3] = ac3; + + Two_Product(bex, dey, bexdey1, bexdey0); + Two_Product(dex, bey, dexbey1, dexbey0); + Two_Two_Diff(bexdey1, bexdey0, dexbey1, dexbey0, bd3, bd[2], bd[1], bd[0]); + bd[3] = bd3; + + temp8alen = scale_expansion_zeroelim(4, cd, bez, temp8a); + temp8blen = scale_expansion_zeroelim(4, bd, -cez, temp8b); + temp8clen = scale_expansion_zeroelim(4, bc, dez, temp8c); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, + temp8blen, temp8b, temp16); + temp24len = fast_expansion_sum_zeroelim(temp8clen, temp8c, + temp16len, temp16, temp24); + alen = scale_expansion_zeroelim(temp24len, temp24, -aeheight, adet); + + temp8alen = scale_expansion_zeroelim(4, da, cez, temp8a); + temp8blen = scale_expansion_zeroelim(4, ac, dez, temp8b); + temp8clen = scale_expansion_zeroelim(4, cd, aez, temp8c); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, + temp8blen, temp8b, temp16); + temp24len = fast_expansion_sum_zeroelim(temp8clen, temp8c, + temp16len, temp16, temp24); + blen = scale_expansion_zeroelim(temp24len, temp24, beheight, bdet); + + temp8alen = scale_expansion_zeroelim(4, ab, dez, temp8a); + temp8blen = scale_expansion_zeroelim(4, bd, aez, temp8b); + temp8clen = scale_expansion_zeroelim(4, da, bez, temp8c); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, + temp8blen, temp8b, temp16); + temp24len = fast_expansion_sum_zeroelim(temp8clen, temp8c, + temp16len, temp16, temp24); + clen = scale_expansion_zeroelim(temp24len, temp24, -ceheight, cdet); + + temp8alen = scale_expansion_zeroelim(4, bc, aez, temp8a); + temp8blen = scale_expansion_zeroelim(4, ac, -bez, temp8b); + temp8clen = scale_expansion_zeroelim(4, ab, cez, temp8c); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, + temp8blen, temp8b, temp16); + temp24len = fast_expansion_sum_zeroelim(temp8clen, temp8c, + temp16len, temp16, temp24); + dlen = scale_expansion_zeroelim(temp24len, temp24, deheight, ddet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + cdlen = fast_expansion_sum_zeroelim(clen, cdet, dlen, ddet, cddet); + finlength = fast_expansion_sum_zeroelim(ablen, abdet, cdlen, cddet, fin1); + + det = estimate(finlength, fin1); + errbound = isperrboundB * permanent; + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + Two_Diff_Tail(pa[0], pe[0], aex, aextail); + Two_Diff_Tail(pa[1], pe[1], aey, aeytail); + Two_Diff_Tail(pa[2], pe[2], aez, aeztail); + Two_Diff_Tail(aheight, eheight, aeheight, aeheighttail); + Two_Diff_Tail(pb[0], pe[0], bex, bextail); + Two_Diff_Tail(pb[1], pe[1], bey, beytail); + Two_Diff_Tail(pb[2], pe[2], bez, beztail); + Two_Diff_Tail(bheight, eheight, beheight, beheighttail); + Two_Diff_Tail(pc[0], pe[0], cex, cextail); + Two_Diff_Tail(pc[1], pe[1], cey, ceytail); + Two_Diff_Tail(pc[2], pe[2], cez, ceztail); + Two_Diff_Tail(cheight, eheight, ceheight, ceheighttail); + Two_Diff_Tail(pd[0], pe[0], dex, dextail); + Two_Diff_Tail(pd[1], pe[1], dey, deytail); + Two_Diff_Tail(pd[2], pe[2], dez, deztail); + Two_Diff_Tail(dheight, eheight, deheight, deheighttail); + if ((aextail == 0.0) && (aeytail == 0.0) && (aeztail == 0.0) + && (bextail == 0.0) && (beytail == 0.0) && (beztail == 0.0) + && (cextail == 0.0) && (ceytail == 0.0) && (ceztail == 0.0) + && (dextail == 0.0) && (deytail == 0.0) && (deztail == 0.0) + && (aeheighttail == 0.0) && (beheighttail == 0.0) + && (ceheighttail == 0.0) && (deheighttail == 0.0)) { + return det; + } + + errbound = isperrboundC * permanent + resulterrbound * Absolute(det); + abeps = (aex * beytail + bey * aextail) + - (aey * bextail + bex * aeytail); + bceps = (bex * ceytail + cey * bextail) + - (bey * cextail + cex * beytail); + cdeps = (cex * deytail + dey * cextail) + - (cey * dextail + dex * ceytail); + daeps = (dex * aeytail + aey * dextail) + - (dey * aextail + aex * deytail); + aceps = (aex * ceytail + cey * aextail) + - (aey * cextail + cex * aeytail); + bdeps = (bex * deytail + dey * bextail) + - (bey * dextail + dex * beytail); + det += ((beheight + * ((cez * daeps + dez * aceps + aez * cdeps) + + (ceztail * da3 + deztail * ac3 + aeztail * cd3)) + + deheight + * ((aez * bceps - bez * aceps + cez * abeps) + + (aeztail * bc3 - beztail * ac3 + ceztail * ab3))) + - (aeheight + * ((bez * cdeps - cez * bdeps + dez * bceps) + + (beztail * cd3 - ceztail * bd3 + deztail * bc3)) + + ceheight + * ((dez * abeps + aez * bdeps + bez * daeps) + + (deztail * ab3 + aeztail * bd3 + beztail * da3)))) + + ((beheighttail * (cez * da3 + dez * ac3 + aez * cd3) + + deheighttail * (aez * bc3 - bez * ac3 + cez * ab3)) + - (aeheighttail * (bez * cd3 - cez * bd3 + dez * bc3) + + ceheighttail * (dez * ab3 + aez * bd3 + bez * da3))); + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + return orient4dexact(pa, pb, pc, pd, pe, + aheight, bheight, cheight, dheight, eheight); +} + +REAL orient4d(REAL* pa, REAL* pb, REAL* pc, REAL* pd, REAL* pe, + REAL aheight, REAL bheight, REAL cheight, REAL dheight, + REAL eheight) +{ + REAL aex, bex, cex, dex; + REAL aey, bey, cey, dey; + REAL aez, bez, cez, dez; + REAL aexbey, bexaey, bexcey, cexbey, cexdey, dexcey, dexaey, aexdey; + REAL aexcey, cexaey, bexdey, dexbey; + REAL aeheight, beheight, ceheight, deheight; + REAL ab, bc, cd, da, ac, bd; + REAL abc, bcd, cda, dab; + REAL aezplus, bezplus, cezplus, dezplus; + REAL aexbeyplus, bexaeyplus, bexceyplus, cexbeyplus; + REAL cexdeyplus, dexceyplus, dexaeyplus, aexdeyplus; + REAL aexceyplus, cexaeyplus, bexdeyplus, dexbeyplus; + REAL det; + REAL permanent, errbound; + + + aex = pa[0] - pe[0]; + bex = pb[0] - pe[0]; + cex = pc[0] - pe[0]; + dex = pd[0] - pe[0]; + aey = pa[1] - pe[1]; + bey = pb[1] - pe[1]; + cey = pc[1] - pe[1]; + dey = pd[1] - pe[1]; + aez = pa[2] - pe[2]; + bez = pb[2] - pe[2]; + cez = pc[2] - pe[2]; + dez = pd[2] - pe[2]; + aeheight = aheight - eheight; + beheight = bheight - eheight; + ceheight = cheight - eheight; + deheight = dheight - eheight; + + aexbey = aex * bey; + bexaey = bex * aey; + ab = aexbey - bexaey; + bexcey = bex * cey; + cexbey = cex * bey; + bc = bexcey - cexbey; + cexdey = cex * dey; + dexcey = dex * cey; + cd = cexdey - dexcey; + dexaey = dex * aey; + aexdey = aex * dey; + da = dexaey - aexdey; + + aexcey = aex * cey; + cexaey = cex * aey; + ac = aexcey - cexaey; + bexdey = bex * dey; + dexbey = dex * bey; + bd = bexdey - dexbey; + + abc = aez * bc - bez * ac + cez * ab; + bcd = bez * cd - cez * bd + dez * bc; + cda = cez * da + dez * ac + aez * cd; + dab = dez * ab + aez * bd + bez * da; + + det = (deheight * abc - ceheight * dab) + (beheight * cda - aeheight * bcd); + + aezplus = Absolute(aez); + bezplus = Absolute(bez); + cezplus = Absolute(cez); + dezplus = Absolute(dez); + aexbeyplus = Absolute(aexbey); + bexaeyplus = Absolute(bexaey); + bexceyplus = Absolute(bexcey); + cexbeyplus = Absolute(cexbey); + cexdeyplus = Absolute(cexdey); + dexceyplus = Absolute(dexcey); + dexaeyplus = Absolute(dexaey); + aexdeyplus = Absolute(aexdey); + aexceyplus = Absolute(aexcey); + cexaeyplus = Absolute(cexaey); + bexdeyplus = Absolute(bexdey); + dexbeyplus = Absolute(dexbey); + permanent = ((cexdeyplus + dexceyplus) * bezplus + + (dexbeyplus + bexdeyplus) * cezplus + + (bexceyplus + cexbeyplus) * dezplus) + * Absolute(aeheight) + + ((dexaeyplus + aexdeyplus) * cezplus + + (aexceyplus + cexaeyplus) * dezplus + + (cexdeyplus + dexceyplus) * aezplus) + * Absolute(beheight) + + ((aexbeyplus + bexaeyplus) * dezplus + + (bexdeyplus + dexbeyplus) * aezplus + + (dexaeyplus + aexdeyplus) * bezplus) + * Absolute(ceheight) + + ((bexceyplus + cexbeyplus) * aezplus + + (cexaeyplus + aexceyplus) * bezplus + + (aexbeyplus + bexaeyplus) * cezplus) + * Absolute(deheight); + errbound = isperrboundA * permanent; + if ((det > errbound) || (-det > errbound)) { + return det; + } + + return orient4dadapt(pa, pb, pc, pd, pe, + aheight, bheight, cheight, dheight, eheight, permanent); +} + + + + + + +//============================================================================== + + diff --git a/SKIRT/tetgen/tetgen.cxx b/SKIRT/tetgen/tetgen.cxx new file mode 100644 index 00000000..899a47b6 --- /dev/null +++ b/SKIRT/tetgen/tetgen.cxx @@ -0,0 +1,36567 @@ +//============================================================================// +// // +// TetGen // +// // +// A Quality Tetrahedral Mesh Generator and A 3D Delaunay Triangulator // +// // +// Version 1.6.0 // +// August 31, 2020 // +// // +// Copyright (C) 2002--2020 // +// // +// Hang Si // +// Research Group: Numerical Mathematics and Scientific Computing // +// Weierstrass Institute for Applied Analysis and Stochastics (WIAS) // +// Mohrenstr. 39, 10117 Berlin, Germany // +// si@wias-berlin.de // +// // +// TetGen is a tetrahedral mesh generator. It creates 3d triangulations of // +// polyhedral domains. It generates meshes with well-shaped elements whose // +// sizes are adapted to the geometric features or user-provided sizing // +// functions. It has applications in various applications in scientific // +// computing, such as computer graphics (CG), computer-aided design (CAD), // +// geometry processing (parametrizations and computer animation), and // +// physical simulations (finite element analysis). // +// // +// TetGen computes (weighted) Delaunay triangulations for three-dimensional // +// (weighted) point sets, and constrained Delaunay triangulations for // +// three-dimensional polyhedral domains. In the latter case, input edges // +// and triangles can be completely preserved in the output meshes. TetGen // +// can refine or coarsen an existing mesh to result in good quality and // +// size-adapted mesh according to the geometric features and user-defined // +// mesh sizing functions. // +// // +// TetGen implements theoretically proven algorithms for computing the // +// Delaunay and constrained Delaunay tetrahedralizations. TetGen achieves // +// robustness and efficiency by using advanced techniques in computational // +// geometry. A technical paper describes the algorithms and methods // +// implemented in TetGen is available in ACM-TOMS, Hang Si ``TetGen, a // +// Delaunay-Based Quality Tetrahedral Mesh Generator", ACM Transactions on // +// Mathematical Software, February 2015, https://doi.org/10.1145/2629697. // +// // +// TetGen is freely available through the website: http://www.tetgen.org. // +// It may be copied, modified, and redistributed for non-commercial use. // +// Please consult the file LICENSE for the detailed copyright notices. // +// // +//============================================================================// + +#include "tetgen.h" + +//== io_cxx ==================================================================// +// // +// // + +//============================================================================// +// // +// load_node_call() Read a list of points from a file. // +// // +// 'infile' is the file handle contains the node list. It may point to a // +// .node, or .poly or .smesh file. 'markers' indicates each node contains an // +// additional marker (integer) or not. 'uvflag' indicates each node contains // +// u,v coordinates or not. It is reuqired by a PSC. 'infilename' is the name // +// of the file being read, it is only used in error messages. // +// // +// The 'firstnumber' (0 or 1) is automatically determined by the number of // +// the first index of the first point. // +// // +//============================================================================// + +bool tetgenio::load_node_call(FILE* infile, int markers, int uvflag, + char* infilename) +{ + char inputline[INPUTLINESIZE]; + char *stringptr; + REAL x, y, z, attrib; + int firstnode, currentmarker; + int index, attribindex; + int i, j; + + // Initialize 'pointlist', 'pointattributelist', and 'pointmarkerlist'. + pointlist = new REAL[numberofpoints * 3]; + if (pointlist == (REAL *) NULL) { + terminatetetgen(NULL, 1); + } + if (numberofpointattributes > 0) { + pointattributelist = new REAL[numberofpoints * numberofpointattributes]; + if (pointattributelist == (REAL *) NULL) { + terminatetetgen(NULL, 1); + } + } + if (markers) { + pointmarkerlist = new int[numberofpoints]; + if (pointmarkerlist == (int *) NULL) { + terminatetetgen(NULL, 1); + } + } + if (uvflag) { + pointparamlist = new pointparam[numberofpoints]; + if (pointparamlist == NULL) { + terminatetetgen(NULL, 1); + } + } + + // Read the point section. + index = 0; + attribindex = 0; + for (i = 0; i < numberofpoints; i++) { + stringptr = readnumberline(inputline, infile, infilename); + if (useindex) { + if (i == 0) { + firstnode = (int) strtol (stringptr, &stringptr, 0); + if ((firstnode == 0) || (firstnode == 1)) { + firstnumber = firstnode; + } + } + stringptr = findnextnumber(stringptr); + } // if (useindex) + if (*stringptr == '\0') { + printf("Error: Point %d has no x coordinate.\n", firstnumber + i); + break; + } + x = (REAL) strtod(stringptr, &stringptr); + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + printf("Error: Point %d has no y coordinate.\n", firstnumber + i); + break; + } + y = (REAL) strtod(stringptr, &stringptr); + if (mesh_dim == 3) { + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + printf("Error: Point %d has no z coordinate.\n", firstnumber + i); + break; + } + z = (REAL) strtod(stringptr, &stringptr); + } else { + z = 0.0; // mesh_dim == 2; + } + pointlist[index++] = x; + pointlist[index++] = y; + pointlist[index++] = z; + // Read the point attributes. + for (j = 0; j < numberofpointattributes; j++) { + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + attrib = 0.0; + } else { + attrib = (REAL) strtod(stringptr, &stringptr); + } + pointattributelist[attribindex++] = attrib; + } + if (markers) { + // Read a point marker. + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + currentmarker = 0; + } else { + currentmarker = (int) strtol (stringptr, &stringptr, 0); + } + pointmarkerlist[i] = currentmarker; + } + } + if (i < numberofpoints) { + // Failed to read points due to some error. + delete [] pointlist; + pointlist = (REAL *) NULL; + if (markers) { + delete [] pointmarkerlist; + pointmarkerlist = (int *) NULL; + } + if (numberofpointattributes > 0) { + delete [] pointattributelist; + pointattributelist = (REAL *) NULL; + } + if (uvflag) { + delete [] pointparamlist; + pointparamlist = NULL; + } + numberofpoints = 0; + return false; + } + return true; +} + +//============================================================================// +// // +// load_node() Load a list of points from a .node file. // +// // +//============================================================================// + +bool tetgenio::load_node(char* filebasename) +{ + FILE *infile; + char innodefilename[FILENAMESIZE]; + char inputline[INPUTLINESIZE]; + char *stringptr; + bool okflag; + int markers; + int uvflag; // for psc input. + + // Assembling the actual file names we want to open. + strcpy(innodefilename, filebasename); + strcat(innodefilename, ".node"); + + // Try to open a .node file. + infile = fopen(innodefilename, "r"); + if (infile == (FILE *) NULL) { + printf(" Cannot access file %s.\n", innodefilename); + return false; + } + printf("Opening %s.\n", innodefilename); + + // Set initial flags. + mesh_dim = 3; + numberofpointattributes = 0; // no point attribute. + markers = 0; // no boundary marker. + uvflag = 0; // no uv parameters (required by a PSC). + + // Read the first line of the file. + stringptr = readnumberline(inputline, infile, innodefilename); + // Does this file contain an index column? + stringptr = strstr(inputline, "rbox"); + if (stringptr == NULL) { + // Read number of points, number of dimensions, number of point + // attributes, and number of boundary markers. + stringptr = inputline; + numberofpoints = (int) strtol (stringptr, &stringptr, 0); + stringptr = findnextnumber(stringptr); + if (*stringptr != '\0') { + mesh_dim = (int) strtol (stringptr, &stringptr, 0); + } + stringptr = findnextnumber(stringptr); + if (*stringptr != '\0') { + numberofpointattributes = (int) strtol (stringptr, &stringptr, 0); + } + stringptr = findnextnumber(stringptr); + if (*stringptr != '\0') { + markers = (int) strtol (stringptr, &stringptr, 0); + } + stringptr = findnextnumber(stringptr); + if (*stringptr != '\0') { + uvflag = (int) strtol (stringptr, &stringptr, 0); + } + } else { + // It is a rbox (qhull) input file. + stringptr = inputline; + // Get the dimension. + mesh_dim = (int) strtol (stringptr, &stringptr, 0); + // Get the number of points. + stringptr = readnumberline(inputline, infile, innodefilename); + numberofpoints = (int) strtol (stringptr, &stringptr, 0); + // There is no index column. + useindex = 0; + } + + // Load the list of nodes. + okflag = load_node_call(infile, markers, uvflag, innodefilename); + + fclose(infile); + return okflag; +} + +//============================================================================// +// // +// load_edge() Load a list of edges from a .edge file. // +// // +//============================================================================// + +bool tetgenio::load_edge(char* filebasename) +{ + FILE *infile; + char inedgefilename[FILENAMESIZE]; + char inputline[INPUTLINESIZE]; + char *stringptr; + int markers, corner; + int index; + int i, j; + + strcpy(inedgefilename, filebasename); + strcat(inedgefilename, ".edge"); + + infile = fopen(inedgefilename, "r"); + if (infile != (FILE *) NULL) { + printf("Opening %s.\n", inedgefilename); + } else { + //printf(" Cannot access file %s.\n", inedgefilename); + return false; + } + + // Read number of boundary edges. + stringptr = readnumberline(inputline, infile, inedgefilename); + numberofedges = (int) strtol (stringptr, &stringptr, 0); + if (numberofedges > 0) { + edgelist = new int[numberofedges * 2]; + if (edgelist == (int *) NULL) { + terminatetetgen(NULL, 1); + } + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + markers = 0; // Default value. + } else { + markers = (int) strtol (stringptr, &stringptr, 0); + } + if (markers > 0) { + edgemarkerlist = new int[numberofedges]; + } + } + + // Read the list of edges. + index = 0; + for (i = 0; i < numberofedges; i++) { + // Read edge index and the edge's two endpoints. + stringptr = readnumberline(inputline, infile, inedgefilename); + for (j = 0; j < 2; j++) { + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + printf("Error: Edge %d is missing vertex %d in %s.\n", + i + firstnumber, j + 1, inedgefilename); + terminatetetgen(NULL, 1); + } + corner = (int) strtol(stringptr, &stringptr, 0); + if (corner < firstnumber || corner >= numberofpoints + firstnumber) { + printf("Error: Edge %d has an invalid vertex index.\n", + i + firstnumber); + terminatetetgen(NULL, 1); + } + edgelist[index++] = corner; + } + if (numberofcorners == 10) { + // Skip an extra vertex (generated by a previous -o2 option). + stringptr = findnextnumber(stringptr); + } + // Read the edge marker if it has. + if (markers) { + stringptr = findnextnumber(stringptr); + edgemarkerlist[i] = (int) strtol(stringptr, &stringptr, 0); + } + } + + fclose(infile); + return true; +} + +//============================================================================// +// // +// load_face() Load a list of faces (triangles) from a .face file. // +// // +//============================================================================// + +bool tetgenio::load_face(char* filebasename) +{ + FILE *infile; + char infilename[FILENAMESIZE]; + char inputline[INPUTLINESIZE]; + char *stringptr; + REAL attrib; + int markers, corner; + int index; + int i, j; + + strcpy(infilename, filebasename); + strcat(infilename, ".face"); + + infile = fopen(infilename, "r"); + if (infile != (FILE *) NULL) { + printf("Opening %s.\n", infilename); + } else { + return false; + } + + // Read number of faces, boundary markers. + stringptr = readnumberline(inputline, infile, infilename); + numberoftrifaces = (int) strtol (stringptr, &stringptr, 0); + stringptr = findnextnumber(stringptr); + if (mesh_dim == 2) { + // Skip a number. + stringptr = findnextnumber(stringptr); + } + if (*stringptr == '\0') { + markers = 0; // Default there is no marker per face. + } else { + markers = (int) strtol (stringptr, &stringptr, 0); + } + if (numberoftrifaces > 0) { + trifacelist = new int[numberoftrifaces * 3]; + if (trifacelist == (int *) NULL) { + terminatetetgen(NULL, 1); + } + if (markers) { + trifacemarkerlist = new int[numberoftrifaces]; + if (trifacemarkerlist == (int *) NULL) { + terminatetetgen(NULL, 1); + } + } + } + + // Read the list of faces. + index = 0; + for (i = 0; i < numberoftrifaces; i++) { + // Read face index and the face's three corners. + stringptr = readnumberline(inputline, infile, infilename); + for (j = 0; j < 3; j++) { + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + printf("Error: Face %d is missing vertex %d in %s.\n", + i + firstnumber, j + 1, infilename); + terminatetetgen(NULL, 1); + } + corner = (int) strtol(stringptr, &stringptr, 0); + if (corner < firstnumber || corner >= numberofpoints + firstnumber) { + printf("Error: Face %d has an invalid vertex index.\n", + i + firstnumber); + terminatetetgen(NULL, 1); + } + trifacelist[index++] = corner; + } + if (numberofcorners == 10) { + // Skip 3 extra vertices (generated by a previous -o2 option). + for (j = 0; j < 3; j++) { + stringptr = findnextnumber(stringptr); + } + } + // Read the boundary marker if it exists. + if (markers) { + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + attrib = 0.0; + } else { + attrib = (REAL) strtod(stringptr, &stringptr); + } + trifacemarkerlist[i] = (int) attrib; + } + } + + fclose(infile); + + return true; +} + +//============================================================================// +// // +// load_tet() Load a list of tetrahedra from a .ele file. // +// // +//============================================================================// + +bool tetgenio::load_tet(char* filebasename) +{ + FILE *infile; + char infilename[FILENAMESIZE]; + char inputline[INPUTLINESIZE]; + char *stringptr; + REAL attrib; + int corner; + int index, attribindex; + int i, j; + + strcpy(infilename, filebasename); + strcat(infilename, ".ele"); + + infile = fopen(infilename, "r"); + if (infile != (FILE *) NULL) { + printf("Opening %s.\n", infilename); + } else { + return false; + } + + // Read number of elements, number of corners (4 or 10), number of + // element attributes. + stringptr = readnumberline(inputline, infile, infilename); + numberoftetrahedra = (int) strtol (stringptr, &stringptr, 0); + if (numberoftetrahedra <= 0) { + printf("Error: Invalid number of tetrahedra.\n"); + fclose(infile); + return false; + } + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + numberofcorners = 4; // Default read 4 nodes per element. + } else { + numberofcorners = (int) strtol(stringptr, &stringptr, 0); + } + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + numberoftetrahedronattributes = 0; // Default no attribute. + } else { + numberoftetrahedronattributes = (int) strtol(stringptr, &stringptr, 0); + } + if (numberofcorners != 4 && numberofcorners != 10) { + printf("Error: Wrong number of corners %d (should be 4 or 10).\n", + numberofcorners); + fclose(infile); + return false; + } + + // Allocate memory for tetrahedra. + tetrahedronlist = new int[numberoftetrahedra * numberofcorners]; + if (tetrahedronlist == (int *) NULL) { + terminatetetgen(NULL, 1); + } + // Allocate memory for output tetrahedron attributes if necessary. + if (numberoftetrahedronattributes > 0) { + tetrahedronattributelist = new REAL[numberoftetrahedra * + numberoftetrahedronattributes]; + if (tetrahedronattributelist == (REAL *) NULL) { + terminatetetgen(NULL, 1); + } + } + + // Read the list of tetrahedra. + index = 0; + attribindex = 0; + for (i = 0; i < numberoftetrahedra; i++) { + // Read tetrahedron index and the tetrahedron's corners. + stringptr = readnumberline(inputline, infile, infilename); + for (j = 0; j < numberofcorners; j++) { + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + printf("Error: Tetrahedron %d is missing vertex %d in %s.\n", + i + firstnumber, j + 1, infilename); + terminatetetgen(NULL, 1); + } + corner = (int) strtol(stringptr, &stringptr, 0); + if (corner < firstnumber || corner >= numberofpoints + firstnumber) { + printf("Error: Tetrahedron %d has an invalid vertex index.\n", + i + firstnumber); + terminatetetgen(NULL, 1); + } + tetrahedronlist[index++] = corner; + } + // Read the tetrahedron's attributes. + for (j = 0; j < numberoftetrahedronattributes; j++) { + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + attrib = 0.0; + } else { + attrib = (REAL) strtod(stringptr, &stringptr); + } + tetrahedronattributelist[attribindex++] = attrib; + } + } + + fclose(infile); + + return true; +} + +//============================================================================// +// // +// load_vol() Load a list of volume constraints from a .vol file. // +// // +//============================================================================// + +bool tetgenio::load_vol(char* filebasename) +{ + FILE *infile; + char inelefilename[FILENAMESIZE]; + char infilename[FILENAMESIZE]; + char inputline[INPUTLINESIZE]; + char *stringptr; + REAL volume; + int volelements; + int i; + + strcpy(infilename, filebasename); + strcat(infilename, ".vol"); + + infile = fopen(infilename, "r"); + if (infile != (FILE *) NULL) { + printf("Opening %s.\n", infilename); + } else { + return false; + } + + // Read number of tetrahedra. + stringptr = readnumberline(inputline, infile, infilename); + volelements = (int) strtol (stringptr, &stringptr, 0); + if (volelements != numberoftetrahedra) { + strcpy(inelefilename, filebasename); + strcat(infilename, ".ele"); + printf("Warning: %s and %s disagree on number of tetrahedra.\n", + inelefilename, infilename); + fclose(infile); + return false; + } + + tetrahedronvolumelist = new REAL[volelements]; + if (tetrahedronvolumelist == (REAL *) NULL) { + terminatetetgen(NULL, 1); + } + + // Read the list of volume constraints. + for (i = 0; i < volelements; i++) { + stringptr = readnumberline(inputline, infile, infilename); + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + volume = -1.0; // No constraint on this tetrahedron. + } else { + volume = (REAL) strtod(stringptr, &stringptr); + } + tetrahedronvolumelist[i] = volume; + } + + fclose(infile); + + return true; +} + +//============================================================================// +// // +// load_var() Load constraints applied on facets, segments, and nodes // +// from a .var file. // +// // +//============================================================================// + +bool tetgenio::load_var(char* filebasename) +{ + FILE *infile; + char varfilename[FILENAMESIZE]; + char inputline[INPUTLINESIZE]; + char *stringptr; + int index; + int i; + + // Variant constraints are saved in file "filename.var". + strcpy(varfilename, filebasename); + strcat(varfilename, ".var"); + infile = fopen(varfilename, "r"); + if (infile != (FILE *) NULL) { + printf("Opening %s.\n", varfilename); + } else { + return false; + } + + // Read the facet constraint section. + stringptr = readnumberline(inputline, infile, varfilename); + if (stringptr == NULL) { + // No region list, return. + fclose(infile); + return true; + } + if (*stringptr != '\0') { + numberoffacetconstraints = (int) strtol (stringptr, &stringptr, 0); + } else { + numberoffacetconstraints = 0; + } + if (numberoffacetconstraints > 0) { + // Initialize 'facetconstraintlist'. + facetconstraintlist = new REAL[numberoffacetconstraints * 2]; + index = 0; + for (i = 0; i < numberoffacetconstraints; i++) { + stringptr = readnumberline(inputline, infile, varfilename); + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + printf("Error: facet constraint %d has no facet marker.\n", + firstnumber + i); + break; + } else { + facetconstraintlist[index++] = (REAL) strtod(stringptr, &stringptr); + } + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + printf("Error: facet constraint %d has no maximum area bound.\n", + firstnumber + i); + break; + } else { + facetconstraintlist[index++] = (REAL) strtod(stringptr, &stringptr); + } + } + if (i < numberoffacetconstraints) { + // This must be caused by an error. + fclose(infile); + return false; + } + } + + // Read the segment constraint section. + stringptr = readnumberline(inputline, infile, varfilename); + if (stringptr == NULL) { + // No segment list, return. + fclose(infile); + return true; + } + if (*stringptr != '\0') { + numberofsegmentconstraints = (int) strtol (stringptr, &stringptr, 0); + } else { + numberofsegmentconstraints = 0; + } + if (numberofsegmentconstraints > 0) { + // Initialize 'segmentconstraintlist'. + segmentconstraintlist = new REAL[numberofsegmentconstraints * 3]; + index = 0; + for (i = 0; i < numberofsegmentconstraints; i++) { + stringptr = readnumberline(inputline, infile, varfilename); + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + printf("Error: segment constraint %d has no frist endpoint.\n", + firstnumber + i); + break; + } else { + segmentconstraintlist[index++] = (REAL) strtod(stringptr, &stringptr); + } + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + printf("Error: segment constraint %d has no second endpoint.\n", + firstnumber + i); + break; + } else { + segmentconstraintlist[index++] = (REAL) strtod(stringptr, &stringptr); + } + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + printf("Error: segment constraint %d has no maximum length bound.\n", + firstnumber + i); + break; + } else { + segmentconstraintlist[index++] = (REAL) strtod(stringptr, &stringptr); + } + } + if (i < numberofsegmentconstraints) { + // This must be caused by an error. + fclose(infile); + return false; + } + } + + fclose(infile); + return true; +} + +//============================================================================// +// // +// load_mtr() Load a size specification map from a .mtr file. // +// // +//============================================================================// + +bool tetgenio::load_mtr(char* filebasename) +{ + FILE *infile; + char mtrfilename[FILENAMESIZE]; + char inputline[INPUTLINESIZE]; + char *stringptr; + REAL mtr; + int ptnum; + int mtrindex; + int i, j; + + strcpy(mtrfilename, filebasename); + strcat(mtrfilename, ".mtr"); + infile = fopen(mtrfilename, "r"); + if (infile != (FILE *) NULL) { + printf("Opening %s.\n", mtrfilename); + } else { + return false; + } + + // Read the number of points. + stringptr = readnumberline(inputline, infile, mtrfilename); + ptnum = (int) strtol (stringptr, &stringptr, 0); + if (ptnum != numberofpoints) { + printf(" !! Point numbers are not equal. Ignored.\n"); + fclose(infile); + return false; + } + // Read the number of columns (1, 3, or 6). + stringptr = findnextnumber(stringptr); // Skip number of points. + if (*stringptr != '\0') { + numberofpointmtrs = (int) strtol (stringptr, &stringptr, 0); + } + if ((numberofpointmtrs != 1) && (numberofpointmtrs != 3) && + (numberofpointmtrs != 6)) { + // Column number doesn't match. + numberofpointmtrs = 0; + printf(" !! Metric size does not match (1, 3, or 6). Ignored.\n"); + fclose(infile); + return false; + } + + // Allocate space for pointmtrlist. + pointmtrlist = new REAL[numberofpoints * numberofpointmtrs]; + if (pointmtrlist == (REAL *) NULL) { + terminatetetgen(NULL, 1); + } + mtrindex = 0; + for (i = 0; i < numberofpoints; i++) { + // Read metrics. + stringptr = readnumberline(inputline, infile, mtrfilename); + for (j = 0; j < numberofpointmtrs; j++) { + if (*stringptr == '\0') { + printf("Error: Metric %d is missing value #%d in %s.\n", + i + firstnumber, j + 1, mtrfilename); + terminatetetgen(NULL, 1); + } + mtr = (REAL) strtod(stringptr, &stringptr); + pointmtrlist[mtrindex++] = mtr; + stringptr = findnextnumber(stringptr); + } + } + + fclose(infile); + return true; +} + +//============================================================================// +// // +// load_elem() Load a list of refine elements from an .elem file. // +// // +//============================================================================// + +bool tetgenio::load_elem(char* filebasename) +{ + FILE *infile; + char inelemfilename[FILENAMESIZE]; + char line[1024]; + + strcpy(inelemfilename, filebasename); + strcat(inelemfilename, ".elem"); + + // Try to open a .elem file. + infile = fopen(inelemfilename, "r"); + if (infile != (FILE *) NULL) { + printf("Opening %s.\n", inelemfilename); + } else { + return false; + } + + int elenum = 0; + float growth_ratio = 0.; + fgets(line, 1023, infile); + sscanf(line, "%d %f", &elenum, &growth_ratio); + + if (elenum == 0) { + fclose(infile); + return false; + } + + refine_elem_list = new int[elenum * 4]; + numberofrefineelems = elenum; + + int *idx, i; + for (i = 0; i < elenum; i++) { + fgets(line, 1023, infile); + idx = &(refine_elem_list[i*4]); + sscanf(line, "%d %d %d %d", &(idx[0]), &(idx[1]), &(idx[2]), &(idx[3])); + } + + fclose(infile); + return true; +} + +//============================================================================// +// // +// load_poly() Load a PL complex from a .poly or a .smesh file. // +// // +//============================================================================// + +bool tetgenio::load_poly(char* filebasename) +{ + FILE *infile; + char inpolyfilename[FILENAMESIZE]; + char insmeshfilename[FILENAMESIZE]; + char inputline[INPUTLINESIZE]; + char *stringptr, *infilename; + int smesh, markers, uvflag, currentmarker; + int index; + int i, j, k; + + // Assembling the actual file names we want to open. + strcpy(inpolyfilename, filebasename); + strcpy(insmeshfilename, filebasename); + strcat(inpolyfilename, ".poly"); + strcat(insmeshfilename, ".smesh"); + + // First assume it is a .poly file. + smesh = 0; + // Try to open a .poly file. + infile = fopen(inpolyfilename, "r"); + if (infile == (FILE *) NULL) { + // .poly doesn't exist! Try to open a .smesh file. + infile = fopen(insmeshfilename, "r"); + if (infile == (FILE *) NULL) { + printf(" Cannot access file %s and %s.\n", + inpolyfilename, insmeshfilename); + return false; + } else { + printf("Opening %s.\n", insmeshfilename); + infilename = insmeshfilename; + } + smesh = 1; + } else { + printf("Opening %s.\n", inpolyfilename); + infilename = inpolyfilename; + } + + // Initialize the default values. + mesh_dim = 3; // Three-dimensional coordinates. + numberofpointattributes = 0; // no point attribute. + markers = 0; // no boundary marker. + uvflag = 0; // no uv parameters (required by a PSC). + + // Read number of points, number of dimensions, number of point + // attributes, and number of boundary markers. + stringptr = readnumberline(inputline, infile, infilename); + numberofpoints = (int) strtol (stringptr, &stringptr, 0); + stringptr = findnextnumber(stringptr); + if (*stringptr != '\0') { + mesh_dim = (int) strtol (stringptr, &stringptr, 0); + } + stringptr = findnextnumber(stringptr); + if (*stringptr != '\0') { + numberofpointattributes = (int) strtol (stringptr, &stringptr, 0); + } + stringptr = findnextnumber(stringptr); + if (*stringptr != '\0') { + markers = (int) strtol (stringptr, &stringptr, 0); + } + if (*stringptr != '\0') { + uvflag = (int) strtol (stringptr, &stringptr, 0); + } + + if (numberofpoints > 0) { + // Load the list of nodes. + if (!load_node_call(infile, markers, uvflag, infilename)) { + fclose(infile); + return false; + } + } else { + // If the .poly or .smesh file claims there are zero points, that + // means the points should be read from a separate .node file. + if (!load_node(filebasename)) { + fclose(infile); + return false; + } + } + + if ((mesh_dim != 3) && (mesh_dim != 2)) { + printf("Input error: TetGen only works for 2D & 3D point sets.\n"); + fclose(infile); + return false; + } + if (numberofpoints < (mesh_dim + 1)) { + printf("Input error: TetGen needs at least %d points.\n", mesh_dim + 1); + fclose(infile); + return false; + } + + facet *f; + polygon *p; + + if (mesh_dim == 3) { + + // Read number of facets and number of boundary markers. + stringptr = readnumberline(inputline, infile, infilename); + if (stringptr == NULL) { + // No facet list, return. + fclose(infile); + return true; + } + numberoffacets = (int) strtol (stringptr, &stringptr, 0); + if (numberoffacets <= 0) { + // No facet list, return. + fclose(infile); + return true; + } + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + markers = 0; // no boundary marker. + } else { + markers = (int) strtol (stringptr, &stringptr, 0); + } + + // Initialize the 'facetlist', 'facetmarkerlist'. + facetlist = new facet[numberoffacets]; + if (markers == 1) { + facetmarkerlist = new int[numberoffacets]; + } + + // Read data into 'facetlist', 'facetmarkerlist'. + if (smesh == 0) { + // Facets are in .poly file format. + for (i = 1; i <= numberoffacets; i++) { + f = &(facetlist[i - 1]); + init(f); + f->numberofholes = 0; + currentmarker = 0; + // Read number of polygons, number of holes, and a boundary marker. + stringptr = readnumberline(inputline, infile, infilename); + f->numberofpolygons = (int) strtol (stringptr, &stringptr, 0); + stringptr = findnextnumber(stringptr); + if (*stringptr != '\0') { + f->numberofholes = (int) strtol (stringptr, &stringptr, 0); + if (markers == 1) { + stringptr = findnextnumber(stringptr); + if (*stringptr != '\0') { + currentmarker = (int) strtol(stringptr, &stringptr, 0); + } + } + } + // Initialize facetmarker if it needs. + if (markers == 1) { + facetmarkerlist[i - 1] = currentmarker; + } + // Each facet should has at least one polygon. + if (f->numberofpolygons <= 0) { + printf("Error: Wrong number of polygon in %d facet.\n", i); + break; + } + // Initialize the 'f->polygonlist'. + f->polygonlist = new polygon[f->numberofpolygons]; + // Go through all polygons, read in their vertices. + for (j = 1; j <= f->numberofpolygons; j++) { + p = &(f->polygonlist[j - 1]); + init(p); + // Read number of vertices of this polygon. + stringptr = readnumberline(inputline, infile, infilename); + p->numberofvertices = (int) strtol(stringptr, &stringptr, 0); + if (p->numberofvertices < 1) { + printf("Error: Wrong polygon %d in facet %d\n", j, i); + break; + } + // Initialize 'p->vertexlist'. + p->vertexlist = new int[p->numberofvertices]; + // Read all vertices of this polygon. + for (k = 1; k <= p->numberofvertices; k++) { + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + // Try to load another non-empty line and continue to read the + // rest of vertices. + stringptr = readnumberline(inputline, infile, infilename); + if (*stringptr == '\0') { + printf("Error: Missing %d endpoints of polygon %d in facet %d", + p->numberofvertices - k, j, i); + break; + } + } + p->vertexlist[k - 1] = (int) strtol (stringptr, &stringptr, 0); + } + } + if (j <= f->numberofpolygons) { + // This must be caused by an error. However, there're j - 1 + // polygons have been read. Reset the 'f->numberofpolygon'. + if (j == 1) { + // This is the first polygon. + delete [] f->polygonlist; + } + f->numberofpolygons = j - 1; + // No hole will be read even it exists. + f->numberofholes = 0; + break; + } + // If this facet has hole pints defined, read them. + if (f->numberofholes > 0) { + // Initialize 'f->holelist'. + f->holelist = new REAL[f->numberofholes * 3]; + // Read the holes' coordinates. + index = 0; + for (j = 1; j <= f->numberofholes; j++) { + stringptr = readnumberline(inputline, infile, infilename); + for (k = 1; k <= 3; k++) { + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + printf("Error: Hole %d in facet %d has no coordinates", j, i); + break; + } + f->holelist[index++] = (REAL) strtod (stringptr, &stringptr); + } + if (k <= 3) { + // This must be caused by an error. + break; + } + } + if (j <= f->numberofholes) { + // This must be caused by an error. + break; + } + } + } + if (i <= numberoffacets) { + // This must be caused by an error. + numberoffacets = i - 1; + fclose(infile); + return false; + } + } else { // poly == 0 + // Read the facets from a .smesh file. + for (i = 1; i <= numberoffacets; i++) { + f = &(facetlist[i - 1]); + init(f); + // Initialize 'f->facetlist'. In a .smesh file, each facetlist only + // contains exactly one polygon, no hole. + f->numberofpolygons = 1; + f->polygonlist = new polygon[f->numberofpolygons]; + p = &(f->polygonlist[0]); + init(p); + // Read number of vertices of this polygon. + stringptr = readnumberline(inputline, infile, insmeshfilename); + p->numberofvertices = (int) strtol (stringptr, &stringptr, 0); + if (p->numberofvertices < 1) { + printf("Error: Wrong number of vertex in facet %d\n", i); + break; + } + // Initialize 'p->vertexlist'. + p->vertexlist = new int[p->numberofvertices]; + for (k = 1; k <= p->numberofvertices; k++) { + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + // Try to load another non-empty line and continue to read the + // rest of vertices. + stringptr = readnumberline(inputline, infile, infilename); + if (*stringptr == '\0') { + printf("Error: Missing %d endpoints in facet %d", + p->numberofvertices - k, i); + break; + } + } + p->vertexlist[k - 1] = (int) strtol (stringptr, &stringptr, 0); + } + if (k <= p->numberofvertices) { + // This must be caused by an error. + break; + } + // Read facet's boundary marker at last. + if (markers == 1) { + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + currentmarker = 0; + } else { + currentmarker = (int) strtol(stringptr, &stringptr, 0); + } + facetmarkerlist[i - 1] = currentmarker; + } + } + if (i <= numberoffacets) { + // This must be caused by an error. + numberoffacets = i - 1; + fclose(infile); + return false; + } + } + + // Read the hole section. + stringptr = readnumberline(inputline, infile, infilename); + if (stringptr == NULL) { + // No hole list, return. + fclose(infile); + return true; + } + if (*stringptr != '\0') { + numberofholes = (int) strtol (stringptr, &stringptr, 0); + } else { + numberofholes = 0; + } + if (numberofholes > 0) { + // Initialize 'holelist'. + holelist = new REAL[numberofholes * 3]; + for (i = 0; i < 3 * numberofholes; i += 3) { + stringptr = readnumberline(inputline, infile, infilename); + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + printf("Error: Hole %d has no x coord.\n", firstnumber + (i / 3)); + break; + } else { + holelist[i] = (REAL) strtod(stringptr, &stringptr); + } + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + printf("Error: Hole %d has no y coord.\n", firstnumber + (i / 3)); + break; + } else { + holelist[i + 1] = (REAL) strtod(stringptr, &stringptr); + } + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + printf("Error: Hole %d has no z coord.\n", firstnumber + (i / 3)); + break; + } else { + holelist[i + 2] = (REAL) strtod(stringptr, &stringptr); + } + } + if (i < 3 * numberofholes) { + // This must be caused by an error. + fclose(infile); + return false; + } + } + + // Read the region section. The 'region' section is optional, if we + // don't reach the end-of-file, try read it in. + stringptr = readnumberline(inputline, infile, NULL); + if (stringptr != (char *) NULL && *stringptr != '\0') { + numberofregions = (int) strtol (stringptr, &stringptr, 0); + } else { + numberofregions = 0; + } + if (numberofregions > 0) { + // Initialize 'regionlist'. + regionlist = new REAL[numberofregions * 5]; + index = 0; + for (i = 0; i < numberofregions; i++) { + stringptr = readnumberline(inputline, infile, infilename); + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + printf("Error: Region %d has no x coordinate.\n", firstnumber + i); + break; + } else { + regionlist[index++] = (REAL) strtod(stringptr, &stringptr); + } + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + printf("Error: Region %d has no y coordinate.\n", firstnumber + i); + break; + } else { + regionlist[index++] = (REAL) strtod(stringptr, &stringptr); + } + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + printf("Error: Region %d has no z coordinate.\n", firstnumber + i); + break; + } else { + regionlist[index++] = (REAL) strtod(stringptr, &stringptr); + } + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + printf("Error: Region %d has no region attrib.\n", firstnumber + i); + break; + } else { + regionlist[index++] = (REAL) strtod(stringptr, &stringptr); + } + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + regionlist[index] = regionlist[index - 1]; + } else { + regionlist[index] = (REAL) strtod(stringptr, &stringptr); + } + index++; + } + if (i < numberofregions) { + // This must be caused by an error. + fclose(infile); + return false; + } + } + + } + + // End of reading poly/smesh file. + fclose(infile); + return true; +} + +//============================================================================// +// // +// load_off() Load a polyhedron from a .off file. // +// // +// The .off format is one of file formats of the Geomview, an interactive // +// program for viewing and manipulating geometric objects. More information // +// is available form: http://www.geomview.org. // +// // +//============================================================================// + +bool tetgenio::load_off(char* filebasename) +{ + FILE *fp; + tetgenio::facet *f; + tetgenio::polygon *p; + char infilename[FILENAMESIZE]; + char buffer[INPUTLINESIZE]; + char *bufferp; + double *coord; + int nverts = 0, iverts = 0; + int nfaces = 0, ifaces = 0; + int nedges = 0; + int line_count = 0, i; + + // Default, the off file's index is from '0'. We check it by remembering the + // smallest index we found in the file. It should be either 0 or 1. + int smallestidx = 0; + + strncpy(infilename, filebasename, 1024 - 1); + infilename[FILENAMESIZE - 1] = '\0'; + if (infilename[0] == '\0') { + printf("Error: No filename.\n"); + return false; + } + if (strcmp(&infilename[strlen(infilename) - 4], ".off") != 0) { + strcat(infilename, ".off"); + } + + if (!(fp = fopen(infilename, "r"))) { + printf(" Unable to open file %s\n", infilename); + return false; + } + printf("Opening %s.\n", infilename); + + while ((bufferp = readline(buffer, fp, &line_count)) != NULL) { + // Check section + if (nverts == 0) { + // Read header + bufferp = strstr(bufferp, "OFF"); + if (bufferp != NULL) { + // Read mesh counts + bufferp = findnextnumber(bufferp); // Skip field "OFF". + if (*bufferp == '\0') { + // Read a non-empty line. + bufferp = readline(buffer, fp, &line_count); + } + if ((sscanf(bufferp, "%d%d%d", &nverts, &nfaces, &nedges) != 3) + || (nverts == 0)) { + printf("Syntax error reading header on line %d in file %s\n", + line_count, infilename); + fclose(fp); + return false; + } + // Allocate memory for 'tetgenio' + if (nverts > 0) { + numberofpoints = nverts; + pointlist = new REAL[nverts * 3]; + smallestidx = nverts + 1; // A bigger enough number. + } + if (nfaces > 0) { + numberoffacets = nfaces; + facetlist = new tetgenio::facet[nfaces]; + } + } + } else if (iverts < nverts) { + // Read vertex coordinates + coord = &pointlist[iverts * 3]; + for (i = 0; i < 3; i++) { + if (*bufferp == '\0') { + printf("Syntax error reading vertex coords on line %d in file %s\n", + line_count, infilename); + fclose(fp); + return false; + } + coord[i] = (REAL) strtod(bufferp, &bufferp); + bufferp = findnextnumber(bufferp); + } + iverts++; + } else if (ifaces < nfaces) { + // Get next face + f = &facetlist[ifaces]; + init(f); + // In .off format, each facet has one polygon, no hole. + f->numberofpolygons = 1; + f->polygonlist = new tetgenio::polygon[1]; + p = &f->polygonlist[0]; + init(p); + // Read the number of vertices, it should be greater than 0. + p->numberofvertices = (int) strtol(bufferp, &bufferp, 0); + if (p->numberofvertices == 0) { + printf("Syntax error reading polygon on line %d in file %s\n", + line_count, infilename); + fclose(fp); + return false; + } + // Allocate memory for face vertices + p->vertexlist = new int[p->numberofvertices]; + for (i = 0; i < p->numberofvertices; i++) { + bufferp = findnextnumber(bufferp); + if (*bufferp == '\0') { + printf("Syntax error reading polygon on line %d in file %s\n", + line_count, infilename); + fclose(fp); + return false; + } + p->vertexlist[i] = (int) strtol(bufferp, &bufferp, 0); + // Detect the smallest index. + if (p->vertexlist[i] < smallestidx) { + smallestidx = p->vertexlist[i]; + } + } + ifaces++; + } else { + // Should never get here + printf("Found extra text starting at line %d in file %s\n", line_count, + infilename); + break; + } + } + + fclose(fp); + + // Decide the firstnumber of the index. + if (smallestidx == 0) { + firstnumber = 0; + } else if (smallestidx == 1) { + firstnumber = 1; + } else { + printf("A wrong smallest index (%d) was detected in file %s\n", + smallestidx, infilename); + return false; + } + + if (iverts != nverts) { + printf("Expected %d vertices, but read only %d vertices in file %s\n", + nverts, iverts, infilename); + return false; + } + if (ifaces != nfaces) { + printf("Expected %d faces, but read only %d faces in file %s\n", + nfaces, ifaces, infilename); + return false; + } + + return true; +} + +//============================================================================// +// // +// load_ply() Load a polyhedron from a .ply file. // +// // +// This is a simplified version of reading .ply files, which only reads the // +// set of vertices and the set of faces. Other informations (such as color, // +// material, texture, etc) in .ply file are ignored. Complete routines for // +// reading and writing ,ply files are available from: http://www.cc.gatech. // +// edu/projects/large_models/ply.html. Except the header section, ply file // +// format has exactly the same format for listing vertices and polygons as // +// off file format. // +// // +//============================================================================// + +bool tetgenio::load_ply(char* filebasename) +{ + FILE *fp; + tetgenio::facet *f; + tetgenio::polygon *p; + char infilename[FILENAMESIZE]; + char buffer[INPUTLINESIZE]; + char *bufferp, *str; + double *coord; + int endheader = 0, format = 0; + int nverts = 0, iverts = 0; + int nfaces = 0, ifaces = 0; + int line_count = 0, i; + + // Default, the ply file's index is from '0'. We check it by remembering the + // smallest index we found in the file. It should be either 0 or 1. + int smallestidx = 0; + + strncpy(infilename, filebasename, FILENAMESIZE - 1); + infilename[FILENAMESIZE - 1] = '\0'; + if (infilename[0] == '\0') { + printf("Error: No filename.\n"); + return false; + } + if (strcmp(&infilename[strlen(infilename) - 4], ".ply") != 0) { + strcat(infilename, ".ply"); + } + + if (!(fp = fopen(infilename, "r"))) { + printf("Error: Unable to open file %s\n", infilename); + return false; + } + printf("Opening %s.\n", infilename); + + while ((bufferp = readline(buffer, fp, &line_count)) != NULL) { + if (!endheader) { + // Find if it is the keyword "end_header". + str = strstr(bufferp, "end_header"); + // strstr() is case sensitive. + if (!str) str = strstr(bufferp, "End_header"); + if (!str) str = strstr(bufferp, "End_Header"); + if (str) { + // This is the end of the header section. + endheader = 1; + continue; + } + // Parse the number of vertices and the number of faces. + if (nverts == 0 || nfaces == 0) { + // Find if it si the keyword "element". + str = strstr(bufferp, "element"); + if (!str) str = strstr(bufferp, "Element"); + if (str) { + bufferp = findnextfield(str); + if (*bufferp == '\0') { + printf("Syntax error reading element type on line%d in file %s\n", + line_count, infilename); + fclose(fp); + return false; + } + if (nverts == 0) { + // Find if it is the keyword "vertex". + str = strstr(bufferp, "vertex"); + if (!str) str = strstr(bufferp, "Vertex"); + if (str) { + bufferp = findnextnumber(str); + if (*bufferp == '\0') { + printf("Syntax error reading vertex number on line"); + printf(" %d in file %s\n", line_count, infilename); + fclose(fp); + return false; + } + nverts = (int) strtol(bufferp, &bufferp, 0); + // Allocate memory for 'tetgenio' + if (nverts > 0) { + numberofpoints = nverts; + pointlist = new REAL[nverts * 3]; + smallestidx = nverts + 1; // A big enough index. + } + } + } + if (nfaces == 0) { + // Find if it is the keyword "face". + str = strstr(bufferp, "face"); + if (!str) str = strstr(bufferp, "Face"); + if (str) { + bufferp = findnextnumber(str); + if (*bufferp == '\0') { + printf("Syntax error reading face number on line"); + printf(" %d in file %s\n", line_count, infilename); + fclose(fp); + return false; + } + nfaces = (int) strtol(bufferp, &bufferp, 0); + // Allocate memory for 'tetgenio' + if (nfaces > 0) { + numberoffacets = nfaces; + facetlist = new tetgenio::facet[nfaces]; + } + } + } + } // It is not the string "element". + } + if (format == 0) { + // Find the keyword "format". + str = strstr(bufferp, "format"); + if (!str) str = strstr(bufferp, "Format"); + if (str) { + format = 1; + bufferp = findnextfield(str); + // Find if it is the string "ascii". + str = strstr(bufferp, "ascii"); + if (!str) str = strstr(bufferp, "ASCII"); + if (!str) { + printf("This routine only reads ascii format of ply files.\n"); + printf("Hint: You can convert the binary to ascii format by\n"); + printf(" using the provided ply tools:\n"); + printf(" ply2ascii < %s > ascii_%s\n", infilename, infilename); + fclose(fp); + return false; + } + } + } + } else if (iverts < nverts) { + // Read vertex coordinates + coord = &pointlist[iverts * 3]; + for (i = 0; i < 3; i++) { + if (*bufferp == '\0') { + printf("Syntax error reading vertex coords on line %d in file %s\n", + line_count, infilename); + fclose(fp); + return false; + } + coord[i] = (REAL) strtod(bufferp, &bufferp); + bufferp = findnextnumber(bufferp); + } + iverts++; + } else if (ifaces < nfaces) { + // Get next face + f = &facetlist[ifaces]; + init(f); + // In .off format, each facet has one polygon, no hole. + f->numberofpolygons = 1; + f->polygonlist = new tetgenio::polygon[1]; + p = &f->polygonlist[0]; + init(p); + // Read the number of vertices, it should be greater than 0. + p->numberofvertices = (int) strtol(bufferp, &bufferp, 0); + if (p->numberofvertices == 0) { + printf("Syntax error reading polygon on line %d in file %s\n", + line_count, infilename); + fclose(fp); + return false; + } + // Allocate memory for face vertices + p->vertexlist = new int[p->numberofvertices]; + for (i = 0; i < p->numberofvertices; i++) { + bufferp = findnextnumber(bufferp); + if (*bufferp == '\0') { + printf("Syntax error reading polygon on line %d in file %s\n", + line_count, infilename); + fclose(fp); + return false; + } + p->vertexlist[i] = (int) strtol(bufferp, &bufferp, 0); + if (p->vertexlist[i] < smallestidx) { + smallestidx = p->vertexlist[i]; + } + } + ifaces++; + } else { + // Should never get here + printf("Found extra text starting at line %d in file %s\n", line_count, + infilename); + break; + } + } + + fclose(fp); + + // Decide the firstnumber of the index. + if (smallestidx == 0) { + firstnumber = 0; + } else if (smallestidx == 1) { + firstnumber = 1; + } else { + printf("A wrong smallest index (%d) was detected in file %s\n", + smallestidx, infilename); + return false; + } + + if (iverts != nverts) { + printf("Expected %d vertices, but read only %d vertices in file %s\n", + nverts, iverts, infilename); + return false; + } + if (ifaces != nfaces) { + printf("Expected %d faces, but read only %d faces in file %s\n", + nfaces, ifaces, infilename); + return false; + } + + return true; +} + +//============================================================================// +// // +// load_stl() Load a surface mesh from a .stl file. // +// // +// The .stl or stereolithography format is an ASCII or binary file used in // +// manufacturing. It is a list of the triangular surfaces that describe a // +// computer generated solid model. This is the standard input for most rapid // +// prototyping machines. // +// // +// Comment: A .stl file many contain many duplicated points. They will be // +// unified during the Delaunay tetrahedralization process. // +// // +//============================================================================// + +static void SwapBytes(char *array, int size, int n) +{ + char *x = new char[size]; + for(int i = 0; i < n; i++) { + char *a = &array[i * size]; + memcpy(x, a, size); + for(int c = 0; c < size; c++) + a[size - 1 - c] = x[c]; + } + delete [] x; +} + +bool tetgenio::load_stl(char* filebasename) +{ + FILE *fp; + tetgenmesh::arraypool *plist; + tetgenio::facet *f; + tetgenio::polygon *p; + char infilename[FILENAMESIZE]; + char buffer[INPUTLINESIZE]; + char *bufferp, *str; + double *coord; + int solid = 0; + int nverts = 0, iverts = 0; + int nfaces = 0; + int line_count = 0, i; + + strncpy(infilename, filebasename, FILENAMESIZE - 1); + infilename[FILENAMESIZE - 1] = '\0'; + if (infilename[0] == '\0') { + printf("Error: No filename.\n"); + return false; + } + if (strcmp(&infilename[strlen(infilename) - 4], ".stl") != 0) { + strcat(infilename, ".stl"); + } + + if (!(fp = fopen(infilename, "rb"))) { + printf("Error: Unable to open file %s\n", infilename); + return false; + } + printf("Opening %s.\n", infilename); + + // "solid", or binary data header + if(!fgets(buffer, sizeof(buffer), fp)){ fclose(fp); return 0; } + bool binary = strncmp(buffer, "solid", 5) && strncmp(buffer, "SOLID", 5); + + // STL file has no number of points available. Use a list to read points. + plist = new tetgenmesh::arraypool(sizeof(double) * 3, 10); + + if(!binary){ + solid = 1; + while ((bufferp = readline(buffer, fp, &line_count)) != NULL) { + // The ASCII .stl file must start with the lower case keyword solid and + // end with endsolid. + if (solid == 0) { + // Read header + bufferp = strstr(bufferp, "solid"); + if (bufferp != NULL) { + solid = 1; + } + } else { + // We're inside the block of the solid. + str = bufferp; + // Is this the end of the solid. + bufferp = strstr(bufferp, "endsolid"); + if (bufferp != NULL) { + solid = 0; + } else { + // Read the XYZ coordinates if it is a vertex. + bufferp = str; + bufferp = strstr(bufferp, "vertex"); + if (bufferp != NULL) { + plist->newindex((void **) &coord); + for (i = 0; i < 3; i++) { + bufferp = findnextnumber(bufferp); + if (*bufferp == '\0') { + printf("Syntax error reading vertex coords on line %d\n", + line_count); + delete plist; + fclose(fp); + return false; + } + coord[i] = (REAL) strtod(bufferp, &bufferp); + } + } + } + } + } + } // if(!binary) + + else { + rewind(fp); + while(!feof(fp)) { + char header[80]; + if(!fread(header, sizeof(char), 80, fp)) break; + unsigned int nfacets = 0; + size_t ret = fread(&nfacets, sizeof(unsigned int), 1, fp); + bool swap = false; + if(nfacets > 100000000){ + //Msg::Info("Swapping bytes from binary file"); + swap = true; + SwapBytes((char*)&nfacets, sizeof(unsigned int), 1); + } + if(ret && nfacets){ + //points.resize(points.size() + 1); + char *data = new char[nfacets * 50 * sizeof(char)]; + ret = fread(data, sizeof(char), nfacets * 50, fp); + if(ret == nfacets * 50){ + for(unsigned int i = 0; i < nfacets; i++) { + float *xyz = (float *)&data[i * 50 * sizeof(char)]; + if(swap) SwapBytes((char*)xyz, sizeof(float), 12); + for(int j = 0; j < 3; j++){ + //SPoint3 p(xyz[3 + 3 * j], xyz[3 + 3 * j + 1], xyz[3 + 3 * j + 2]); + //points.back().push_back(p); + //bbox += p; + plist->newindex((void **) &coord); + coord[0] = xyz[3 + 3 * j]; + coord[1] = xyz[3 + 3 * j + 1]; + coord[2] = xyz[3 + 3 * j + 2]; + } + } + } + delete [] data; + } + } // while (!feof(fp)) + } // binary + + fclose(fp); + + nverts = (int) plist->objects; + // nverts should be an integer times 3 (every 3 vertices denote a face). + if (nverts == 0 || (nverts % 3 != 0)) { + printf("Error: Wrong number of vertices in file %s.\n", infilename); + delete plist; + return false; + } + numberofpoints = nverts; + pointlist = new REAL[nverts * 3]; + for (i = 0; i < nverts; i++) { + coord = (double *) fastlookup(plist, i); + iverts = i * 3; + pointlist[iverts] = (REAL) coord[0]; + pointlist[iverts + 1] = (REAL) coord[1]; + pointlist[iverts + 2] = (REAL) coord[2]; + } + + nfaces = (int) (nverts / 3); + numberoffacets = nfaces; + facetlist = new tetgenio::facet[nfaces]; + + // Default use '1' as the array starting index. + firstnumber = 1; + iverts = firstnumber; + for (i = 0; i < nfaces; i++) { + f = &facetlist[i]; + init(f); + // In .stl format, each facet has one polygon, no hole. + f->numberofpolygons = 1; + f->polygonlist = new tetgenio::polygon[1]; + p = &f->polygonlist[0]; + init(p); + // Each polygon has three vertices. + p->numberofvertices = 3; + p->vertexlist = new int[p->numberofvertices]; + p->vertexlist[0] = iverts; + p->vertexlist[1] = iverts + 1; + p->vertexlist[2] = iverts + 2; + iverts += 3; + } + + delete plist; + return true; +} + +//============================================================================// +// // +// load_medit() Load a surface mesh from a .mesh file. // +// // +// The .mesh format is the file format of Medit, a user-friendly interactive // +// mesh viewer program. // +// // +//============================================================================// + +bool tetgenio::load_medit(char* filebasename, int istetmesh) +{ + FILE *fp; + tetgenio::facet *tmpflist, *f; + tetgenio::polygon *p; + char infilename[FILENAMESIZE]; + char buffer[INPUTLINESIZE]; + char *bufferp, *str; + double *coord; + int *tmpfmlist; + int dimension = 0; + int nverts = 0; + int nfaces = 0; + int ntets = 0; + int line_count = 0; + int corners = 0; // 3 (triangle) or 4 (quad). + int *plist; + int i, j; + + int smallestidx = 0; + + strncpy(infilename, filebasename, FILENAMESIZE - 1); + infilename[FILENAMESIZE - 1] = '\0'; + if (infilename[0] == '\0') { + //printf("Error: No filename.\n"); + return false; + } + if (strcmp(&infilename[strlen(infilename) - 5], ".mesh") != 0) { + strcat(infilename, ".mesh"); + } + + if (!(fp = fopen(infilename, "r"))) { + //printf("Error: Unable to open file %s\n", infilename); + return false; + } + printf("Opening %s.\n", infilename); + + while ((bufferp = readline(buffer, fp, &line_count)) != NULL) { + if (*bufferp == '#') continue; // A comment line is skipped. + if (dimension == 0) { + // Find if it is the keyword "Dimension". + str = strstr(bufferp, "Dimension"); + if (!str) str = strstr(bufferp, "dimension"); + if (!str) str = strstr(bufferp, "DIMENSION"); + if (str) { + // Read the dimensions + bufferp = findnextnumber(str); // Skip field "Dimension". + if (*bufferp == '\0') { + // Read a non-empty line. + bufferp = readline(buffer, fp, &line_count); + } + dimension = (int) strtol(bufferp, &bufferp, 0); + if (dimension != 2 && dimension != 3) { + printf("Unknown dimension in file on line %d in file %s\n", + line_count, infilename); + fclose(fp); + return false; + } + mesh_dim = dimension; + } + } + if (nverts == 0) { + // Find if it is the keyword "Vertices". + str = strstr(bufferp, "Vertices"); + if (!str) str = strstr(bufferp, "vertices"); + if (!str) str = strstr(bufferp, "VERTICES"); + if (str) { + // Read the number of vertices. + bufferp = findnextnumber(str); // Skip field "Vertices". + if (*bufferp == '\0') { + // Read a non-empty line. + bufferp = readline(buffer, fp, &line_count); + } + nverts = (int) strtol(bufferp, &bufferp, 0); + // Initialize the smallest index. + smallestidx = nverts + 1; + // Allocate memory for 'tetgenio' + if (nverts > 0) { + numberofpoints = nverts; + pointlist = new REAL[nverts * 3]; + } + // Read the follwoing node list. + for (i = 0; i < nverts; i++) { + bufferp = readline(buffer, fp, &line_count); + if (bufferp == NULL) { + printf("Unexpected end of file on line %d in file %s\n", + line_count, infilename); + fclose(fp); + return false; + } + // Read vertex coordinates + coord = &pointlist[i * 3]; + for (j = 0; j < 3; j++) { + if (*bufferp == '\0') { + printf("Syntax error reading vertex coords on line"); + printf(" %d in file %s\n", line_count, infilename); + fclose(fp); + return false; + } + if ((j < 2) || (dimension == 3)) { + coord[j] = (REAL) strtod(bufferp, &bufferp); + } else { + coord[j] = 0.0; + } + bufferp = findnextnumber(bufferp); + } + } + continue; + } + } + if ((ntets == 0) && istetmesh) { // Only read tetrahedra if 'istetmesh = 1'. + // Find if it is the keyword "Tetrahedra" + corners = 0; + str = strstr(bufferp, "Tetrahedra"); + if (!str) str = strstr(bufferp, "tetrahedra"); + if (!str) str = strstr(bufferp, "TETRAHEDRA"); + if (str) { + corners = 4; + } + if (corners == 4) { + // Read the number of tetrahedra + bufferp = findnextnumber(str); // Skip field "Tetrahedra". + if (*bufferp == '\0') { + // Read a non-empty line. + bufferp = readline(buffer, fp, &line_count); + } + ntets = strtol(bufferp, &bufferp, 0); + if (ntets > 0) { + // It is a tetrahedral mesh. + numberoftetrahedra = ntets; + numberofcorners = 4; + numberoftetrahedronattributes = 1; + tetrahedronlist = new int[ntets * 4]; + tetrahedronattributelist = new REAL[ntets]; + } + } // if (corners == 4) + // Read the list of tetrahedra. + for (i = 0; i < numberoftetrahedra; i++) { + plist = &(tetrahedronlist[i * 4]); + bufferp = readline(buffer, fp, &line_count); + if (bufferp == NULL) { + printf("Unexpected end of file on line %d in file %s\n", + line_count, infilename); + fclose(fp); + return false; + } + // Read the vertices of the tet. + for (j = 0; j < corners; j++) { + if (*bufferp == '\0') { + printf("Syntax error reading face on line %d in file %s\n", + line_count, infilename); + fclose(fp); + return false; + } + plist[j] = (int) strtol(bufferp, &bufferp, 0); + // Remember the smallest index. + if (plist[j] < smallestidx) smallestidx = plist[j]; + bufferp = findnextnumber(bufferp); + } + // Read the attribute of the tet if it exists. + tetrahedronattributelist[i] = 0; + if (*bufferp != '\0') { + tetrahedronattributelist[i] = (REAL) strtol(bufferp, &bufferp, 0); + } + } // i + } // Tetrahedra + if (nfaces == 0) { + // Find if it is the keyword "Triangles" or "Quadrilaterals". + corners = 0; + str = strstr(bufferp, "Triangles"); + if (!str) str = strstr(bufferp, "triangles"); + if (!str) str = strstr(bufferp, "TRIANGLES"); + if (str) { + corners = 3; + } else { + str = strstr(bufferp, "Quadrilaterals"); + if (!str) str = strstr(bufferp, "quadrilaterals"); + if (!str) str = strstr(bufferp, "QUADRILATERALS"); + if (str) { + corners = 4; + } + } + if (corners == 3 || corners == 4) { + // Read the number of triangles (or quadrilaterals). + bufferp = findnextnumber(str); // Skip field "Triangles". + if (*bufferp == '\0') { + // Read a non-empty line. + bufferp = readline(buffer, fp, &line_count); + } + nfaces = strtol(bufferp, &bufferp, 0); + // Allocate memory for 'tetgenio' + if (nfaces > 0) { + if (!istetmesh) { + // It is a PLC surface mesh. + if (numberoffacets > 0) { + // facetlist has already been allocated. Enlarge arrays. + // This happens when the surface mesh contains mixed cells. + tmpflist = new tetgenio::facet[numberoffacets + nfaces]; + tmpfmlist = new int[numberoffacets + nfaces]; + // Copy the data of old arrays into new arrays. + for (i = 0; i < numberoffacets; i++) { + f = &(tmpflist[i]); + tetgenio::init(f); + *f = facetlist[i]; + tmpfmlist[i] = facetmarkerlist[i]; + } + // Release old arrays. + delete [] facetlist; + delete [] facetmarkerlist; + // Remember the new arrays. + facetlist = tmpflist; + facetmarkerlist = tmpfmlist; + } else { + // This is the first time to allocate facetlist. + facetlist = new tetgenio::facet[nfaces]; + facetmarkerlist = new int[nfaces]; + } + } else { + if (corners == 3) { + // It is a surface mesh of a tetrahedral mesh. + numberoftrifaces = nfaces; + trifacelist = new int[nfaces * 3]; + trifacemarkerlist = new int[nfaces]; + } + } + } // if (nfaces > 0) + // Read the following list of faces. + if (!istetmesh) { + for (i = numberoffacets; i < numberoffacets + nfaces; i++) { + bufferp = readline(buffer, fp, &line_count); + if (bufferp == NULL) { + printf("Unexpected end of file on line %d in file %s\n", + line_count, infilename); + fclose(fp); + return false; + } + f = &facetlist[i]; + tetgenio::init(f); + // In .mesh format, each facet has one polygon, no hole. + f->numberofpolygons = 1; + f->polygonlist = new tetgenio::polygon[1]; + p = &f->polygonlist[0]; + tetgenio::init(p); + p->numberofvertices = corners; + // Allocate memory for face vertices + p->vertexlist = new int[p->numberofvertices]; + // Read the vertices of the face. + for (j = 0; j < corners; j++) { + if (*bufferp == '\0') { + printf("Syntax error reading face on line %d in file %s\n", + line_count, infilename); + fclose(fp); + return false; + } + p->vertexlist[j] = (int) strtol(bufferp, &bufferp, 0); + // Remember the smallest index. + if (p->vertexlist[j] < smallestidx) { + smallestidx = p->vertexlist[j]; + } + bufferp = findnextnumber(bufferp); + } + // Read the marker of the face if it exists. + facetmarkerlist[i] = 0; + if (*bufferp != '\0') { + facetmarkerlist[i] = (int) strtol(bufferp, &bufferp, 0); + } + } + // Have read in a list of triangles/quads. + numberoffacets += nfaces; + nfaces = 0; + } else { + // It is a surface mesh of a tetrahedral mesh. + if (corners == 3) { + for (i = 0; i < numberoftrifaces; i++) { + plist = &(trifacelist[i * 3]); + bufferp = readline(buffer, fp, &line_count); + if (bufferp == NULL) { + printf("Unexpected end of file on line %d in file %s\n", + line_count, infilename); + fclose(fp); + return false; + } + // Read the vertices of the face. + for (j = 0; j < corners; j++) { + if (*bufferp == '\0') { + printf("Syntax error reading face on line %d in file %s\n", + line_count, infilename); + fclose(fp); + return false; + } + plist[j] = (int) strtol(bufferp, &bufferp, 0); + // Remember the smallest index. + if (plist[j] < smallestidx) { + smallestidx = plist[j]; + } + bufferp = findnextnumber(bufferp); + } + // Read the marker of the face if it exists. + trifacemarkerlist[i] = 0; + if (*bufferp != '\0') { + trifacemarkerlist[i] = (int) strtol(bufferp, &bufferp, 0); + } + } // i + } // if (corners == 3) + } // if (b->refine) + } // if (corners == 3 || corners == 4) + } + } + + fclose(fp); + + // Decide the firstnumber of the index. + if (smallestidx == 0) { + firstnumber = 0; + } else if (smallestidx == 1) { + firstnumber = 1; + } else { + printf("A wrong smallest index (%d) was detected in file %s\n", + smallestidx, infilename); + return false; + } + + return true; +} + +//============================================================================// +// // +// load_vtk() Load VTK surface mesh from file (.vtk ascii or binary). // +// // +// This function is contributed by: Bryn Lloyd, Computer Vision Laboratory, // +// ETH, Zuerich. May 7, 2007. // +// // +//============================================================================// + +// Two inline functions used in read/write VTK files. + +void swapBytes(unsigned char* var, int size) +{ + int i = 0; + int j = size - 1; + char c; + + while (i < j) { + c = var[i]; var[i] = var[j]; var[j] = c; + i++, j--; + } +} + +bool testIsBigEndian() +{ + short word = 0x4321; + if((*(char *)& word) != 0x21) + return true; + else + return false; +} + +bool tetgenio::load_vtk(char* filebasename) +{ + FILE *fp; + tetgenio::facet *f; + tetgenio::polygon *p; + char infilename[FILENAMESIZE]; + char line[INPUTLINESIZE]; + char mode[128], id[256], fmt[64]; + char *bufferp; + double *coord; + float _x, _y, _z; + int nverts = 0; + int nfaces = 0; + int line_count = 0; + int dummy; + int id1, id2, id3; + int nn = -1; + int nn_old = -1; + int i, j; + bool ImALittleEndian = !testIsBigEndian(); + + int smallestidx = 0; + + strncpy(infilename, filebasename, FILENAMESIZE - 1); + infilename[FILENAMESIZE - 1] = '\0'; + if (infilename[0] == '\0') { + printf("Error: No filename.\n"); + return false; + } + if (strcmp(&infilename[strlen(infilename) - 4], ".vtk") != 0) { + strcat(infilename, ".vtk"); + } + if (!(fp = fopen(infilename, "r"))) { + printf("Error: Unable to open file %s\n", infilename); + return false; + } + printf("Opening %s.\n", infilename); + + // Default uses the index starts from '0'. + firstnumber = 0; + strcpy(mode, "BINARY"); + + while((bufferp = readline(line, fp, &line_count)) != NULL) { + if(strlen(line) == 0) continue; + //swallow lines beginning with a comment sign or white space + if(line[0] == '#' || line[0]=='\n' || line[0] == 10 || line[0] == 13 || + line[0] == 32) continue; + + sscanf(line, "%s", id); + if(!strcmp(id, "ASCII")) { + strcpy(mode, "ASCII"); + } + + if(!strcmp(id, "POINTS")) { + sscanf(line, "%s %d %s", id, &nverts, fmt); + if (nverts > 0) { + numberofpoints = nverts; + pointlist = new REAL[nverts * 3]; + smallestidx = nverts + 1; + } + + if(!strcmp(mode, "BINARY")) { + for(i = 0; i < nverts; i++) { + coord = &pointlist[i * 3]; + if(!strcmp(fmt, "double")) { + fread((char*)(&(coord[0])), sizeof(double), 1, fp); + fread((char*)(&(coord[1])), sizeof(double), 1, fp); + fread((char*)(&(coord[2])), sizeof(double), 1, fp); + if(ImALittleEndian){ + swapBytes((unsigned char *) &(coord[0]), sizeof(coord[0])); + swapBytes((unsigned char *) &(coord[1]), sizeof(coord[1])); + swapBytes((unsigned char *) &(coord[2]), sizeof(coord[2])); + } + } else if(!strcmp(fmt, "float")) { + fread((char*)(&_x), sizeof(float), 1, fp); + fread((char*)(&_y), sizeof(float), 1, fp); + fread((char*)(&_z), sizeof(float), 1, fp); + if(ImALittleEndian){ + swapBytes((unsigned char *) &_x, sizeof(_x)); + swapBytes((unsigned char *) &_y, sizeof(_y)); + swapBytes((unsigned char *) &_z, sizeof(_z)); + } + coord[0] = double(_x); + coord[1] = double(_y); + coord[2] = double(_z); + } else { + printf("Error: Only float or double formats are supported!\n"); + return false; + } + } + } else if(!strcmp(mode, "ASCII")) { + for(i = 0; i < nverts; i++){ + bufferp = readline(line, fp, &line_count); + if (bufferp == NULL) { + printf("Unexpected end of file on line %d in file %s\n", + line_count, infilename); + fclose(fp); + return false; + } + // Read vertex coordinates + coord = &pointlist[i * 3]; + for (j = 0; j < 3; j++) { + if (*bufferp == '\0') { + printf("Syntax error reading vertex coords on line"); + printf(" %d in file %s\n", line_count, infilename); + fclose(fp); + return false; + } + coord[j] = (REAL) strtod(bufferp, &bufferp); + bufferp = findnextnumber(bufferp); + } + } + } + continue; + } + + if(!strcmp(id, "POLYGONS")) { + sscanf(line, "%s %d %d", id, &nfaces, &dummy); + if (nfaces > 0) { + numberoffacets = nfaces; + facetlist = new tetgenio::facet[nfaces]; + } + + if(!strcmp(mode, "BINARY")) { + for(i = 0; i < nfaces; i++){ + fread((char*)(&nn), sizeof(int), 1, fp); + if(ImALittleEndian){ + swapBytes((unsigned char *) &nn, sizeof(nn)); + } + if (i == 0) + nn_old = nn; + if (nn != nn_old) { + printf("Error: No mixed cells are allowed.\n"); + return false; + } + + if(nn == 3){ + fread((char*)(&id1), sizeof(int), 1, fp); + fread((char*)(&id2), sizeof(int), 1, fp); + fread((char*)(&id3), sizeof(int), 1, fp); + if(ImALittleEndian){ + swapBytes((unsigned char *) &id1, sizeof(id1)); + swapBytes((unsigned char *) &id2, sizeof(id2)); + swapBytes((unsigned char *) &id3, sizeof(id3)); + } + f = &facetlist[i]; + init(f); + // In .off format, each facet has one polygon, no hole. + f->numberofpolygons = 1; + f->polygonlist = new tetgenio::polygon[1]; + p = &f->polygonlist[0]; + init(p); + // Set number of vertices + p->numberofvertices = 3; + // Allocate memory for face vertices + p->vertexlist = new int[p->numberofvertices]; + p->vertexlist[0] = id1; + p->vertexlist[1] = id2; + p->vertexlist[2] = id3; + // Detect the smallest index. + for (j = 0; j < 3; j++) { + if (p->vertexlist[j] < smallestidx) { + smallestidx = p->vertexlist[j]; + } + } + } else { + printf("Error: Only triangles are supported\n"); + return false; + } + } + } else if(!strcmp(mode, "ASCII")) { + for(i = 0; i < nfaces; i++) { + bufferp = readline(line, fp, &line_count); + nn = (int) strtol(bufferp, &bufferp, 0); + if (i == 0) + nn_old = nn; + if (nn != nn_old) { + printf("Error: No mixed cells are allowed.\n"); + return false; + } + + if (nn == 3) { + bufferp = findnextnumber(bufferp); // Skip the first field. + id1 = (int) strtol(bufferp, &bufferp, 0); + bufferp = findnextnumber(bufferp); + id2 = (int) strtol(bufferp, &bufferp, 0); + bufferp = findnextnumber(bufferp); + id3 = (int) strtol(bufferp, &bufferp, 0); + f = &facetlist[i]; + init(f); + // In .off format, each facet has one polygon, no hole. + f->numberofpolygons = 1; + f->polygonlist = new tetgenio::polygon[1]; + p = &f->polygonlist[0]; + init(p); + // Set number of vertices + p->numberofvertices = 3; + // Allocate memory for face vertices + p->vertexlist = new int[p->numberofvertices]; + p->vertexlist[0] = id1; + p->vertexlist[1] = id2; + p->vertexlist[2] = id3; + // Detect the smallest index. + for (j = 0; j < 3; j++) { + if (p->vertexlist[j] < smallestidx) { + smallestidx = p->vertexlist[j]; + } + } + } else { + printf("Error: Only triangles are supported.\n"); + return false; + } + } + } + + fclose(fp); + + // Decide the firstnumber of the index. + if (smallestidx == 0) { + firstnumber = 0; + } else if (smallestidx == 1) { + firstnumber = 1; + } else { + printf("A wrong smallest index (%d) was detected in file %s\n", + smallestidx, infilename); + return false; + } + + return true; + } + + if(!strcmp(id,"LINES") || !strcmp(id,"CELLS")){ + printf("Warning: load_vtk(): cannot read formats LINES, CELLS.\n"); + } + } // while () + + return true; +} + +//============================================================================// +// // +// load_plc() Load a piecewise linear complex from file(s). // +// // +//============================================================================// + +bool tetgenio::load_plc(char* filebasename, int object) +{ + bool success; + + if (object == (int) tetgenbehavior::NODES) { + success = load_node(filebasename); + } else if (object == (int) tetgenbehavior::POLY) { + success = load_poly(filebasename); + } else if (object == (int) tetgenbehavior::OFF) { + success = load_off(filebasename); + } else if (object == (int) tetgenbehavior::PLY) { + success = load_ply(filebasename); + } else if (object == (int) tetgenbehavior::STL) { + success = load_stl(filebasename); + } else if (object == (int) tetgenbehavior::MEDIT) { + success = load_medit(filebasename, 0); + } else if (object == (int) tetgenbehavior::VTK) { + success = load_vtk(filebasename); + } else { + success = load_poly(filebasename); + } + + if (success) { + // Try to load the following files (.edge, .var, .mtr). + load_edge(filebasename); + load_var(filebasename); + load_mtr(filebasename); + } + + return success; +} + +//============================================================================// +// // +// load_mesh() Load a tetrahedral mesh from file(s). // +// // +//============================================================================// + +bool tetgenio::load_tetmesh(char* filebasename, int object) +{ + bool success = false; + + if (object == (int) tetgenbehavior::MEDIT) { + success = load_medit(filebasename, 1); + } else if (object == (int) tetgenbehavior::NEU_MESH) { + //success = load_neumesh(filebasename, 1); + } else { + success = load_node(filebasename); + if (success) { + success = load_tet(filebasename); + } + if (success) { + // Try to load the following files (.face, .edge, .vol). + load_face(filebasename); + load_edge(filebasename); + load_vol(filebasename); + } + } + + if (success) { + // Try to load the following files (.var, .mtr). + load_var(filebasename); + load_mtr(filebasename); + load_elem(filebasename); + } + + return success; +} + +//============================================================================// +// // +// save_nodes() Save points to a .node file. // +// // +//============================================================================// + +void tetgenio::save_nodes(const char *filebasename) +{ + FILE *fout; + char outnodefilename[FILENAMESIZE]; + char outmtrfilename[FILENAMESIZE]; + int i, j; + + sprintf(outnodefilename, "%s.node", filebasename); + printf("Saving nodes to %s\n", outnodefilename); + fout = fopen(outnodefilename, "w"); + fprintf(fout, "%d %d %d %d\n", numberofpoints, mesh_dim, + numberofpointattributes, pointmarkerlist != NULL ? 1 : 0); + for (i = 0; i < numberofpoints; i++) { + if (mesh_dim == 2) { + fprintf(fout, "%d %.16g %.16g", i + firstnumber, pointlist[i * 3], + pointlist[i * 3 + 1]); + } else { + fprintf(fout, "%d %.16g %.16g %.16g", i + firstnumber, + pointlist[i * 3], pointlist[i * 3 + 1], pointlist[i * 3 + 2]); + } + for (j = 0; j < numberofpointattributes; j++) { + fprintf(fout, " %.16g", + pointattributelist[i * numberofpointattributes + j]); + } + if (pointmarkerlist != NULL) { + fprintf(fout, " %d", pointmarkerlist[i]); + } + fprintf(fout, "\n"); + } + fclose(fout); + + // If the point metrics exist, output them to a .mtr file. + if ((numberofpointmtrs > 0) && (pointmtrlist != (REAL *) NULL)) { + sprintf(outmtrfilename, "%s.mtr", filebasename); + printf("Saving metrics to %s\n", outmtrfilename); + fout = fopen(outmtrfilename, "w"); + fprintf(fout, "%d %d\n", numberofpoints, numberofpointmtrs); + for (i = 0; i < numberofpoints; i++) { + for (j = 0; j < numberofpointmtrs; j++) { + fprintf(fout, "%.16g ", pointmtrlist[i * numberofpointmtrs + j]); + } + fprintf(fout, "\n"); + } + fclose(fout); + } +} + +//============================================================================// +// // +// save_elements() Save elements to a .ele file. // +// // +//============================================================================// + +void tetgenio::save_elements(const char* filebasename) +{ + FILE *fout; + char outelefilename[FILENAMESIZE]; + int i, j; + + sprintf(outelefilename, "%s.ele", filebasename); + printf("Saving elements to %s\n", outelefilename); + fout = fopen(outelefilename, "w"); + if (mesh_dim == 3) { + fprintf(fout, "%d %d %d\n", numberoftetrahedra, numberofcorners, + numberoftetrahedronattributes); + for (i = 0; i < numberoftetrahedra; i++) { + fprintf(fout, "%d", i + firstnumber); + for (j = 0; j < numberofcorners; j++) { + fprintf(fout, " %5d", tetrahedronlist[i * numberofcorners + j]); + } + for (j = 0; j < numberoftetrahedronattributes; j++) { + fprintf(fout, " %g", + tetrahedronattributelist[i * numberoftetrahedronattributes + j]); + } + fprintf(fout, "\n"); + } + } else { + // Save a two-dimensional mesh. + fprintf(fout, "%d %d %d\n",numberoftrifaces,3,trifacemarkerlist ? 1 : 0); + for (i = 0; i < numberoftrifaces; i++) { + fprintf(fout, "%d", i + firstnumber); + for (j = 0; j < 3; j++) { + fprintf(fout, " %5d", trifacelist[i * 3 + j]); + } + if (trifacemarkerlist != NULL) { + fprintf(fout, " %d", trifacemarkerlist[i]); + } + fprintf(fout, "\n"); + } + } + + fclose(fout); +} + +//============================================================================// +// // +// save_faces() Save faces to a .face file. // +// // +//============================================================================// + +void tetgenio::save_faces(const char* filebasename) +{ + FILE *fout; + char outfacefilename[FILENAMESIZE]; + int i; + + sprintf(outfacefilename, "%s.face", filebasename); + printf("Saving faces to %s\n", outfacefilename); + fout = fopen(outfacefilename, "w"); + fprintf(fout, "%d %d\n", numberoftrifaces, + trifacemarkerlist != NULL ? 1 : 0); + for (i = 0; i < numberoftrifaces; i++) { + fprintf(fout, "%d %5d %5d %5d", i + firstnumber, trifacelist[i * 3], + trifacelist[i * 3 + 1], trifacelist[i * 3 + 2]); + if (trifacemarkerlist != NULL) { + fprintf(fout, " %d", trifacemarkerlist[i]); + } + fprintf(fout, "\n"); + } + + fclose(fout); +} + +//============================================================================// +// // +// save_edges() Save egdes to a .edge file. // +// // +//============================================================================// + +void tetgenio::save_edges(char* filebasename) +{ + FILE *fout; + char outedgefilename[FILENAMESIZE]; + int i; + + sprintf(outedgefilename, "%s.edge", filebasename); + printf("Saving edges to %s\n", outedgefilename); + fout = fopen(outedgefilename, "w"); + fprintf(fout, "%d %d\n", numberofedges, edgemarkerlist != NULL ? 1 : 0); + for (i = 0; i < numberofedges; i++) { + fprintf(fout, "%d %4d %4d", i + firstnumber, edgelist[i * 2], + edgelist[i * 2 + 1]); + if (edgemarkerlist != NULL) { + fprintf(fout, " %d", edgemarkerlist[i]); + } + fprintf(fout, "\n"); + } + + fclose(fout); +} + +//============================================================================// +// // +// save_neighbors() Save egdes to a .neigh file. // +// // +//============================================================================// + +void tetgenio::save_neighbors(char* filebasename) +{ + FILE *fout; + char outneighborfilename[FILENAMESIZE]; + int i; + + sprintf(outneighborfilename, "%s.neigh", filebasename); + printf("Saving neighbors to %s\n", outneighborfilename); + fout = fopen(outneighborfilename, "w"); + fprintf(fout, "%d %d\n", numberoftetrahedra, mesh_dim + 1); + for (i = 0; i < numberoftetrahedra; i++) { + if (mesh_dim == 2) { + fprintf(fout, "%d %5d %5d %5d", i + firstnumber, neighborlist[i * 3], + neighborlist[i * 3 + 1], neighborlist[i * 3 + 2]); + } else { + fprintf(fout, "%d %5d %5d %5d %5d", i + firstnumber, + neighborlist[i * 4], neighborlist[i * 4 + 1], + neighborlist[i * 4 + 2], neighborlist[i * 4 + 3]); + } + fprintf(fout, "\n"); + } + + fclose(fout); +} + +//============================================================================// +// // +// save_poly() Save segments or facets to a .poly file. // +// // +// It only save the facets, holes and regions. No .node file is saved. // +// // +//============================================================================// + +void tetgenio::save_poly(const char *filebasename) +{ + FILE *fout; + facet *f; + polygon *p; + char outpolyfilename[FILENAMESIZE]; + int i, j, k; + + sprintf(outpolyfilename, "%s.poly", filebasename); + printf("Saving poly to %s\n", outpolyfilename); + fout = fopen(outpolyfilename, "w"); + + // The zero indicates that the vertices are in a separate .node file. + // Followed by number of dimensions, number of vertex attributes, + // and number of boundary markers (zero or one). + fprintf(fout, "%d %d %d %d\n", 0, mesh_dim, numberofpointattributes, + pointmarkerlist != NULL ? 1 : 0); + + // Save segments or facets. + if (mesh_dim == 2) { + // Number of segments, number of boundary markers (zero or one). + fprintf(fout, "%d %d\n", numberofedges, edgemarkerlist != NULL ? 1 : 0); + for (i = 0; i < numberofedges; i++) { + fprintf(fout, "%d %4d %4d", i + firstnumber, edgelist[i * 2], + edgelist[i * 2 + 1]); + if (edgemarkerlist != NULL) { + fprintf(fout, " %d", edgemarkerlist[i]); + } + fprintf(fout, "\n"); + } + } else { + // Number of facets, number of boundary markers (zero or one). + fprintf(fout, "%d %d\n", numberoffacets, facetmarkerlist != NULL ? 1 : 0); + for (i = 0; i < numberoffacets; i++) { + f = &(facetlist[i]); + fprintf(fout, "%d %d %d # %d\n", f->numberofpolygons,f->numberofholes, + facetmarkerlist != NULL ? facetmarkerlist[i] : 0, i + firstnumber); + // Output polygons of this facet. + for (j = 0; j < f->numberofpolygons; j++) { + p = &(f->polygonlist[j]); + fprintf(fout, "%d ", p->numberofvertices); + for (k = 0; k < p->numberofvertices; k++) { + if (((k + 1) % 10) == 0) { + fprintf(fout, "\n "); + } + fprintf(fout, " %d", p->vertexlist[k]); + } + fprintf(fout, "\n"); + } + // Output holes of this facet. + for (j = 0; j < f->numberofholes; j++) { + fprintf(fout, "%d %.12g %.12g %.12g\n", j + firstnumber, + f->holelist[j * 3], f->holelist[j * 3 + 1], f->holelist[j * 3 + 2]); + } + } + } + + // Save holes. + fprintf(fout, "%d\n", numberofholes); + for (i = 0; i < numberofholes; i++) { + // Output x, y coordinates. + fprintf(fout, "%d %.12g %.12g", i + firstnumber, holelist[i * mesh_dim], + holelist[i * mesh_dim + 1]); + if (mesh_dim == 3) { + // Output z coordinate. + fprintf(fout, " %.12g", holelist[i * mesh_dim + 2]); + } + fprintf(fout, "\n"); + } + + // Save regions. + fprintf(fout, "%d\n", numberofregions); + for (i = 0; i < numberofregions; i++) { + if (mesh_dim == 2) { + // Output the index, x, y coordinates, attribute (region number) + // and maximum area constraint (maybe -1). + fprintf(fout, "%d %.12g %.12g %.12g %.12g\n", i + firstnumber, + regionlist[i * 4], regionlist[i * 4 + 1], + regionlist[i * 4 + 2], regionlist[i * 4 + 3]); + } else { + // Output the index, x, y, z coordinates, attribute (region number) + // and maximum volume constraint (maybe -1). + fprintf(fout, "%d %.12g %.12g %.12g %.12g %.12g\n", i + firstnumber, + regionlist[i * 5], regionlist[i * 5 + 1], + regionlist[i * 5 + 2], regionlist[i * 5 + 3], + regionlist[i * 5 + 4]); + } + } + + fclose(fout); +} + +//============================================================================// +// // +// save_faces2smesh() Save triangular faces to a .smesh file. // +// // +// It only save the facets. No holes and regions. No .node file. // +// // +//============================================================================// + +void tetgenio::save_faces2smesh(char* filebasename) +{ + FILE *fout; + char outsmeshfilename[FILENAMESIZE]; + int i, j; + + sprintf(outsmeshfilename, "%s.smesh", filebasename); + printf("Saving faces to %s\n", outsmeshfilename); + fout = fopen(outsmeshfilename, "w"); + + // The zero indicates that the vertices are in a separate .node file. + // Followed by number of dimensions, number of vertex attributes, + // and number of boundary markers (zero or one). + fprintf(fout, "%d %d %d %d\n", 0, mesh_dim, numberofpointattributes, + pointmarkerlist != NULL ? 1 : 0); + + // Number of facets, number of boundary markers (zero or one). + fprintf(fout, "%d %d\n", numberoftrifaces, + trifacemarkerlist != NULL ? 1 : 0); + + // Output triangular facets. + for (i = 0; i < numberoftrifaces; i++) { + j = i * 3; + fprintf(fout, "3 %d %d %d", trifacelist[j], trifacelist[j + 1], + trifacelist[j + 2]); + if (trifacemarkerlist != NULL) { + fprintf(fout, " %d", trifacemarkerlist[i]); + } + fprintf(fout, "\n"); + } + + // No holes and regions. + fprintf(fout, "0\n"); + fprintf(fout, "0\n"); + + fclose(fout); +} + +//============================================================================// +// // +// readline() Read a nonempty line from a file. // +// // +// A line is considered "nonempty" if it contains something more than white // +// spaces. If a line is considered empty, it will be dropped and the next // +// line will be read, this process ends until reaching the end-of-file or a // +// non-empty line. Return NULL if it is the end-of-file, otherwise, return // +// a pointer to the first non-whitespace character of the line. // +// // +//============================================================================// + +char* tetgenio::readline(char *string, FILE *infile, int *linenumber) +{ + char *result; + + // Search for a non-empty line. + do { + result = fgets(string, INPUTLINESIZE - 1, infile); + if (linenumber) (*linenumber)++; + if (result == (char *) NULL) { + return (char *) NULL; + } + // Skip white spaces. + while ((*result == ' ') || (*result == '\t')) result++; + // If it's end of line, read another line and try again. + } while ((*result == '\0') || (*result == '\r') || (*result == '\n')); + return result; +} + +//============================================================================// +// // +// findnextfield() Find the next field of a string. // +// // +// Jumps past the current field by searching for whitespace or a comma, then // +// jumps past the whitespace or the comma to find the next field. // +// // +//============================================================================// + +char* tetgenio::findnextfield(char *string) +{ + char *result; + + result = string; + // Skip the current field. Stop upon reaching whitespace or a comma. + while ((*result != '\0') && (*result != ' ') && (*result != '\t') && + (*result != ',') && (*result != ';')) { + result++; + } + // Now skip the whitespace or the comma, stop at anything else that looks + // like a character, or the end of a line. + while ((*result == ' ') || (*result == '\t') || (*result == ',') || + (*result == ';')) { + result++; + } + return result; +} + +//============================================================================// +// // +// readnumberline() Read a nonempty number line from a file. // +// // +// A line is considered "nonempty" if it contains something that looks like // +// a number. Comments (prefaced by `#') are ignored. // +// // +//============================================================================// + +char* tetgenio::readnumberline(char *string, FILE *infile, char *infilename) +{ + char *result; + + // Search for something that looks like a number. + do { + result = fgets(string, INPUTLINESIZE, infile); + if (result == (char *) NULL) { + return result; + } + // Skip anything that doesn't look like a number, a comment, + // or the end of a line. + while ((*result != '\0') && (*result != '#') + && (*result != '.') && (*result != '+') && (*result != '-') + && ((*result < '0') || (*result > '9'))) { + result++; + } + // If it's a comment or end of line, read another line and try again. + } while ((*result == '#') || (*result == '\0')); + return result; +} + +//============================================================================// +// // +// findnextnumber() Find the next field of a number string. // +// // +// Jumps past the current field by searching for whitespace or a comma, then // +// jumps past the whitespace or the comma to find the next field that looks // +// like a number. // +// // +//============================================================================// + +char* tetgenio::findnextnumber(char *string) +{ + char *result; + + result = string; + // Skip the current field. Stop upon reaching whitespace or a comma. + while ((*result != '\0') && (*result != '#') && (*result != ' ') && + (*result != '\t') && (*result != ',')) { + result++; + } + // Now skip the whitespace and anything else that doesn't look like a + // number, a comment, or the end of a line. + while ((*result != '\0') && (*result != '#') + && (*result != '.') && (*result != '+') && (*result != '-') + && ((*result < '0') || (*result > '9'))) { + result++; + } + // Check for a comment (prefixed with `#'). + if (*result == '#') { + *result = '\0'; + } + return result; +} + +// // +// // +//== io_cxx ==================================================================// + + +//== behavior_cxx ============================================================// +// // +// // + +//============================================================================// +// // +// syntax() Print list of command line switches. // +// // +//============================================================================// + +void tetgenbehavior::syntax() +{ + printf(" tetgen [-pYrq_Aa_miO_S_T_XMwcdzfenvgkJBNEFICQVh] input_file\n"); + printf(" -p Tetrahedralizes a piecewise linear complex (PLC).\n"); + printf(" -Y Preserves the input surface mesh (does not modify it).\n"); + printf(" -r Reconstructs a previously generated mesh.\n"); + printf(" -q Refines mesh (to improve mesh quality).\n"); + printf(" -R Mesh coarsening (to reduce the mesh elements).\n"); + printf(" -A Assigns attributes to tetrahedra in different regions.\n"); + printf(" -a Applies a maximum tetrahedron volume constraint.\n"); + printf(" -m Applies a mesh sizing function.\n"); + printf(" -i Inserts a list of additional points.\n"); + printf(" -O Specifies the level of mesh optimization.\n"); + printf(" -S Specifies maximum number of added points.\n"); + printf(" -T Sets a tolerance for coplanar test (default 1e-8).\n"); + printf(" -X Suppresses use of exact arithmetic.\n"); + printf(" -M No merge of coplanar facets or very close vertices.\n"); + printf(" -w Generates weighted Delaunay (regular) triangulation.\n"); + printf(" -c Retains the convex hull of the PLC.\n"); + printf(" -d Detects self-intersections of facets of the PLC.\n"); + printf(" -z Numbers all output items starting from zero.\n"); + printf(" -f Outputs all faces to .face file.\n"); + printf(" -e Outputs all edges to .edge file.\n"); + printf(" -n Outputs tetrahedra neighbors to .neigh file.\n"); + printf(" -g Outputs mesh to .mesh file for viewing by Medit.\n"); + printf(" -k Outputs mesh to .vtk file for viewing by Paraview.\n"); + printf(" -J No jettison of unused vertices from output .node file.\n"); + printf(" -B Suppresses output of boundary information.\n"); + printf(" -N Suppresses output of .node file.\n"); + printf(" -E Suppresses output of .ele file.\n"); + printf(" -F Suppresses output of .face and .edge file.\n"); + printf(" -I Suppresses mesh iteration numbers.\n"); + printf(" -C Checks the consistency of the final mesh.\n"); + printf(" -Q Quiet: No terminal output except errors.\n"); + printf(" -V Verbose: Detailed information, more terminal output.\n"); + printf(" -h Help: A brief instruction for using TetGen.\n"); +} + +//============================================================================// +// // +// usage() Print a brief instruction for using TetGen. // +// // +//============================================================================// + +void tetgenbehavior::usage() +{ + printf("TetGen\n"); + printf("A Quality Tetrahedral Mesh Generator and 3D Delaunay "); + printf("Triangulator\n"); + printf("Version 1.6\n"); + printf("August, 2020\n"); + printf("\n"); + printf("Copyright (C) 2002 - 2020\n"); + printf("\n"); + printf("What Can TetGen Do?\n"); + printf("\n"); + printf(" TetGen generates Delaunay tetrahedralizations, constrained\n"); + printf(" Delaunay tetrahedralizations, and quality tetrahedral meshes.\n"); + printf("\n"); + printf("Command Line Syntax:\n"); + printf("\n"); + printf(" Below is the basic command line syntax of TetGen with a list of "); + printf("short\n"); + printf(" descriptions. Underscores indicate that numbers may optionally\n"); + printf(" follow certain switches. Do not leave any space between a "); + printf("switch\n"); + printf(" and its numeric parameter. \'input_file\' contains input data\n"); + printf(" depending on the switches you supplied, which may be a "); + printf(" piecewise\n"); + printf(" linear complex or a list of nodes. File formats and detailed\n"); + printf(" description of command line switches are found in the user's "); + printf("manual.\n"); + printf("\n"); + syntax(); + printf("\n"); + printf("Examples of How to Use TetGen:\n"); + printf("\n"); + printf(" \'tetgen object\' reads vertices from object.node, and writes "); + printf("their\n Delaunay tetrahedralization to object.1.node, "); + printf("object.1.ele\n (tetrahedra), and object.1.face"); + printf(" (convex hull faces).\n"); + printf("\n"); + printf(" \'tetgen -p object\' reads a PLC from object.poly or object."); + printf("smesh (and\n possibly object.node) and writes its constrained "); + printf("Delaunay\n tetrahedralization to object.1.node, object.1.ele, "); + printf("object.1.face,\n"); + printf(" (boundary faces) and object.1.edge (boundary edges).\n"); + printf("\n"); + printf(" \'tetgen -pq1.414a.1 object\' reads a PLC from object.poly or\n"); + printf(" object.smesh (and possibly object.node), generates a mesh "); + printf("whose\n tetrahedra have radius-edge ratio smaller than 1.414 and "); + printf("have volume\n of 0.1 or less, and writes the mesh to "); + printf("object.1.node, object.1.ele,\n object.1.face, and object.1.edge\n"); + printf("\n"); + printf("Please send bugs/comments to Hang Si \n"); + terminatetetgen(NULL, 0); +} + +//============================================================================// +// // +// parse_commandline() Read the command line, identify switches, and set // +// up options and file names. // +// // +// 'argc' and 'argv' are the same parameters passed to the function main() // +// of a C/C++ program. They together represent the command line user invoked // +// from an environment in which TetGen is running. // +// // +//============================================================================// + +bool tetgenbehavior::parse_commandline(int argc, char **argv) +{ + int startindex; + int increment; + int meshnumber; + int i, j, k; + char workstring[1024]; + + // First determine the input style of the switches. + if (argc == 0) { + startindex = 0; // Switches are given without a dash. + argc = 1; // For running the following for-loop once. + commandline[0] = '\0'; + } else { + startindex = 1; + strcpy(commandline, argv[0]); + strcat(commandline, " "); + } + + for (i = startindex; i < argc; i++) { + // Remember the command line for output. + strcat(commandline, argv[i]); + strcat(commandline, " "); + if (startindex == 1) { + // Is this string a filename? + if (argv[i][0] != '-') { + strncpy(infilename, argv[i], 1024 - 1); + infilename[1024 - 1] = '\0'; + continue; + } + } + // Parse the individual switch from the string. + for (j = startindex; argv[i][j] != '\0'; j++) { + if (argv[i][j] == 'p') { + plc = 1; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + facet_separate_ang_tol = (REAL) strtod(workstring, (char **) NULL); + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { + j++; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.') || (argv[i][j + 1] == 'e') || + (argv[i][j + 1] == '-') || (argv[i][j + 1] == '+')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + //facet_overlap_ang_tol = (REAL) strtod(workstring, (char **) NULL); + } + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { + j++; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + facet_small_ang_tol = (REAL) strtod(workstring, (char **) NULL); + } + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { + j++; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + collinear_ang_tol = (REAL) strtod(workstring, (char **) NULL); + } + } + } else if (argv[i][j] == 'Y') { + nobisect++; + if (cdt > 0) { + printf("Warning: switch -D is omitted.\n"); + cdt = 0; + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { + j++; + if ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) { + supsteiner_level = (argv[i][j + 1] - '0'); + j++; + } + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { + j++; + if ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) { + addsteiner_algo = (argv[i][j + 1] - '0'); + j++; + } + } + } else if (argv[i][j] == 'r') { + refine = 1; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + elem_growth_ratio = (REAL) strtod(workstring, (char **) NULL); + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { + j++; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + refine_progress_ratio = (REAL) strtod(workstring, (char **) NULL); + } + } + } else if (argv[i][j] == 'q') { + quality = 1; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + minratio = (REAL) strtod(workstring, (char **) NULL); + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { + j++; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + mindihedral = (REAL) strtod(workstring, (char **) NULL); + } + } + } else if (argv[i][j] == 'R') { + coarsen = 1; + if ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) { + coarsen_param = (argv[i][j + 1] - '0'); + j++; + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { + j++; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + coarsen_percent = (REAL) strtod(workstring, (char **) NULL); + } + } + } else if (argv[i][j] == 'w') { + weighted = 1; + if ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) { + weighted_param = (argv[i][j + 1] - '0'); + j++; + } + } else if (argv[i][j] == 'b') { + // -b(brio_threshold/brio_ratio/hilbert_limit/hilbert_order) + brio_hilbert = 1; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + brio_threshold = (int) strtol(workstring, (char **) &workstring, 0); + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { + j++; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + brio_ratio = (REAL) strtod(workstring, (char **) NULL); + } + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { + j++; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.') || (argv[i][j + 1] == '-')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.') || (argv[i][j + 1] == '-')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + hilbert_limit = (int) strtol(workstring, (char **) &workstring, 0); + } + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { + j++; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.') || (argv[i][j + 1] == '-')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.') || (argv[i][j + 1] == '-')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + hilbert_order = (REAL) strtod(workstring, (char **) NULL); + } + } + if (brio_threshold == 0) { // -b0 + brio_hilbert = 0; // Turn off BRIO-Hilbert sorting. + } + if (brio_ratio >= 1.0) { // -b/1 + no_sort = 1; + brio_hilbert = 0; // Turn off BRIO-Hilbert sorting. + } + } else if (argv[i][j] == 'L') { + flipinsert = 1; + } else if (argv[i][j] == 'm') { + metric = 1; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.') || (argv[i][j + 1] == 'e') || + (argv[i][j + 1] == '-') || (argv[i][j + 1] == '+')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + metric_scale = (REAL) strtod(workstring, (char **) NULL); + } + } else if (argv[i][j] == 'a') { + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + fixedvolume = 1; + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.') || (argv[i][j + 1] == 'e') || + (argv[i][j + 1] == '-') || (argv[i][j + 1] == '+')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + maxvolume = (REAL) strtod(workstring, (char **) NULL); + maxvolume_length = pow(maxvolume, 1./3.) / 3.; + } else { + varvolume = 1; + } + } else if (argv[i][j] == 'A') { + regionattrib = 1; + } else if (argv[i][j] == 'D') { + if ((argv[i][j + 1] >= '1') && (argv[i][j + 1] <= '7')) { + // -D# (with a number following it.) + cdtrefine = (argv[i][j + 1] - '1') + 1; + j++; + } else { + cdt = 1; // -D without a number following it. + } + } else if (argv[i][j] == 'i') { + insertaddpoints = 1; + } else if (argv[i][j] == 'd') { + diagnose = 1; + } else if (argv[i][j] == 'c') { + convex = 1; + } else if (argv[i][j] == 'M') { + nomergefacet = 1; + nomergevertex = 1; + if ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '1')) { + nomergefacet = (argv[i][j + 1] - '0'); + j++; + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { + j++; + if ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '1')) { + nomergevertex = (argv[i][j + 1] - '0'); + j++; + } + } + } else if (argv[i][j] == 'X') { + if (argv[i][j + 1] == '1') { + nostaticfilter = 1; + j++; + } else { + noexact = 1; + } + } else if (argv[i][j] == 'z') { + if (argv[i][j + 1] == '1') { // -z1 + reversetetori = 1; + j++; + } else { + zeroindex = 1; // -z + } + } else if (argv[i][j] == 'f') { + facesout++; + } else if (argv[i][j] == 'e') { + edgesout++; + } else if (argv[i][j] == 'n') { + neighout++; + } else if (argv[i][j] == 'g') { + meditview = 1; + } else if (argv[i][j] == 'k') { + if (argv[i][j + 1] == '2') { // -k2 + vtksurfview = 1; + j++; + } + else { + vtkview = 1; + } + } else if (argv[i][j] == 'J') { + nojettison = 1; + } else if (argv[i][j] == 'B') { + nobound = 1; + } else if (argv[i][j] == 'N') { + nonodewritten = 1; + } else if (argv[i][j] == 'E') { + noelewritten = 1; + } else if (argv[i][j] == 'F') { + nofacewritten = 1; + } else if (argv[i][j] == 'I') { + noiterationnum = 1; + } else if (argv[i][j] == 'S') { + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.') || (argv[i][j + 1] == 'e') || + (argv[i][j + 1] == '-') || (argv[i][j + 1] == '+')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + steinerleft = (int) strtol(workstring, (char **) NULL, 0); + } + } else if (argv[i][j] == 'o') { + if (argv[i][j + 1] == '2') { + order = 2; + j++; + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { // -o/# + j++; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + optmaxdihedral = (REAL) strtod(workstring, (char **) NULL); + } + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { // -o//# + j++; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.') || (argv[i][j + 1] == 'e') || + (argv[i][j + 1] == '-') || (argv[i][j + 1] == '+')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + opt_max_asp_ratio = (REAL) strtod(workstring, (char **) NULL); + } + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { // -o///# + j++; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.') || (argv[i][j + 1] == 'e') || + (argv[i][j + 1] == '-') || (argv[i][j + 1] == '+')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + opt_max_edge_ratio = (REAL) strtod(workstring, (char **) NULL); + } + } + } else if (argv[i][j] == 'O') { + if ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) { // -O# + opt_max_flip_level = (argv[i][j + 1] - '0'); + j++; + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { // -O/# + j++; + if ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '7')) { + opt_scheme = (argv[i][j + 1] - '0'); + j++; + } + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { // -O//# + j++; + if ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.') || (argv[i][j + 1] == 'e') || + (argv[i][j + 1] == '-') || (argv[i][j + 1] == '+')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + opt_iterations = (int) strtol(workstring, (char **) NULL, 0); + j++; + } + } + } else if (argv[i][j] == 's') { + if ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) { // -s# + smooth_cirterion = (argv[i][j + 1] - '0'); + j++; + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { // -s#/# + j++; + if ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.') || (argv[i][j + 1] == 'e') || + (argv[i][j + 1] == '-') || (argv[i][j + 1] == '+')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + smooth_maxiter = (int) strtol(workstring, (char **) NULL, 0); + } + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { // -s#/#/# + j++; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.') || (argv[i][j + 1] == 'e') || + (argv[i][j + 1] == '-') || (argv[i][j + 1] == '+')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + smooth_alpha = (REAL) strtod(workstring, (char **) NULL); + } + } + } else if (argv[i][j] == 'T') { + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.') || (argv[i][j + 1] == 'e') || + (argv[i][j + 1] == '-') || (argv[i][j + 1] == '+')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + epsilon = (REAL) strtod(workstring, (char **) NULL); + } + } else if (argv[i][j] == 'C') { + docheck++; + } else if (argv[i][j] == 'Q') { + quiet = 1; + } else if (argv[i][j] == 'W') { + nowarning = 1; + } else if (argv[i][j] == 'V') { + verbose++; + } else if (argv[i][j] == 'l') { + //refine_list = 1; //incrflip = 1; + } else if (argv[i][j] == 'x') { + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.') || (argv[i][j + 1] == 'e') || + (argv[i][j + 1] == '-') || (argv[i][j + 1] == '+')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + tetrahedraperblock = (int) strtol(workstring, (char **) NULL, 0); + if (tetrahedraperblock > 8188) { + vertexperblock = tetrahedraperblock / 2; + shellfaceperblock = vertexperblock / 2; + } else { + tetrahedraperblock = 8188; + } + } + } else if (argv[i][j] == 'H') { + if (argv[i+1][0] != '-') { + hole_mesh = 1; + // It is a filename following by -H + strncpy(hole_mesh_filename, argv[i+1], 1024 - 1); + hole_mesh_filename[1024 - 1] = '\0'; + i++; // Skip the next string. + break; // j + } + } else if ((argv[i][j] == 'h') || // (argv[i][j] == 'H') + (argv[i][j] == '?')) { + usage(); + } else { + printf("Warning: Unknown switch -%c.\n", argv[i][j]); + } + } + } + + if (startindex == 0) { + // Set a temporary filename for debugging output. + strcpy(infilename, "tetgen-tmpfile"); + } else { + if (infilename[0] == '\0') { + // No input file name. Print the syntax and exit. + syntax(); + terminatetetgen(NULL, 0); + } + // Recognize the object from file extension if it is available. + if (!strcmp(&infilename[strlen(infilename) - 5], ".node")) { + infilename[strlen(infilename) - 5] = '\0'; + object = NODES; + } else if (!strcmp(&infilename[strlen(infilename) - 5], ".poly")) { + infilename[strlen(infilename) - 5] = '\0'; + object = POLY; + plc = 1; + } else if (!strcmp(&infilename[strlen(infilename) - 6], ".smesh")) { + infilename[strlen(infilename) - 6] = '\0'; + object = POLY; + plc = 1; + } else if (!strcmp(&infilename[strlen(infilename) - 4], ".off")) { + infilename[strlen(infilename) - 4] = '\0'; + object = OFF; + plc = 1; + } else if (!strcmp(&infilename[strlen(infilename) - 4], ".ply")) { + infilename[strlen(infilename) - 4] = '\0'; + object = PLY; + plc = 1; + } else if (!strcmp(&infilename[strlen(infilename) - 4], ".stl")) { + infilename[strlen(infilename) - 4] = '\0'; + object = STL; + plc = 1; + } else if (!strcmp(&infilename[strlen(infilename) - 5], ".mesh")) { + infilename[strlen(infilename) - 5] = '\0'; + object = MEDIT; + if (!refine) plc = 1; + } else if (!strcmp(&infilename[strlen(infilename) - 4], ".vtk")) { + infilename[strlen(infilename) - 4] = '\0'; + object = VTK; + plc = 1; + } else if (!strcmp(&infilename[strlen(infilename) - 4], ".ele")) { + infilename[strlen(infilename) - 4] = '\0'; + object = MESH; + refine = 1; + } else if (!strcmp(&infilename[strlen(infilename) - 4], ".neu")) { + infilename[strlen(infilename) - 4] = '\0'; + object = NEU_MESH; + refine = 1; + } + } + + if (nobisect && (!plc && !refine)) { // -Y + plc = 1; // Default -p option. + } + if (quality && (!plc && !refine)) { // -q + plc = 1; // Default -p option. + } + if (diagnose && !plc) { // -d + plc = 1; + } + if (refine && !quality) { // -r only + // Reconstruct a mesh, no mesh optimization. + opt_max_flip_level = 0; + opt_iterations = 0; + } + if (insertaddpoints && (opt_max_flip_level == 0)) { // with -i option + opt_max_flip_level = 2; + } + if (coarsen && (opt_max_flip_level == 0)) { // with -R option + opt_max_flip_level = 2; + } + + // Detect improper combinations of switches. + if ((refine || plc) && weighted) { + printf("Error: Switches -w cannot use together with -p or -r.\n"); + return false; + } + + if (convex) { // -c + if (plc && !regionattrib) { + // -A (region attribute) is needed for marking exterior tets (-1). + regionattrib = 1; + } + } + + // Note: -A must not used together with -r option. + // Be careful not to add an extra attribute to each element unless the + // input supports it (PLC in, but not refining a preexisting mesh). + if (refine || !plc) { + regionattrib = 0; + } + // Be careful not to allocate space for element area constraints that + // will never be assigned any value (other than the default -1.0). + if (!refine && !plc) { + varvolume = 0; + } + // If '-a' or '-aa' is in use, enable '-q' option too. + if (fixedvolume || varvolume) { + if (quality == 0) { + quality = 1; + if (!plc && !refine) { + plc = 1; // enable -p. + } + } + } + if (!quality) { + // If no user-specified dihedral angle bound. Use default ones. + if (optmaxdihedral == 177.0) { // set by -o/# + optmaxdihedral = 179.9; + } + } + + if (quiet > 0) { + verbose = 0; // No printf output during the execution. + } + + increment = 0; + strcpy(workstring, infilename); + j = 1; + while (workstring[j] != '\0') { + if ((workstring[j] == '.') && (workstring[j + 1] != '\0')) { + increment = j + 1; + } + j++; + } + meshnumber = 0; + if (increment > 0) { + j = increment; + do { + if ((workstring[j] >= '0') && (workstring[j] <= '9')) { + meshnumber = meshnumber * 10 + (int) (workstring[j] - '0'); + } else { + increment = 0; + } + j++; + } while (workstring[j] != '\0'); + } + if (noiterationnum) { + strcpy(outfilename, infilename); + } else if (increment == 0) { + strcpy(outfilename, infilename); + strcat(outfilename, ".1"); + } else { + workstring[increment] = '%'; + workstring[increment + 1] = 'd'; + workstring[increment + 2] = '\0'; + sprintf(outfilename, workstring, meshnumber + 1); + } + // Additional input file name has the end ".a". + strcpy(addinfilename, infilename); + strcat(addinfilename, ".a"); + // Background filename has the form "*.b.ele", "*.b.node", ... + strcpy(bgmeshfilename, infilename); + strcat(bgmeshfilename, ".b"); + + return true; +} + +// // +// // +//== behavior_cxx ============================================================// + +//== mempool_cxx =============================================================// +// // +// // + +// Initialize fast lookup tables for mesh maniplulation primitives. + +int tetgenmesh::bondtbl[12][12] = {{0,},}; +int tetgenmesh::enexttbl[12] = {0,}; +int tetgenmesh::eprevtbl[12] = {0,}; +int tetgenmesh::enextesymtbl[12] = {0,}; +int tetgenmesh::eprevesymtbl[12] = {0,}; +int tetgenmesh::eorgoppotbl[12] = {0,}; +int tetgenmesh::edestoppotbl[12] = {0,}; +int tetgenmesh::fsymtbl[12][12] = {{0,},}; +int tetgenmesh::facepivot1[12] = {0,}; +int tetgenmesh::facepivot2[12][12] = {{0,},}; +int tetgenmesh::tsbondtbl[12][6] = {{0,},}; +int tetgenmesh::stbondtbl[12][6] = {{0,},}; +int tetgenmesh::tspivottbl[12][6] = {{0,},}; +int tetgenmesh::stpivottbl[12][6] = {{0,},}; + +// Table 'esymtbl' takes an directed edge (version) as input, returns the +// inversed edge (version) of it. + +int tetgenmesh::esymtbl[12] = {9, 6, 11, 4, 3, 7, 1, 5, 10, 0, 8, 2}; + +// The following four tables give the 12 permutations of the set {0,1,2,3}. +// An offset 4 is added to each element for a direct access of the points +// in the tetrahedron data structure. + +int tetgenmesh:: orgpivot[12] = {7, 7, 5, 5, 6, 4, 4, 6, 5, 6, 7, 4}; +int tetgenmesh::destpivot[12] = {6, 4, 4, 6, 5, 6, 7, 4, 7, 7, 5, 5}; +int tetgenmesh::apexpivot[12] = {5, 6, 7, 4, 7, 7, 5, 5, 6, 4, 4, 6}; +int tetgenmesh::oppopivot[12] = {4, 5, 6, 7, 4, 5, 6, 7, 4, 5, 6, 7}; + +// The twelve versions correspond to six undirected edges. The following two +// tables map a version to an undirected edge and vice versa. + +int tetgenmesh::ver2edge[12] = {0, 1, 2, 3, 3, 5, 1, 5, 4, 0, 4, 2}; +int tetgenmesh::edge2ver[ 6] = {0, 1, 2, 3, 8, 5}; + +// Edge versions whose apex or opposite may be dummypoint. + +int tetgenmesh::epivot[12] = {4, 5, 2, 11, 4, 5, 2, 11, 4, 5, 2, 11}; + + +// Table 'snextpivot' takes an edge version as input, returns the next edge +// version in the same edge ring. + +int tetgenmesh::snextpivot[6] = {2, 5, 4, 1, 0, 3}; + +// The following three tables give the 6 permutations of the set {0,1,2}. +// An offset 3 is added to each element for a direct access of the points +// in the triangle data structure. + +int tetgenmesh::sorgpivot [6] = {3, 4, 4, 5, 5, 3}; +int tetgenmesh::sdestpivot[6] = {4, 3, 5, 4, 3, 5}; +int tetgenmesh::sapexpivot[6] = {5, 5, 3, 3, 4, 4}; + +//============================================================================// +// // +// inittable() Initialize the look-up tables. // +// // +//============================================================================// + +void tetgenmesh::inittables() +{ + int soffset, toffset; + int i, j; + + + // i = t1.ver; j = t2.ver; + for (i = 0; i < 12; i++) { + for (j = 0; j < 12; j++) { + bondtbl[i][j] = (j & 3) + (((i & 12) + (j & 12)) % 12); + } + } + + + // i = t1.ver; j = t2.ver + for (i = 0; i < 12; i++) { + for (j = 0; j < 12; j++) { + fsymtbl[i][j] = (j + 12 - (i & 12)) % 12; + } + } + + + for (i = 0; i < 12; i++) { + facepivot1[i] = (esymtbl[i] & 3); + } + + for (i = 0; i < 12; i++) { + for (j = 0; j < 12; j++) { + facepivot2[i][j] = fsymtbl[esymtbl[i]][j]; + } + } + + for (i = 0; i < 12; i++) { + enexttbl[i] = (i + 4) % 12; + eprevtbl[i] = (i + 8) % 12; + } + + for (i = 0; i < 12; i++) { + enextesymtbl[i] = esymtbl[enexttbl[i]]; + eprevesymtbl[i] = esymtbl[eprevtbl[i]]; + } + + for (i = 0; i < 12; i++) { + eorgoppotbl [i] = eprevtbl[esymtbl[enexttbl[i]]]; + edestoppotbl[i] = enexttbl[esymtbl[eprevtbl[i]]]; + } + + + // i = t.ver, j = s.shver + for (i = 0; i < 12; i++) { + for (j = 0; j < 6; j++) { + if ((j & 1) == 0) { + soffset = (6 - ((i & 12) >> 1)) % 6; + toffset = (12 - ((j & 6) << 1)) % 12; + } else { + soffset = (i & 12) >> 1; + toffset = (j & 6) << 1; + } + tsbondtbl[i][j] = (j & 1) + (((j & 6) + soffset) % 6); + stbondtbl[i][j] = (i & 3) + (((i & 12) + toffset) % 12); + } + } + + + // i = t.ver, j = s.shver + for (i = 0; i < 12; i++) { + for (j = 0; j < 6; j++) { + if ((j & 1) == 0) { + soffset = (i & 12) >> 1; + toffset = (j & 6) << 1; + } else { + soffset = (6 - ((i & 12) >> 1)) % 6; + toffset = (12 - ((j & 6) << 1)) % 12; + } + tspivottbl[i][j] = (j & 1) + (((j & 6) + soffset) % 6); + stpivottbl[i][j] = (i & 3) + (((i & 12) + toffset) % 12); + } + } +} + + +//============================================================================// +// // +// restart() Deallocate all objects in this pool. // +// // +// The pool returns to a fresh state, like after it was initialized, except // +// that no memory is freed to the operating system. Rather, the previously // +// allocated blocks are ready to be used. // +// // +//============================================================================// + +void tetgenmesh::arraypool::restart() +{ + objects = 0l; +} + +//============================================================================// +// // +// poolinit() Initialize an arraypool for allocation of objects. // +// // +// Before the pool may be used, it must be initialized by this procedure. // +// After initialization, memory can be allocated and freed in this pool. // +// // +//============================================================================// + +void tetgenmesh::arraypool::poolinit(int sizeofobject, int log2objperblk) +{ + // Each object must be at least one byte long. + objectbytes = sizeofobject > 1 ? sizeofobject : 1; + + log2objectsperblock = log2objperblk; + // Compute the number of objects in each block. + objectsperblock = ((int) 1) << log2objectsperblock; + objectsperblockmark = objectsperblock - 1; + + // No memory has been allocated. + totalmemory = 0l; + // The top array has not been allocated yet. + toparray = (char **) NULL; + toparraylen = 0; + + // Ready all indices to be allocated. + restart(); +} + +//============================================================================// +// // +// arraypool() The constructor and destructor. // +// // +//============================================================================// + +tetgenmesh::arraypool::arraypool(int sizeofobject, int log2objperblk) +{ + poolinit(sizeofobject, log2objperblk); +} + +tetgenmesh::arraypool::~arraypool() +{ + int i; + + // Has anything been allocated at all? + if (toparray != (char **) NULL) { + // Walk through the top array. + for (i = 0; i < toparraylen; i++) { + // Check every pointer; NULLs may be scattered randomly. + if (toparray[i] != (char *) NULL) { + // Free an allocated block. + free((void *) toparray[i]); + } + } + // Free the top array. + free((void *) toparray); + } + + // The top array is no longer allocated. + toparray = (char **) NULL; + toparraylen = 0; + objects = 0; + totalmemory = 0; +} + +//============================================================================// +// // +// getblock() Return (and perhaps create) the block containing the object // +// with a given index. // +// // +// This function takes care of allocating or resizing the top array if nece- // +// ssary, and of allocating the block if it hasn't yet been allocated. // +// // +// Return a pointer to the beginning of the block (NOT the object). // +// // +//============================================================================// + +char* tetgenmesh::arraypool::getblock(int objectindex) +{ + char **newarray; + char *block; + int newsize; + int topindex; + int i; + + // Compute the index in the top array (upper bits). + topindex = objectindex >> log2objectsperblock; + // Does the top array need to be allocated or resized? + if (toparray == (char **) NULL) { + // Allocate the top array big enough to hold 'topindex', and NULL out + // its contents. + newsize = topindex + 128; + toparray = (char **) malloc((size_t) (newsize * sizeof(char *))); + toparraylen = newsize; + for (i = 0; i < newsize; i++) { + toparray[i] = (char *) NULL; + } + // Account for the memory. + totalmemory = newsize * (uintptr_t) sizeof(char *); + } else if (topindex >= toparraylen) { + // Resize the top array, making sure it holds 'topindex'. + newsize = 3 * toparraylen; + if (topindex >= newsize) { + newsize = topindex + 128; + } + // Allocate the new array, copy the contents, NULL out the rest, and + // free the old array. + newarray = (char **) malloc((size_t) (newsize * sizeof(char *))); + for (i = 0; i < toparraylen; i++) { + newarray[i] = toparray[i]; + } + for (i = toparraylen; i < newsize; i++) { + newarray[i] = (char *) NULL; + } + free(toparray); + // Account for the memory. + totalmemory += (newsize - toparraylen) * sizeof(char *); + toparray = newarray; + toparraylen = newsize; + } + + // Find the block, or learn that it hasn't been allocated yet. + block = toparray[topindex]; + if (block == (char *) NULL) { + // Allocate a block at this index. + block = (char *) malloc((size_t) (objectsperblock * objectbytes)); + toparray[topindex] = block; + // Account for the memory. + totalmemory += objectsperblock * objectbytes; + } + + // Return a pointer to the block. + return block; +} + +//============================================================================// +// // +// lookup() Return the pointer to the object with a given index, or NULL // +// if the object's block doesn't exist yet. // +// // +//============================================================================// + +void* tetgenmesh::arraypool::lookup(int objectindex) +{ + char *block; + int topindex; + + // Has the top array been allocated yet? + if (toparray == (char **) NULL) { + return (void *) NULL; + } + + // Compute the index in the top array (upper bits). + topindex = objectindex >> log2objectsperblock; + // Does the top index fit in the top array? + if (topindex >= toparraylen) { + return (void *) NULL; + } + + // Find the block, or learn that it hasn't been allocated yet. + block = toparray[topindex]; + if (block == (char *) NULL) { + return (void *) NULL; + } + + // Compute a pointer to the object with the given index. Note that + // 'objectsperblock' is a power of two, so the & operation is a bit mask + // that preserves the lower bits. + return (void *)(block + (objectindex & (objectsperblock - 1)) * objectbytes); +} + +//============================================================================// +// // +// newindex() Allocate space for a fresh object from the pool. // +// // +// 'newptr' returns a pointer to the new object (it must not be a NULL). // +// // +//============================================================================// + +int tetgenmesh::arraypool::newindex(void **newptr) +{ + // Allocate an object at index 'firstvirgin'. + int newindex = objects; + *newptr = (void *) (getblock(objects) + + (objects & (objectsperblock - 1)) * objectbytes); + objects++; + + return newindex; +} + + +/////////////////////////////////////////////////////////////////////////////// +// // +// memorypool() The constructors of memorypool. // +// // +/////////////////////////////////////////////////////////////////////////////// + +tetgenmesh::memorypool::memorypool() +{ + firstblock = nowblock = (void **) NULL; + nextitem = (void *) NULL; + deaditemstack = (void *) NULL; + pathblock = (void **) NULL; + pathitem = (void *) NULL; + alignbytes = 0; + itembytes = itemwords = 0; + itemsperblock = 0; + items = maxitems = 0l; + unallocateditems = 0; + pathitemsleft = 0; +} + +tetgenmesh::memorypool::memorypool(int bytecount, int itemcount, int wsize, + int alignment) +{ + poolinit(bytecount, itemcount, wsize, alignment); +} + +//============================================================================// +// // +// ~memorypool() Free to the operating system all memory taken by a pool. // +// // +//============================================================================// + +tetgenmesh::memorypool::~memorypool() +{ + while (firstblock != (void **) NULL) { + nowblock = (void **) *(firstblock); + free(firstblock); + firstblock = nowblock; + } +} + +//============================================================================// +// // +// poolinit() Initialize a pool of memory for allocation of items. // +// // +// A `pool' is created whose records have size at least `bytecount'. Items // +// will be allocated in `itemcount'-item blocks. Each item is assumed to be // +// a collection of words, and either pointers or floating-point values are // +// assumed to be the "primary" word type. (The "primary" word type is used // +// to determine alignment of items.) If `alignment' isn't zero, all items // +// will be `alignment'-byte aligned in memory. `alignment' must be either a // +// multiple or a factor of the primary word size; powers of two are safe. // +// `alignment' is normally used to create a few unused bits at the bottom of // +// each item's pointer, in which information may be stored. // +// // +//============================================================================// + +void tetgenmesh::memorypool::poolinit(int bytecount,int itemcount,int wordsize, + int alignment) +{ + // Find the proper alignment, which must be at least as large as: + // - The parameter `alignment'. + // - The primary word type, to avoid unaligned accesses. + // - sizeof(void *), so the stack of dead items can be maintained + // without unaligned accesses. + if (alignment > wordsize) { + alignbytes = alignment; + } else { + alignbytes = wordsize; + } + if ((int) sizeof(void *) > alignbytes) { + alignbytes = (int) sizeof(void *); + } + itemwords = ((bytecount + alignbytes - 1) / alignbytes) + * (alignbytes / wordsize); + itembytes = itemwords * wordsize; + itemsperblock = itemcount; + + // Allocate a block of items. Space for `itemsperblock' items and one + // pointer (to point to the next block) are allocated, as well as space + // to ensure alignment of the items. + firstblock = (void **) malloc(itemsperblock * itembytes + sizeof(void *) + + alignbytes); + if (firstblock == (void **) NULL) { + terminatetetgen(NULL, 1); + } + // Set the next block pointer to NULL. + *(firstblock) = (void *) NULL; + restart(); +} + +//============================================================================// +// // +// restart() Deallocate all items in this pool. // +// // +// The pool is returned to its starting state, except that no memory is // +// freed to the operating system. Rather, the previously allocated blocks // +// are ready to be reused. // +// // +//============================================================================// + +void tetgenmesh::memorypool::restart() +{ + uintptr_t alignptr; + + items = 0; + maxitems = 0; + + // Set the currently active block. + nowblock = firstblock; + // Find the first item in the pool. Increment by the size of (void *). + alignptr = (uintptr_t) (nowblock + 1); + // Align the item on an `alignbytes'-byte boundary. + nextitem = (void *) + (alignptr + (uintptr_t) alignbytes - + (alignptr % (uintptr_t) alignbytes)); + // There are lots of unallocated items left in this block. + unallocateditems = itemsperblock; + // The stack of deallocated items is empty. + deaditemstack = (void *) NULL; +} + +//============================================================================// +// // +// alloc() Allocate space for an item. // +// // +//============================================================================// + +void* tetgenmesh::memorypool::alloc() +{ + void *newitem; + void **newblock; + uintptr_t alignptr; + + // First check the linked list of dead items. If the list is not + // empty, allocate an item from the list rather than a fresh one. + if (deaditemstack != (void *) NULL) { + newitem = deaditemstack; // Take first item in list. + deaditemstack = * (void **) deaditemstack; + } else { + // Check if there are any free items left in the current block. + if (unallocateditems == 0) { + // Check if another block must be allocated. + if (*nowblock == (void *) NULL) { + // Allocate a new block of items, pointed to by the previous block. + newblock = (void **) malloc(itemsperblock * itembytes + sizeof(void *) + + alignbytes); + if (newblock == (void **) NULL) { + terminatetetgen(NULL, 1); + } + *nowblock = (void *) newblock; + // The next block pointer is NULL. + *newblock = (void *) NULL; + } + // Move to the new block. + nowblock = (void **) *nowblock; + // Find the first item in the block. + // Increment by the size of (void *). + alignptr = (uintptr_t) (nowblock + 1); + // Align the item on an `alignbytes'-byte boundary. + nextitem = (void *) + (alignptr + (uintptr_t) alignbytes - + (alignptr % (uintptr_t) alignbytes)); + // There are lots of unallocated items left in this block. + unallocateditems = itemsperblock; + } + // Allocate a new item. + newitem = nextitem; + // Advance `nextitem' pointer to next free item in block. + nextitem = (void *) ((uintptr_t) nextitem + itembytes); + unallocateditems--; + maxitems++; + } + items++; + return newitem; +} + +//============================================================================// +// // +// dealloc() Deallocate space for an item. // +// // +// The deallocated space is stored in a queue for later reuse. // +// // +//============================================================================// + +void tetgenmesh::memorypool::dealloc(void *dyingitem) +{ + // Push freshly killed item onto stack. + *((void **) dyingitem) = deaditemstack; + deaditemstack = dyingitem; + items--; +} + +//============================================================================// +// // +// traversalinit() Prepare to traverse the entire list of items. // +// // +// This routine is used in conjunction with traverse(). // +// // +//============================================================================// + +void tetgenmesh::memorypool::traversalinit() +{ + uintptr_t alignptr; + + // Begin the traversal in the first block. + pathblock = firstblock; + // Find the first item in the block. Increment by the size of (void *). + alignptr = (uintptr_t) (pathblock + 1); + // Align with item on an `alignbytes'-byte boundary. + pathitem = (void *) + (alignptr + (uintptr_t) alignbytes - + (alignptr % (uintptr_t) alignbytes)); + // Set the number of items left in the current block. + pathitemsleft = itemsperblock; +} + +//============================================================================// +// // +// traverse() Find the next item in the list. // +// // +// This routine is used in conjunction with traversalinit(). Be forewarned // +// that this routine successively returns all items in the list, including // +// deallocated ones on the deaditemqueue. It's up to you to figure out which // +// ones are actually dead. It can usually be done more space-efficiently by // +// a routine that knows something about the structure of the item. // +// // +//============================================================================// + +void* tetgenmesh::memorypool::traverse() +{ + void *newitem; + uintptr_t alignptr; + + // Stop upon exhausting the list of items. + if (pathitem == nextitem) { + return (void *) NULL; + } + // Check whether any untraversed items remain in the current block. + if (pathitemsleft == 0) { + // Find the next block. + pathblock = (void **) *pathblock; + // Find the first item in the block. Increment by the size of (void *). + alignptr = (uintptr_t) (pathblock + 1); + // Align with item on an `alignbytes'-byte boundary. + pathitem = (void *) + (alignptr + (uintptr_t) alignbytes - + (alignptr % (uintptr_t) alignbytes)); + // Set the number of items left in the current block. + pathitemsleft = itemsperblock; + } + newitem = pathitem; + // Find the next item in the block. + pathitem = (void *) ((uintptr_t) pathitem + itembytes); + pathitemsleft--; + return newitem; +} + +//============================================================================// +// // +// makeindex2pointmap() Create a map from index to vertices. // +// // +// 'idx2verlist' returns the created map. Traverse all vertices, a pointer // +// to each vertex is set into the array. The pointer to the first vertex is // +// saved in 'idx2verlist[in->firstnumber]'. // +// // +//============================================================================// + +void tetgenmesh::makeindex2pointmap(point*& idx2verlist) +{ + point pointloop; + int idx; + + if (b->verbose > 1) { + printf(" Constructing mapping from indices to points.\n"); + } + + idx2verlist = new point[points->items + 1]; + + points->traversalinit(); + pointloop = pointtraverse(); + idx = in->firstnumber; + while (pointloop != (point) NULL) { + idx2verlist[idx++] = pointloop; + pointloop = pointtraverse(); + } +} + +//============================================================================// +// // +// makesubfacemap() Create a map from vertex to subfaces incident at it. // +// // +// The map is returned in two arrays 'idx2faclist' and 'facperverlist'. All // +// subfaces incident at i-th vertex (i is counted from 0) are found in the // +// array facperverlist[j], where idx2faclist[i] <= j < idx2faclist[i + 1]. // +// Each entry in facperverlist[j] is a subface whose origin is the vertex. // +// // +// NOTE: These two arrays will be created inside this routine, don't forget // +// to free them after using. // +// // +//============================================================================// + +void tetgenmesh::makepoint2submap(memorypool* pool, int*& idx2faclist, + face*& facperverlist) +{ + face shloop; + int i, j, k; + + if (b->verbose > 1) { + printf(" Making a map from points to subfaces.\n"); + } + + // Initialize 'idx2faclist'. + idx2faclist = new int[points->items + 1]; + for (i = 0; i < points->items + 1; i++) idx2faclist[i] = 0; + + // Loop all subfaces, counter the number of subfaces incident at a vertex. + pool->traversalinit(); + shloop.sh = shellfacetraverse(pool); + while (shloop.sh != (shellface *) NULL) { + // Increment the number of incident subfaces for each vertex. + j = pointmark((point) shloop.sh[3]) - in->firstnumber; + idx2faclist[j]++; + j = pointmark((point) shloop.sh[4]) - in->firstnumber; + idx2faclist[j]++; + // Skip the third corner if it is a segment. + if (shloop.sh[5] != NULL) { + j = pointmark((point) shloop.sh[5]) - in->firstnumber; + idx2faclist[j]++; + } + shloop.sh = shellfacetraverse(pool); + } + + // Calculate the total length of array 'facperverlist'. + j = idx2faclist[0]; + idx2faclist[0] = 0; // Array starts from 0 element. + for (i = 0; i < points->items; i++) { + k = idx2faclist[i + 1]; + idx2faclist[i + 1] = idx2faclist[i] + j; + j = k; + } + + // The total length is in the last unit of idx2faclist. + facperverlist = new face[idx2faclist[i]]; + + // Loop all subfaces again, remember the subfaces at each vertex. + pool->traversalinit(); + shloop.sh = shellfacetraverse(pool); + while (shloop.sh != (shellface *) NULL) { + j = pointmark((point) shloop.sh[3]) - in->firstnumber; + shloop.shver = 0; // save the origin. + facperverlist[idx2faclist[j]] = shloop; + idx2faclist[j]++; + // Is it a subface or a subsegment? + if (shloop.sh[5] != NULL) { + j = pointmark((point) shloop.sh[4]) - in->firstnumber; + shloop.shver = 2; // save the origin. + facperverlist[idx2faclist[j]] = shloop; + idx2faclist[j]++; + j = pointmark((point) shloop.sh[5]) - in->firstnumber; + shloop.shver = 4; // save the origin. + facperverlist[idx2faclist[j]] = shloop; + idx2faclist[j]++; + } else { + j = pointmark((point) shloop.sh[4]) - in->firstnumber; + shloop.shver = 1; // save the origin. + facperverlist[idx2faclist[j]] = shloop; + idx2faclist[j]++; + } + shloop.sh = shellfacetraverse(pool); + } + + // Contents in 'idx2faclist' are shifted, now shift them back. + for (i = points->items - 1; i >= 0; i--) { + idx2faclist[i + 1] = idx2faclist[i]; + } + idx2faclist[0] = 0; +} + +//============================================================================// +// // +// tetrahedrondealloc() Deallocate space for a tet., marking it dead. // +// // +//============================================================================// + +void tetgenmesh::tetrahedrondealloc(tetrahedron *dyingtetrahedron) +{ + // Set tetrahedron's vertices to NULL. This makes it possible to detect + // dead tetrahedra when traversing the list of all tetrahedra. + dyingtetrahedron[4] = (tetrahedron) NULL; + + // Dealloc the space to subfaces/subsegments. + if (dyingtetrahedron[8] != NULL) { + tet2segpool->dealloc((shellface *) dyingtetrahedron[8]); + } + if (dyingtetrahedron[9] != NULL) { + tet2subpool->dealloc((shellface *) dyingtetrahedron[9]); + } + + tetrahedrons->dealloc((void *) dyingtetrahedron); +} + +//============================================================================// +// // +// tetrahedrontraverse() Traverse the tetrahedra, skipping dead ones. // +// // +//============================================================================// + +tetgenmesh::tetrahedron* tetgenmesh::tetrahedrontraverse() +{ + tetrahedron *newtetrahedron; + + do { + newtetrahedron = (tetrahedron *) tetrahedrons->traverse(); + if (newtetrahedron == (tetrahedron *) NULL) { + return (tetrahedron *) NULL; + } + } while ((newtetrahedron[4] == (tetrahedron) NULL) || + ((point) newtetrahedron[7] == dummypoint)); + return newtetrahedron; +} + +tetgenmesh::tetrahedron* tetgenmesh::alltetrahedrontraverse() +{ + tetrahedron *newtetrahedron; + + do { + newtetrahedron = (tetrahedron *) tetrahedrons->traverse(); + if (newtetrahedron == (tetrahedron *) NULL) { + return (tetrahedron *) NULL; + } + } while (newtetrahedron[4] == (tetrahedron) NULL); // Skip dead ones. + return newtetrahedron; +} + +//============================================================================// +// // +// shellfacedealloc() Deallocate space for a shellface, marking it dead. // +// Used both for dealloc a subface and subsegment. // +// // +//============================================================================// + +void tetgenmesh::shellfacedealloc(memorypool *pool, shellface *dyingsh) +{ + // Set shellface's vertices to NULL. This makes it possible to detect dead + // shellfaces when traversing the list of all shellfaces. + dyingsh[3] = (shellface) NULL; + pool->dealloc((void *) dyingsh); +} + +//============================================================================// +// // +// shellfacetraverse() Traverse the subfaces, skipping dead ones. Used // +// for both subfaces and subsegments pool traverse. // +// // +//============================================================================// + +tetgenmesh::shellface* tetgenmesh::shellfacetraverse(memorypool *pool) +{ + shellface *newshellface; + + do { + newshellface = (shellface *) pool->traverse(); + if (newshellface == (shellface *) NULL) { + return (shellface *) NULL; + } + } while (newshellface[3] == (shellface) NULL); // Skip dead ones. + return newshellface; +} + + +//============================================================================// +// // +// pointdealloc() Deallocate space for a point, marking it dead. // +// // +//============================================================================// + +void tetgenmesh::pointdealloc(point dyingpoint) +{ + // Mark the point as dead. This makes it possible to detect dead points + // when traversing the list of all points. + setpointtype(dyingpoint, DEADVERTEX); + points->dealloc((void *) dyingpoint); +} + +//============================================================================// +// // +// pointtraverse() Traverse the points, skipping dead ones. // +// // +//============================================================================// + +tetgenmesh::point tetgenmesh::pointtraverse() +{ + point newpoint; + + do { + newpoint = (point) points->traverse(); + if (newpoint == (point) NULL) { + return (point) NULL; + } + } while (pointtype(newpoint) == DEADVERTEX); // Skip dead ones. + return newpoint; +} + +//============================================================================// +// // +// maketetrahedron() Create a new tetrahedron. // +// // +//============================================================================// + +void tetgenmesh::maketetrahedron(triface *newtet) +{ + newtet->tet = (tetrahedron *) tetrahedrons->alloc(); + + // Initialize the four adjoining tetrahedra to be "outer space". + newtet->tet[0] = NULL; + newtet->tet[1] = NULL; + newtet->tet[2] = NULL; + newtet->tet[3] = NULL; + // Four NULL vertices. + newtet->tet[4] = NULL; + newtet->tet[5] = NULL; + newtet->tet[6] = NULL; + newtet->tet[7] = NULL; + // No attached segments and subfaces yet. + newtet->tet[8] = NULL; + newtet->tet[9] = NULL; + + newtet->tet[10] = NULL; // used by mesh improvement + + // Init the volume to be zero. + //REAL *polar = get_polar(newtet->tet); + //polar[4] = 0.0; + // Initialize the marker (clear all flags). + setelemmarker(newtet->tet, 0); + for (int i = 0; i < numelemattrib; i++) { + setelemattribute(newtet->tet, i, 0.0); + } + if (b->varvolume) { + setvolumebound(newtet->tet, -1.0); + } + + // Initialize the version to be Zero. + newtet->ver = 11; +} + +void tetgenmesh::maketetrahedron2(triface* newtet, point pa, point pb, + point pc, point pd) +{ + newtet->tet = (tetrahedron *) tetrahedrons->alloc(); + + // Initialize the four adjoining tetrahedra to be "outer space". + newtet->tet[0] = NULL; + newtet->tet[1] = NULL; + newtet->tet[2] = NULL; + newtet->tet[3] = NULL; + // Set four vertices. + newtet->tet[4] = (tetrahedron) pa; + newtet->tet[5] = (tetrahedron) pb; + newtet->tet[6] = (tetrahedron) pc; + newtet->tet[7] = (tetrahedron) pd; // may be dummypoint + // No attached segments and subfaces yet. + newtet->tet[8] = NULL; + newtet->tet[9] = NULL; + + newtet->tet[10] = NULL; // used by mesh improvement + + + // Initialize the marker (clear all flags). + setelemmarker(newtet->tet, 0); + for (int i = 0; i < numelemattrib; i++) { + setelemattribute(newtet->tet, i, 0.0); + } + if (b->varvolume) { + setvolumebound(newtet->tet, -1.0); + } + + // Initialize the version to be Zero. + newtet->ver = 11; +} + +//============================================================================// +// // +// makeshellface() Create a new shellface with version zero. Used for // +// both subfaces and subsegments. // +// // +//============================================================================// + +void tetgenmesh::makeshellface(memorypool *pool, face *newface) +{ + newface->sh = (shellface *) pool->alloc(); + + // No adjointing subfaces. + newface->sh[0] = NULL; + newface->sh[1] = NULL; + newface->sh[2] = NULL; + // Three NULL vertices. + newface->sh[3] = NULL; + newface->sh[4] = NULL; + newface->sh[5] = NULL; + // No adjoining subsegments. + newface->sh[6] = NULL; + newface->sh[7] = NULL; + newface->sh[8] = NULL; + // No adjoining tetrahedra. + newface->sh[9] = NULL; + newface->sh[10] = NULL; + if (checkconstraints) { + // Initialize the maximum area bound. + setareabound(*newface, 0.0); + } + // Set the boundary marker to zero. + setshellmark(*newface, 0); + // Clear the infection and marktest bits. + ((int *) (newface->sh))[shmarkindex + 1] = 0; + if (useinsertradius) { + setfacetindex(*newface, 0); + } + + newface->shver = 0; +} + +//============================================================================// +// // +// makepoint() Create a new point. // +// // +//============================================================================// + +void tetgenmesh::makepoint(point* pnewpoint, enum verttype vtype) +{ + int i; + + *pnewpoint = (point) points->alloc(); + + // Initialize the point attributes. + for (i = 0; i < numpointattrib; i++) { + (*pnewpoint)[3 + i] = 0.0; + } + // Initialize the metric tensor. + for (i = 0; i < sizeoftensor; i++) { + (*pnewpoint)[pointmtrindex + i] = 0.0; + } + setpoint2tet(*pnewpoint, NULL); + setpoint2ppt(*pnewpoint, NULL); + if (b->plc || b->refine) { + // Initialize the point-to-simplex field. + setpoint2sh(*pnewpoint, NULL); + if (b->metric && (bgm != NULL)) { + setpoint2bgmtet(*pnewpoint, NULL); + } + } + // Initialize the point marker (starting from in->firstnumber). + setpointmark(*pnewpoint, (int) (points->items) - (!in->firstnumber)); + // Clear all flags. + ((int *) (*pnewpoint))[pointmarkindex + 1] = 0; + // Initialize (set) the point type. + setpointtype(*pnewpoint, vtype); +} + +//============================================================================// +// // +// initializepools() Calculate the sizes of the point, tetrahedron, and // +// subface. Initialize their memory pools. // +// // +// This routine also computes the indices 'pointmarkindex', 'point2simindex', // +// 'point2pbcptindex', 'elemattribindex', and 'volumeboundindex'. They are // +// used to find values within each point and tetrahedron, respectively. // +// // +//============================================================================// + +void tetgenmesh::initializepools() +{ + int pointsize = 0, elesize = 0, shsize = 0; + int i; + + if (b->verbose) { + printf(" Initializing memorypools.\n"); + printf(" tetrahedron per block: %d.\n", b->tetrahedraperblock); + } + + inittables(); + + // There are three input point lists available, which are in, addin, + // and bgm->in. These point lists may have different number of + // attributes. Decide the maximum number. + numpointattrib = in->numberofpointattributes; + if (bgm != NULL) { + if (bgm->in->numberofpointattributes > numpointattrib) { + numpointattrib = bgm->in->numberofpointattributes; + } + } + if (addin != NULL) { + if (addin->numberofpointattributes > numpointattrib) { + numpointattrib = addin->numberofpointattributes; + } + } + if (b->weighted || b->flipinsert) { // -w or -L. + // The internal number of point attribute needs to be at least 1 + // (for storing point weights). + if (numpointattrib == 0) { + numpointattrib = 1; + } + } + + // Default varconstraint = 0; + if (in->segmentconstraintlist || in->facetconstraintlist) { + checkconstraints = 1; + } + if (b->plc || b->refine || b->quality) { + // Save the insertion radius for Steiner points if boundaries + // are allowed be split. + //if (!b->nobisect || checkconstraints) { + useinsertradius = 1; + //} + } + + // The index within each point at which its metric tensor is found. + // Each vertex has three coordinates. + if (b->psc) { + // '-s' option (PSC), the u,v coordinates are provided. + pointmtrindex = 5 + numpointattrib; + // The index within each point at which its u, v coordinates are found. + // Comment: They are saved after the list of point attributes. + pointparamindex = pointmtrindex - 2; + } else { + pointmtrindex = 3 + numpointattrib; + } + // For '-m' option. A tensor field is provided (*.mtr or *.b.mtr file). + if (b->metric) { + // Decide the size (1, 3, or 6) of the metric tensor. + if (bgm != (tetgenmesh *) NULL) { + // A background mesh is allocated. It may not exist though. + sizeoftensor = (bgm->in != (tetgenio *) NULL) ? + bgm->in->numberofpointmtrs : in->numberofpointmtrs; + } else { + // No given background mesh - Itself is a background mesh. + sizeoftensor = in->numberofpointmtrs; + } + // Make sure sizeoftensor is at least 1. + sizeoftensor = (sizeoftensor > 0) ? sizeoftensor : 1; + } else { + // For '-q' option. Make sure to have space for saving a scalar value. + sizeoftensor = b->quality ? 1 : 0; + } + if (useinsertradius) { + // Increase a space (REAL) for saving point insertion radius, it is + // saved directly after the metric. + sizeoftensor++; + } + pointinsradiusindex = pointmtrindex + sizeoftensor - 1; + // The index within each point at which an element pointer is found, where + // the index is measured in pointers. Ensure the index is aligned to a + // sizeof(tetrahedron)-byte address. + point2simindex = ((pointmtrindex + sizeoftensor) * sizeof(REAL) + + sizeof(tetrahedron) - 1) / sizeof(tetrahedron); + if (b->plc || b->refine /*|| b->voroout*/) { + // Increase the point size by three pointers, which are: + // - a pointer to a tet, read by point2tet(); + // - a pointer to a parent point, read by point2ppt()). + // - a pointer to a subface or segment, read by point2sh(); + if (b->metric && (bgm != (tetgenmesh *) NULL)) { + // Increase one pointer into the background mesh, point2bgmtet(). + pointsize = (point2simindex + 4) * sizeof(tetrahedron); + } else { + pointsize = (point2simindex + 3) * sizeof(tetrahedron); + } + } else { + // Increase the point size by two pointer, which are: + // - a pointer to a tet, read by point2tet(); + // - a pointer to a parent point, read by point2ppt()). -- Used by btree. + pointsize = (point2simindex + 2) * sizeof(tetrahedron); + } + // The index within each point at which the boundary marker is found, + // Ensure the point marker is aligned to a sizeof(int)-byte address. + pointmarkindex = (pointsize + sizeof(int) - 1) / sizeof(int); + // Now point size is the ints (indicated by pointmarkindex) plus: + // - an integer for boundary marker; + // - an integer for vertex type; + // - an integer for local index (for vertex insertion) + pointsize = (pointmarkindex + 3) * sizeof(tetrahedron); + + // Initialize the pool of vertices. + points = new memorypool(pointsize, b->vertexperblock, sizeof(REAL), 0); + + if (b->verbose) { + printf(" Size of a point: %d bytes.\n", points->itembytes); + } + + // Initialize the infinite vertex. + dummypoint = (point) new char[pointsize]; + // Initialize all fields of this point. + dummypoint[0] = 0.0; + dummypoint[1] = 0.0; + dummypoint[2] = 0.0; + for (i = 0; i < numpointattrib; i++) { + dummypoint[3 + i] = 0.0; + } + // Initialize the metric tensor. + for (i = 0; i < sizeoftensor; i++) { + dummypoint[pointmtrindex + i] = 0.0; + } + setpoint2tet(dummypoint, NULL); + setpoint2ppt(dummypoint, NULL); + if (b->plc || b->psc || b->refine) { + // Initialize the point-to-simplex field. + setpoint2sh(dummypoint, NULL); + if (b->metric && (bgm != NULL)) { + setpoint2bgmtet(dummypoint, NULL); + } + } + // Initialize the point marker (starting from in->firstnumber). + setpointmark(dummypoint, -1); // The unique marker for dummypoint. + // Clear all flags. + ((int *) (dummypoint))[pointmarkindex + 1] = 0; + // Initialize (set) the point type. + setpointtype(dummypoint, UNUSEDVERTEX); // Does not matter. + + // The number of bytes occupied by a tetrahedron is varying by the user- + // specified options. The contents of the first 12 pointers are listed + // in the following table: + // [0] |__ neighbor at f0 __| + // [1] |__ neighbor at f1 __| + // [2] |__ neighbor at f2 __| + // [3] |__ neighbor at f3 __| + // [4] |_____ vertex p0 ____| + // [5] |_____ vertex p1 ____| + // [6] |_____ vertex p2 ____| + // [7] |_____ vertex p3 ____| + // [8] |__ segments array __| (used by -p) + // [9] |__ subfaces array __| (used by -p) + // [10] |_____ reserved _____| + // [11] |___ elem marker ____| (used as an integer) + + elesize = 12 * sizeof(tetrahedron); + + // The index to find the element markers. An integer containing varies + // flags and element counter. + if (!(sizeof(int) <= sizeof(tetrahedron)) || + ((sizeof(tetrahedron) % sizeof(int)))) { + terminatetetgen(this, 2); + } + elemmarkerindex = (elesize - sizeof(tetrahedron)) / sizeof(int); + + // Let (cx, cy, cz) be the circumcenter of this element, r be the radius + // of the circumsphere, and V be the (positive) volume of this element. + // We save the following five values: + // 2*cx, 2*cy, 2*cz, cx^2 + cy^2 + cz^2 - r^2 (height), 6*v. + // where the first four values define the polar plane of this element, + // the fifth value should be postive. It is used to guard the correctness + // of the polar plane. Otherwise, use exact arithmetics to calculate. + + // The index within each element which its polar parameters are found, + // this index is measured in REALs. + polarindex = (elesize + sizeof(REAL) - 1) / sizeof(REAL); + // The index within each element at which its attributes are found, where + // the index is measured in REALs. + //elemattribindex = (elesize + sizeof(REAL) - 1) / sizeof(REAL); + elemattribindex = polarindex; // polarindex + 5; + // The actual number of element attributes. Note that if the + // `b->regionattrib' flag is set, an additional attribute will be added. + numelemattrib = in->numberoftetrahedronattributes + (b->regionattrib > 0); + // The index within each element at which the maximum volume bound is + // found, where the index is measured in REALs. + volumeboundindex = elemattribindex + numelemattrib; + // If element attributes or an constraint are needed, increase the number + // of bytes occupied by an element. + if (!b->varvolume) { + if (b->refine && (in->refine_elem_list != NULL)) { + b->varvolume = 1; // refine a given element list. + } + } + if (b->varvolume) { + elesize = (volumeboundindex + 1) * sizeof(REAL); + } else { + elesize = volumeboundindex * sizeof(REAL); + } + + + // Having determined the memory size of an element, initialize the pool. + tetrahedrons = new memorypool(elesize, b->tetrahedraperblock, sizeof(void *), + 16); + + if (b->verbose) { + printf(" Size of a tetrahedron: %d (%d) bytes.\n", elesize, + tetrahedrons->itembytes); + } + + if (b->plc || b->refine) { // if (b->useshelles) { + // The number of bytes occupied by a subface. The list of pointers + // stored in a subface are: three to other subfaces, three to corners, + // three to subsegments, two to tetrahedra. + shsize = 11 * sizeof(shellface); + // The index within each subface at which the maximum area bound is + // found, where the index is measured in REALs. + areaboundindex = (shsize + sizeof(REAL) - 1) / sizeof(REAL); + // If -q switch is in use, increase the number of bytes occupied by + // a subface for saving maximum area bound. + if (checkconstraints) { + shsize = (areaboundindex + 1) * sizeof(REAL); + } else { + shsize = areaboundindex * sizeof(REAL); + } + // The index within subface at which the facet marker is found. Ensure + // the marker is aligned to a sizeof(int)-byte address. + shmarkindex = (shsize + sizeof(int) - 1) / sizeof(int); + // Increase the number of bytes by two or three integers, one for facet + // marker, one for shellface type and flags, and optionally one + // for storing facet index (for mesh refinement). + shsize = (shmarkindex + 2 + useinsertradius) * sizeof(shellface); + + // Initialize the pool of subfaces. Each subface record is eight-byte + // aligned so it has room to store an edge version (from 0 to 5) in + // the least three bits. + subfaces = new memorypool(shsize, b->shellfaceperblock, sizeof(void *), 8); + + if (b->verbose) { + printf(" Size of a shellface: %d (%d) bytes.\n", shsize, + subfaces->itembytes); + } + + // Initialize the pool of subsegments. The subsegment's record is same + // with subface. + subsegs = new memorypool(shsize, b->shellfaceperblock, sizeof(void *), 8); + + // Initialize the pool for tet-subseg connections. + tet2segpool = new memorypool(6 * sizeof(shellface), b->shellfaceperblock, + sizeof(void *), 0); + // Initialize the pool for tet-subface connections. + tet2subpool = new memorypool(4 * sizeof(shellface), b->shellfaceperblock, + sizeof(void *), 0); + + // Initialize arraypools for segment & facet recovery. + subsegstack = new arraypool(sizeof(face), 10); + subfacstack = new arraypool(sizeof(face), 10); + subvertstack = new arraypool(sizeof(point), 8); + + // Initialize arraypools for surface point insertion/deletion. + caveshlist = new arraypool(sizeof(face), 8); + caveshbdlist = new arraypool(sizeof(face), 8); + cavesegshlist = new arraypool(sizeof(face), 4); + + cavetetshlist = new arraypool(sizeof(face), 8); + cavetetseglist = new arraypool(sizeof(face), 8); + caveencshlist = new arraypool(sizeof(face), 8); + caveencseglist = new arraypool(sizeof(face), 8); + } + + // Initialize the pools for flips. + flippool = new memorypool(sizeof(badface), 1024, sizeof(void *), 0); + later_unflip_queue = new arraypool(sizeof(badface), 10); + unflipqueue = new arraypool(sizeof(badface), 10); + + // Initialize the arraypools for point insertion. + cavetetlist = new arraypool(sizeof(triface), 10); + cavebdrylist = new arraypool(sizeof(triface), 10); + caveoldtetlist = new arraypool(sizeof(triface), 10); + cavetetvertlist = new arraypool(sizeof(point), 10); + cave_oldtet_list = new arraypool(sizeof(tetrahedron*), 10); +} + +// // +// // +//== mempool_cxx =============================================================// + +//== geom_cxx ================================================================// +// // +// // + +// PI is the ratio of a circle's circumference to its diameter. +REAL tetgenmesh::PI = 3.14159265358979323846264338327950288419716939937510582; + +//============================================================================// +// // +// insphere_s() Insphere test with symbolic perturbation. // +// // +// Given four points pa, pb, pc, and pd, test if the point pe lies inside or // +// outside the circumscribed sphere of the four points. // +// // +// Here we assume that the 3d orientation of the point sequence {pa, pb, pc, // +// pd} is positive (NOT zero), i.e., pd lies above the plane passing through // +// points pa, pb, and pc. Otherwise, the returned sign is flipped. // +// // +// Return a positive value (> 0) if pe lies inside, a negative value (< 0) // +// if pe lies outside the sphere, the returned value will not be zero. // +// // +//============================================================================// + +REAL tetgenmesh::insphere_s(REAL* pa, REAL* pb, REAL* pc, REAL* pd, REAL* pe) +{ + REAL sign; + + sign = insphere(pa, pb, pc, pd, pe); + if (sign != 0.0) { + return sign; + } + + // Symbolic perturbation. + point pt[5], swappt; + REAL oriA, oriB; + int swaps, count; + int n, i; + + pt[0] = pa; + pt[1] = pb; + pt[2] = pc; + pt[3] = pd; + pt[4] = pe; + + // Sort the five points such that their indices are in the increasing + // order. An optimized bubble sort algorithm is used, i.e., it has + // the worst case O(n^2) runtime, but it is usually much faster. + swaps = 0; // Record the total number of swaps. + n = 5; + do { + count = 0; + n = n - 1; + for (i = 0; i < n; i++) { + if (pointmark(pt[i]) > pointmark(pt[i+1])) { + swappt = pt[i]; pt[i] = pt[i+1]; pt[i+1] = swappt; + count++; + } + } + swaps += count; + } while (count > 0); // Continue if some points are swapped. + + oriA = orient3d(pt[1], pt[2], pt[3], pt[4]); + if (oriA != 0.0) { + // Flip the sign if there are odd number of swaps. + if ((swaps % 2) != 0) oriA = -oriA; + return oriA; + } + + oriB = -orient3d(pt[0], pt[2], pt[3], pt[4]); + if (oriB == 0.0) { + terminatetetgen(this, 2); + } + // Flip the sign if there are odd number of swaps. + if ((swaps % 2) != 0) oriB = -oriB; + return oriB; +} + +//============================================================================// +// // +// orient4d_s() 4d orientation test with symbolic perturbation. // +// // +// Given four lifted points pa', pb', pc', and pd' in R^4,test if the lifted // +// point pe' in R^4 lies below or above the hyperplane passing through the // +// four points pa', pb', pc', and pd'. // +// // +// Here we assume that the 3d orientation of the point sequence {pa, pb, pc, // +// pd} is positive (NOT zero), i.e., pd lies above the plane passing through // +// the points pa, pb, and pc. Otherwise, the returned sign is flipped. // +// // +// Return a positive value (> 0) if pe' lies below, a negative value (< 0) // +// if pe' lies above the hyperplane, the returned value should not be zero. // +// // +//============================================================================// + +REAL tetgenmesh::orient4d_s(REAL* pa, REAL* pb, REAL* pc, REAL* pd, REAL* pe, + REAL aheight, REAL bheight, REAL cheight, + REAL dheight, REAL eheight) +{ + REAL sign; + + sign = orient4d(pa, pb, pc, pd, pe, + aheight, bheight, cheight, dheight, eheight); + if (sign != 0.0) { + return sign; + } + + // Symbolic perturbation. + point pt[5], swappt; + REAL oriA, oriB; + int swaps, count; + int n, i; + + pt[0] = pa; + pt[1] = pb; + pt[2] = pc; + pt[3] = pd; + pt[4] = pe; + + // Sort the five points such that their indices are in the increasing + // order. An optimized bubble sort algorithm is used, i.e., it has + // the worst case O(n^2) runtime, but it is usually much faster. + swaps = 0; // Record the total number of swaps. + n = 5; + do { + count = 0; + n = n - 1; + for (i = 0; i < n; i++) { + if (pointmark(pt[i]) > pointmark(pt[i+1])) { + swappt = pt[i]; pt[i] = pt[i+1]; pt[i+1] = swappt; + count++; + } + } + swaps += count; + } while (count > 0); // Continue if some points are swapped. + + oriA = orient3d(pt[1], pt[2], pt[3], pt[4]); + if (oriA != 0.0) { + // Flip the sign if there are odd number of swaps. + if ((swaps % 2) != 0) oriA = -oriA; + return oriA; + } + + oriB = -orient3d(pt[0], pt[2], pt[3], pt[4]); + if (oriB == 0.0) { + terminatetetgen(this, 2); + } + // Flip the sign if there are odd number of swaps. + if ((swaps % 2) != 0) oriB = -oriB; + return oriB; +} + +//============================================================================// +// // +// tri_edge_test() Triangle-edge intersection test. // +// // +// This routine takes a triangle T (with vertices A, B, C) and an edge E (P, // +// Q) in 3D, and tests if they intersect each other. // +// // +// If the point 'R' is not NULL, it lies strictly above the plane defined by // +// A, B, C. It is used in test when T and E are coplanar. // +// // +// If T and E intersect each other, they may intersect in different ways. If // +// 'level' > 0, their intersection type will be reported 'types' and 'pos'. // +// // +// The return value indicates one of the following cases: // +// - 0, T and E are disjoint. // +// - 1, T and E intersect each other. // +// - 2, T and E are not coplanar. They intersect at a single point. // +// - 4, T and E are coplanar. They intersect at a single point or a line // +// segment (if types[1] != DISJOINT). // +// // +//============================================================================// + +#define SETVECTOR3(V, a0, a1, a2) (V)[0] = (a0); (V)[1] = (a1); (V)[2] = (a2) + +#define SWAP2(a0, a1, tmp) (tmp) = (a0); (a0) = (a1); (a1) = (tmp) + +int tetgenmesh::tri_edge_2d(point A, point B, point C, point P, point Q, + point R, int level, int *types, int *pos) +{ + point U[3], V[3]; // The permuted vectors of points. + int pu[3], pv[3]; // The original positions of points. + REAL abovept[3]; + REAL sA, sB, sC; + REAL s1, s2, s3, s4; + int z1; + + if (R == NULL) { + // Calculate a lift point. + if (1) { + REAL n[3], len; + // Calculate a lift point, saved in dummypoint. + facenormal(A, B, C, n, 1, NULL); + len = sqrt(dot(n, n)); + if (len != 0) { + n[0] /= len; + n[1] /= len; + n[2] /= len; + len = distance(A, B); + len += distance(B, C); + len += distance(C, A); + len /= 3.0; + R = abovept; //dummypoint; + R[0] = A[0] + len * n[0]; + R[1] = A[1] + len * n[1]; + R[2] = A[2] + len * n[2]; + } else { + // The triangle [A,B,C] is (nearly) degenerate, i.e., it is (close) + // to a line. We need a line-line intersection test. + // !!! A non-save return value.!!! + return 0; // DISJOINT + } + } + } + + // Test A's, B's, and C's orientations wrt plane PQR. + sA = orient3d(P, Q, R, A); + sB = orient3d(P, Q, R, B); + sC = orient3d(P, Q, R, C); + + + if (sA < 0) { + if (sB < 0) { + if (sC < 0) { // (---). + return 0; + } else { + if (sC > 0) { // (--+). + // All points are in the right positions. + SETVECTOR3(U, A, B, C); // I3 + SETVECTOR3(V, P, Q, R); // I2 + SETVECTOR3(pu, 0, 1, 2); + SETVECTOR3(pv, 0, 1, 2); + z1 = 0; + } else { // (--0). + SETVECTOR3(U, A, B, C); // I3 + SETVECTOR3(V, P, Q, R); // I2 + SETVECTOR3(pu, 0, 1, 2); + SETVECTOR3(pv, 0, 1, 2); + z1 = 1; + } + } + } else { + if (sB > 0) { + if (sC < 0) { // (-+-). + SETVECTOR3(U, C, A, B); // PT = ST + SETVECTOR3(V, P, Q, R); // I2 + SETVECTOR3(pu, 2, 0, 1); + SETVECTOR3(pv, 0, 1, 2); + z1 = 0; + } else { + if (sC > 0) { // (-++). + SETVECTOR3(U, B, C, A); // PT = ST x ST + SETVECTOR3(V, Q, P, R); // PL = SL + SETVECTOR3(pu, 1, 2, 0); + SETVECTOR3(pv, 1, 0, 2); + z1 = 0; + } else { // (-+0). + SETVECTOR3(U, C, A, B); // PT = ST + SETVECTOR3(V, P, Q, R); // I2 + SETVECTOR3(pu, 2, 0, 1); + SETVECTOR3(pv, 0, 1, 2); + z1 = 2; + } + } + } else { + if (sC < 0) { // (-0-). + SETVECTOR3(U, C, A, B); // PT = ST + SETVECTOR3(V, P, Q, R); // I2 + SETVECTOR3(pu, 2, 0, 1); + SETVECTOR3(pv, 0, 1, 2); + z1 = 1; + } else { + if (sC > 0) { // (-0+). + SETVECTOR3(U, B, C, A); // PT = ST x ST + SETVECTOR3(V, Q, P, R); // PL = SL + SETVECTOR3(pu, 1, 2, 0); + SETVECTOR3(pv, 1, 0, 2); + z1 = 2; + } else { // (-00). + SETVECTOR3(U, B, C, A); // PT = ST x ST + SETVECTOR3(V, Q, P, R); // PL = SL + SETVECTOR3(pu, 1, 2, 0); + SETVECTOR3(pv, 1, 0, 2); + z1 = 3; + } + } + } + } + } else { + if (sA > 0) { + if (sB < 0) { + if (sC < 0) { // (+--). + SETVECTOR3(U, B, C, A); // PT = ST x ST + SETVECTOR3(V, P, Q, R); // I2 + SETVECTOR3(pu, 1, 2, 0); + SETVECTOR3(pv, 0, 1, 2); + z1 = 0; + } else { + if (sC > 0) { // (+-+). + SETVECTOR3(U, C, A, B); // PT = ST + SETVECTOR3(V, Q, P, R); // PL = SL + SETVECTOR3(pu, 2, 0, 1); + SETVECTOR3(pv, 1, 0, 2); + z1 = 0; + } else { // (+-0). + SETVECTOR3(U, C, A, B); // PT = ST + SETVECTOR3(V, Q, P, R); // PL = SL + SETVECTOR3(pu, 2, 0, 1); + SETVECTOR3(pv, 1, 0, 2); + z1 = 2; + } + } + } else { + if (sB > 0) { + if (sC < 0) { // (++-). + SETVECTOR3(U, A, B, C); // I3 + SETVECTOR3(V, Q, P, R); // PL = SL + SETVECTOR3(pu, 0, 1, 2); + SETVECTOR3(pv, 1, 0, 2); + z1 = 0; + } else { + if (sC > 0) { // (+++). + return 0; + } else { // (++0). + SETVECTOR3(U, A, B, C); // I3 + SETVECTOR3(V, Q, P, R); // PL = SL + SETVECTOR3(pu, 0, 1, 2); + SETVECTOR3(pv, 1, 0, 2); + z1 = 1; + } + } + } else { // (+0#) + if (sC < 0) { // (+0-). + SETVECTOR3(U, B, C, A); // PT = ST x ST + SETVECTOR3(V, P, Q, R); // I2 + SETVECTOR3(pu, 1, 2, 0); + SETVECTOR3(pv, 0, 1, 2); + z1 = 2; + } else { + if (sC > 0) { // (+0+). + SETVECTOR3(U, C, A, B); // PT = ST + SETVECTOR3(V, Q, P, R); // PL = SL + SETVECTOR3(pu, 2, 0, 1); + SETVECTOR3(pv, 1, 0, 2); + z1 = 1; + } else { // (+00). + SETVECTOR3(U, B, C, A); // PT = ST x ST + SETVECTOR3(V, P, Q, R); // I2 + SETVECTOR3(pu, 1, 2, 0); + SETVECTOR3(pv, 0, 1, 2); + z1 = 3; + } + } + } + } + } else { + if (sB < 0) { + if (sC < 0) { // (0--). + SETVECTOR3(U, B, C, A); // PT = ST x ST + SETVECTOR3(V, P, Q, R); // I2 + SETVECTOR3(pu, 1, 2, 0); + SETVECTOR3(pv, 0, 1, 2); + z1 = 1; + } else { + if (sC > 0) { // (0-+). + SETVECTOR3(U, A, B, C); // I3 + SETVECTOR3(V, P, Q, R); // I2 + SETVECTOR3(pu, 0, 1, 2); + SETVECTOR3(pv, 0, 1, 2); + z1 = 2; + } else { // (0-0). + SETVECTOR3(U, C, A, B); // PT = ST + SETVECTOR3(V, Q, P, R); // PL = SL + SETVECTOR3(pu, 2, 0, 1); + SETVECTOR3(pv, 1, 0, 2); + z1 = 3; + } + } + } else { + if (sB > 0) { + if (sC < 0) { // (0+-). + SETVECTOR3(U, A, B, C); // I3 + SETVECTOR3(V, Q, P, R); // PL = SL + SETVECTOR3(pu, 0, 1, 2); + SETVECTOR3(pv, 1, 0, 2); + z1 = 2; + } else { + if (sC > 0) { // (0++). + SETVECTOR3(U, B, C, A); // PT = ST x ST + SETVECTOR3(V, Q, P, R); // PL = SL + SETVECTOR3(pu, 1, 2, 0); + SETVECTOR3(pv, 1, 0, 2); + z1 = 1; + } else { // (0+0). + SETVECTOR3(U, C, A, B); // PT = ST + SETVECTOR3(V, P, Q, R); // I2 + SETVECTOR3(pu, 2, 0, 1); + SETVECTOR3(pv, 0, 1, 2); + z1 = 3; + } + } + } else { // (00#) + if (sC < 0) { // (00-). + SETVECTOR3(U, A, B, C); // I3 + SETVECTOR3(V, Q, P, R); // PL = SL + SETVECTOR3(pu, 0, 1, 2); + SETVECTOR3(pv, 1, 0, 2); + z1 = 3; + } else { + if (sC > 0) { // (00+). + SETVECTOR3(U, A, B, C); // I3 + SETVECTOR3(V, P, Q, R); // I2 + SETVECTOR3(pu, 0, 1, 2); + SETVECTOR3(pv, 0, 1, 2); + z1 = 3; + } else { // (000) + // Not possible unless ABC is degenerate. + // Avoiding compiler warnings. + SETVECTOR3(U, A, B, C); // I3 + SETVECTOR3(V, P, Q, R); // I2 + SETVECTOR3(pu, 0, 1, 2); + SETVECTOR3(pv, 0, 1, 2); + z1 = 4; + } + } + } + } + } + } + + s1 = orient3d(U[0], U[2], R, V[1]); // A, C, R, Q + s2 = orient3d(U[1], U[2], R, V[0]); // B, C, R, P + + if (s1 > 0) { + return 0; + } + if (s2 < 0) { + return 0; + } + + if (level == 0) { + return 1; // They are intersected. + } + + + if (z1 == 1) { + if (s1 == 0) { // (0###) + // C = Q. + types[0] = (int) SHAREVERT; + pos[0] = pu[2]; // C + pos[1] = pv[1]; // Q + types[1] = (int) DISJOINT; + } else { + if (s2 == 0) { // (#0##) + // C = P. + types[0] = (int) SHAREVERT; + pos[0] = pu[2]; // C + pos[1] = pv[0]; // P + types[1] = (int) DISJOINT; + } else { // (-+##) + // C in [P, Q]. + types[0] = (int) ACROSSVERT; + pos[0] = pu[2]; // C + pos[1] = pv[0]; // [P, Q] + types[1] = (int) DISJOINT; + } + } + return 4; + } + + s3 = orient3d(U[0], U[2], R, V[0]); // A, C, R, P + s4 = orient3d(U[1], U[2], R, V[1]); // B, C, R, Q + + if (z1 == 0) { // (tritri-03) + if (s1 < 0) { + if (s3 > 0) { + if (s4 > 0) { + // [P, Q] overlaps [k, l] (-+++). + types[0] = (int) ACROSSEDGE; + pos[0] = pu[2]; // [C, A] + pos[1] = pv[0]; // [P, Q] + types[1] = (int) TOUCHFACE; + pos[2] = 3; // [A, B, C] + pos[3] = pv[1]; // Q + } else { + if (s4 == 0) { + // Q = l, [P, Q] contains [k, l] (-++0). + types[0] = (int) ACROSSEDGE; + pos[0] = pu[2]; // [C, A] + pos[1] = pv[0]; // [P, Q] + types[1] = (int) TOUCHEDGE; + pos[2] = pu[1]; // [B, C] + pos[3] = pv[1]; // Q + } else { // s4 < 0 + // [P, Q] contains [k, l] (-++-). + types[0] = (int) ACROSSEDGE; + pos[0] = pu[2]; // [C, A] + pos[1] = pv[0]; // [P, Q] + types[1] = (int) ACROSSEDGE; + pos[2] = pu[1]; // [B, C] + pos[3] = pv[0]; // [P, Q] + } + } + } else { + if (s3 == 0) { + if (s4 > 0) { + // P = k, [P, Q] in [k, l] (-+0+). + types[0] = (int) TOUCHEDGE; + pos[0] = pu[2]; // [C, A] + pos[1] = pv[0]; // P + types[1] = (int) TOUCHFACE; + pos[2] = 3; // [A, B, C] + pos[3] = pv[1]; // Q + } else { + if (s4 == 0) { + // [P, Q] = [k, l] (-+00). + types[0] = (int) TOUCHEDGE; + pos[0] = pu[2]; // [C, A] + pos[1] = pv[0]; // P + types[1] = (int) TOUCHEDGE; + pos[2] = pu[1]; // [B, C] + pos[3] = pv[1]; // Q + } else { + // P = k, [P, Q] contains [k, l] (-+0-). + types[0] = (int) TOUCHEDGE; + pos[0] = pu[2]; // [C, A] + pos[1] = pv[0]; // P + types[1] = (int) ACROSSEDGE; + pos[2] = pu[1]; // [B, C] + pos[3] = pv[0]; // [P, Q] + } + } + } else { // s3 < 0 + if (s2 > 0) { + if (s4 > 0) { + // [P, Q] in [k, l] (-+-+). + types[0] = (int) TOUCHFACE; + pos[0] = 3; // [A, B, C] + pos[1] = pv[0]; // P + types[1] = (int) TOUCHFACE; + pos[2] = 3; // [A, B, C] + pos[3] = pv[1]; // Q + } else { + if (s4 == 0) { + // Q = l, [P, Q] in [k, l] (-+-0). + types[0] = (int) TOUCHFACE; + pos[0] = 3; // [A, B, C] + pos[1] = pv[0]; // P + types[1] = (int) TOUCHEDGE; + pos[2] = pu[1]; // [B, C] + pos[3] = pv[1]; // Q + } else { // s4 < 0 + // [P, Q] overlaps [k, l] (-+--). + types[0] = (int) TOUCHFACE; + pos[0] = 3; // [A, B, C] + pos[1] = pv[0]; // P + types[1] = (int) ACROSSEDGE; + pos[2] = pu[1]; // [B, C] + pos[3] = pv[0]; // [P, Q] + } + } + } else { // s2 == 0 + // P = l (#0##). + types[0] = (int) TOUCHEDGE; + pos[0] = pu[1]; // [B, C] + pos[1] = pv[0]; // P + types[1] = (int) DISJOINT; + } + } + } + } else { // s1 == 0 + // Q = k (0####) + types[0] = (int) TOUCHEDGE; + pos[0] = pu[2]; // [C, A] + pos[1] = pv[1]; // Q + types[1] = (int) DISJOINT; + } + } else if (z1 == 2) { // (tritri-23) + if (s1 < 0) { + if (s3 > 0) { + if (s4 > 0) { + // [P, Q] overlaps [A, l] (-+++). + types[0] = (int) ACROSSVERT; + pos[0] = pu[0]; // A + pos[1] = pv[0]; // [P, Q] + types[1] = (int) TOUCHFACE; + pos[2] = 3; // [A, B, C] + pos[3] = pv[1]; // Q + } else { + if (s4 == 0) { + // Q = l, [P, Q] contains [A, l] (-++0). + types[0] = (int) ACROSSVERT; + pos[0] = pu[0]; // A + pos[1] = pv[0]; // [P, Q] + types[1] = (int) TOUCHEDGE; + pos[2] = pu[1]; // [B, C] + pos[3] = pv[1]; // Q + } else { // s4 < 0 + // [P, Q] contains [A, l] (-++-). + types[0] = (int) ACROSSVERT; + pos[0] = pu[0]; // A + pos[1] = pv[0]; // [P, Q] + types[1] = (int) ACROSSEDGE; + pos[2] = pu[1]; // [B, C] + pos[3] = pv[0]; // [P, Q] + } + } + } else { + if (s3 == 0) { + if (s4 > 0) { + // P = A, [P, Q] in [A, l] (-+0+). + types[0] = (int) SHAREVERT; + pos[0] = pu[0]; // A + pos[1] = pv[0]; // P + types[1] = (int) TOUCHFACE; + pos[2] = 3; // [A, B, C] + pos[3] = pv[1]; // Q + } else { + if (s4 == 0) { + // [P, Q] = [A, l] (-+00). + types[0] = (int) SHAREVERT; + pos[0] = pu[0]; // A + pos[1] = pv[0]; // P + types[1] = (int) TOUCHEDGE; + pos[2] = pu[1]; // [B, C] + pos[3] = pv[1]; // Q + } else { // s4 < 0 + // Q = l, [P, Q] in [A, l] (-+0-). + types[0] = (int) SHAREVERT; + pos[0] = pu[0]; // A + pos[1] = pv[0]; // P + types[1] = (int) ACROSSEDGE; + pos[2] = pu[1]; // [B, C] + pos[3] = pv[0]; // [P, Q] + } + } + } else { // s3 < 0 + if (s2 > 0) { + if (s4 > 0) { + // [P, Q] in [A, l] (-+-+). + types[0] = (int) TOUCHFACE; + pos[0] = 3; // [A, B, C] + pos[1] = pv[0]; // P + types[0] = (int) TOUCHFACE; + pos[0] = 3; // [A, B, C] + pos[1] = pv[1]; // Q + } else { + if (s4 == 0) { + // Q = l, [P, Q] in [A, l] (-+-0). + types[0] = (int) TOUCHFACE; + pos[0] = 3; // [A, B, C] + pos[1] = pv[0]; // P + types[0] = (int) TOUCHEDGE; + pos[0] = pu[1]; // [B, C] + pos[1] = pv[1]; // Q + } else { // s4 < 0 + // [P, Q] overlaps [A, l] (-+--). + types[0] = (int) TOUCHFACE; + pos[0] = 3; // [A, B, C] + pos[1] = pv[0]; // P + types[0] = (int) ACROSSEDGE; + pos[0] = pu[1]; // [B, C] + pos[1] = pv[0]; // [P, Q] + } + } + } else { // s2 == 0 + // P = l (#0##). + types[0] = (int) TOUCHEDGE; + pos[0] = pu[1]; // [B, C] + pos[1] = pv[0]; // P + types[1] = (int) DISJOINT; + } + } + } + } else { // s1 == 0 + // Q = A (0###). + types[0] = (int) SHAREVERT; + pos[0] = pu[0]; // A + pos[1] = pv[1]; // Q + types[1] = (int) DISJOINT; + } + } else if (z1 == 3) { // (tritri-33) + if (s1 < 0) { + if (s3 > 0) { + if (s4 > 0) { + // [P, Q] overlaps [A, B] (-+++). + types[0] = (int) ACROSSVERT; + pos[0] = pu[0]; // A + pos[1] = pv[0]; // [P, Q] + types[1] = (int) TOUCHEDGE; + pos[2] = pu[0]; // [A, B] + pos[3] = pv[1]; // Q + } else { + if (s4 == 0) { + // Q = B, [P, Q] contains [A, B] (-++0). + types[0] = (int) ACROSSVERT; + pos[0] = pu[0]; // A + pos[1] = pv[0]; // [P, Q] + types[1] = (int) SHAREVERT; + pos[2] = pu[1]; // B + pos[3] = pv[1]; // Q + } else { // s4 < 0 + // [P, Q] contains [A, B] (-++-). + types[0] = (int) ACROSSVERT; + pos[0] = pu[0]; // A + pos[1] = pv[0]; // [P, Q] + types[1] = (int) ACROSSVERT; + pos[2] = pu[1]; // B + pos[3] = pv[0]; // [P, Q] + } + } + } else { + if (s3 == 0) { + if (s4 > 0) { + // P = A, [P, Q] in [A, B] (-+0+). + types[0] = (int) SHAREVERT; + pos[0] = pu[0]; // A + pos[1] = pv[0]; // P + types[1] = (int) TOUCHEDGE; + pos[2] = pu[0]; // [A, B] + pos[3] = pv[1]; // Q + } else { + if (s4 == 0) { + // [P, Q] = [A, B] (-+00). + types[0] = (int) SHAREEDGE; + pos[0] = pu[0]; // [A, B] + pos[1] = pv[0]; // [P, Q] + types[1] = (int) DISJOINT; + } else { // s4 < 0 + // P= A, [P, Q] in [A, B] (-+0-). + types[0] = (int) SHAREVERT; + pos[0] = pu[0]; // A + pos[1] = pv[0]; // P + types[1] = (int) ACROSSVERT; + pos[2] = pu[1]; // B + pos[3] = pv[0]; // [P, Q] + } + } + } else { // s3 < 0 + if (s2 > 0) { + if (s4 > 0) { + // [P, Q] in [A, B] (-+-+). + types[0] = (int) TOUCHEDGE; + pos[0] = pu[0]; // [A, B] + pos[1] = pv[0]; // P + types[1] = (int) TOUCHEDGE; + pos[2] = pu[0]; // [A, B] + pos[3] = pv[1]; // Q + } else { + if (s4 == 0) { + // Q = B, [P, Q] in [A, B] (-+-0). + types[0] = (int) TOUCHEDGE; + pos[0] = pu[0]; // [A, B] + pos[1] = pv[0]; // P + types[1] = (int) SHAREVERT; + pos[2] = pu[1]; // B + pos[3] = pv[1]; // Q + } else { // s4 < 0 + // [P, Q] overlaps [A, B] (-+--). + types[0] = (int) TOUCHEDGE; + pos[0] = pu[0]; // [A, B] + pos[1] = pv[0]; // P + types[1] = (int) ACROSSVERT; + pos[2] = pu[1]; // B + pos[3] = pv[0]; // [P, Q] + } + } + } else { // s2 == 0 + // P = B (#0##). + types[0] = (int) SHAREVERT; + pos[0] = pu[1]; // B + pos[1] = pv[0]; // P + types[1] = (int) DISJOINT; + } + } + } + } else { // s1 == 0 + // Q = A (0###). + types[0] = (int) SHAREVERT; + pos[0] = pu[0]; // A + pos[1] = pv[1]; // Q + types[1] = (int) DISJOINT; + } + } + + return 4; +} + +int tetgenmesh::tri_edge_tail(point A,point B,point C,point P,point Q,point R, + REAL sP,REAL sQ,int level,int *types,int *pos) +{ + point U[3], V[3]; //, Ptmp; + int pu[3], pv[3]; //, itmp; + REAL s1, s2, s3; + int z1; + + + if (sP < 0) { + if (sQ < 0) { // (--) disjoint + return 0; + } else { + if (sQ > 0) { // (-+) + SETVECTOR3(U, A, B, C); + SETVECTOR3(V, P, Q, R); + SETVECTOR3(pu, 0, 1, 2); + SETVECTOR3(pv, 0, 1, 2); + z1 = 0; + } else { // (-0) + SETVECTOR3(U, A, B, C); + SETVECTOR3(V, P, Q, R); + SETVECTOR3(pu, 0, 1, 2); + SETVECTOR3(pv, 0, 1, 2); + z1 = 1; + } + } + } else { + if (sP > 0) { // (+-) + if (sQ < 0) { + SETVECTOR3(U, A, B, C); + SETVECTOR3(V, Q, P, R); // P and Q are flipped. + SETVECTOR3(pu, 0, 1, 2); + SETVECTOR3(pv, 1, 0, 2); + z1 = 0; + } else { + if (sQ > 0) { // (++) disjoint + return 0; + } else { // (+0) + SETVECTOR3(U, B, A, C); // A and B are flipped. + SETVECTOR3(V, P, Q, R); + SETVECTOR3(pu, 1, 0, 2); + SETVECTOR3(pv, 0, 1, 2); + z1 = 1; + } + } + } else { // sP == 0 + if (sQ < 0) { // (0-) + SETVECTOR3(U, A, B, C); + SETVECTOR3(V, Q, P, R); // P and Q are flipped. + SETVECTOR3(pu, 0, 1, 2); + SETVECTOR3(pv, 1, 0, 2); + z1 = 1; + } else { + if (sQ > 0) { // (0+) + SETVECTOR3(U, B, A, C); // A and B are flipped. + SETVECTOR3(V, Q, P, R); // P and Q are flipped. + SETVECTOR3(pu, 1, 0, 2); + SETVECTOR3(pv, 1, 0, 2); + z1 = 1; + } else { // (00) + // A, B, C, P, and Q are coplanar. + z1 = 2; + } + } + } + } + + if (z1 == 2) { + // The triangle and the edge are coplanar. + return tri_edge_2d(A, B, C, P, Q, R, level, types, pos); + } + + s1 = orient3d(U[0], U[1], V[0], V[1]); + if (s1 < 0) { + return 0; + } + + s2 = orient3d(U[1], U[2], V[0], V[1]); + if (s2 < 0) { + return 0; + } + + s3 = orient3d(U[2], U[0], V[0], V[1]); + if (s3 < 0) { + return 0; + } + + if (level == 0) { + return 1; // The are intersected. + } + + types[1] = (int) DISJOINT; // No second intersection point. + + if (z1 == 0) { + if (s1 > 0) { + if (s2 > 0) { + if (s3 > 0) { // (+++) + // [P, Q] passes interior of [A, B, C]. + types[0] = (int) ACROSSFACE; + pos[0] = 3; // interior of [A, B, C] + pos[1] = 0; // [P, Q] + } else { // s3 == 0 (++0) + // [P, Q] intersects [C, A]. + types[0] = (int) ACROSSEDGE; + pos[0] = pu[2]; // [C, A] + pos[1] = 0; // [P, Q] + } + } else { // s2 == 0 + if (s3 > 0) { // (+0+) + // [P, Q] intersects [B, C]. + types[0] = (int) ACROSSEDGE; + pos[0] = pu[1]; // [B, C] + pos[1] = 0; // [P, Q] + } else { // s3 == 0 (+00) + // [P, Q] passes C. + types[0] = (int) ACROSSVERT; + pos[0] = pu[2]; // C + pos[1] = 0; // [P, Q] + } + } + } else { // s1 == 0 + if (s2 > 0) { + if (s3 > 0) { // (0++) + // [P, Q] intersects [A, B]. + types[0] = (int) ACROSSEDGE; + pos[0] = pu[0]; // [A, B] + pos[1] = 0; // [P, Q] + } else { // s3 == 0 (0+0) + // [P, Q] passes A. + types[0] = (int) ACROSSVERT; + pos[0] = pu[0]; // A + pos[1] = 0; // [P, Q] + } + } else { // s2 == 0 + if (s3 > 0) { // (00+) + // [P, Q] passes B. + types[0] = (int) ACROSSVERT; + pos[0] = pu[1]; // B + pos[1] = 0; // [P, Q] + } + } + } + } else { // z1 == 1 + if (s1 > 0) { + if (s2 > 0) { + if (s3 > 0) { // (+++) + // Q lies in [A, B, C]. + types[0] = (int) TOUCHFACE; + pos[0] = 0; // [A, B, C] + pos[1] = pv[1]; // Q + } else { // s3 == 0 (++0) + // Q lies on [C, A]. + types[0] = (int) TOUCHEDGE; + pos[0] = pu[2]; // [C, A] + pos[1] = pv[1]; // Q + } + } else { // s2 == 0 + if (s3 > 0) { // (+0+) + // Q lies on [B, C]. + types[0] = (int) TOUCHEDGE; + pos[0] = pu[1]; // [B, C] + pos[1] = pv[1]; // Q + } else { // s3 == 0 (+00) + // Q = C. + types[0] = (int) SHAREVERT; + pos[0] = pu[2]; // C + pos[1] = pv[1]; // Q + } + } + } else { // s1 == 0 + if (s2 > 0) { + if (s3 > 0) { // (0++) + // Q lies on [A, B]. + types[0] = (int) TOUCHEDGE; + pos[0] = pu[0]; // [A, B] + pos[1] = pv[1]; // Q + } else { // s3 == 0 (0+0) + // Q = A. + types[0] = (int) SHAREVERT; + pos[0] = pu[0]; // A + pos[1] = pv[1]; // Q + } + } else { // s2 == 0 + if (s3 > 0) { // (00+) + // Q = B. + types[0] = (int) SHAREVERT; + pos[0] = pu[1]; // B + pos[1] = pv[1]; // Q + } + } + } + } + + // T and E intersect in a single point. + return 2; +} + +int tetgenmesh::tri_edge_test(point A, point B, point C, point P, point Q, + point R, int level, int *types, int *pos) +{ + REAL sP, sQ; + + // Test the locations of P and Q with respect to ABC. + sP = orient3d(A, B, C, P); + sQ = orient3d(A, B, C, Q); + + return tri_edge_tail(A, B, C, P, Q, R, sP, sQ, level, types, pos); +} + +//============================================================================// +// // +// tri_tri_inter() Test whether two triangle (abc) and (opq) are // +// intersecting or not. // +// // +// Return 0 if they are disjoint. Otherwise, return 1. 'type' returns one of // +// the four cases: SHAREVERTEX, SHAREEDGE, SHAREFACE, and INTERSECT. // +// // +//============================================================================// + +int tetgenmesh::tri_edge_inter_tail(REAL* A, REAL* B, REAL* C, REAL* P, + REAL* Q, REAL s_p, REAL s_q) +{ + int types[2], pos[4]; + int ni; // =0, 2, 4 + + ni = tri_edge_tail(A, B, C, P, Q, NULL, s_p, s_q, 1, types, pos); + + if (ni > 0) { + if (ni == 2) { + // Get the intersection type. + if (types[0] == (int) SHAREVERT) { + return (int) SHAREVERT; + } else { + return (int) INTERSECT; + } + } else if (ni == 4) { + // There may be two intersections. + if (types[0] == (int) SHAREVERT) { + if (types[1] == (int) DISJOINT) { + return (int) SHAREVERT; + } else { + return (int) INTERSECT; + } + } else { + if (types[0] == (int) SHAREEDGE) { + return (int) SHAREEDGE; + } else { + return (int) INTERSECT; + } + } + } + } + + return (int) DISJOINT; +} + +int tetgenmesh::tri_tri_inter(REAL* A,REAL* B,REAL* C,REAL* O,REAL* P,REAL* Q) +{ + REAL s_o, s_p, s_q; + REAL s_a, s_b, s_c; + + s_o = orient3d(A, B, C, O); + s_p = orient3d(A, B, C, P); + s_q = orient3d(A, B, C, Q); + if ((s_o * s_p > 0.0) && (s_o * s_q > 0.0)) { + // o, p, q are all in the same halfspace of ABC. + return 0; // DISJOINT; + } + + s_a = orient3d(O, P, Q, A); + s_b = orient3d(O, P, Q, B); + s_c = orient3d(O, P, Q, C); + if ((s_a * s_b > 0.0) && (s_a * s_c > 0.0)) { + // a, b, c are all in the same halfspace of OPQ. + return 0; // DISJOINT; + } + + int abcop, abcpq, abcqo; + int shareedge = 0; + + abcop = tri_edge_inter_tail(A, B, C, O, P, s_o, s_p); + if (abcop == (int) INTERSECT) { + return (int) INTERSECT; + } else if (abcop == (int) SHAREEDGE) { + shareedge++; + } + abcpq = tri_edge_inter_tail(A, B, C, P, Q, s_p, s_q); + if (abcpq == (int) INTERSECT) { + return (int) INTERSECT; + } else if (abcpq == (int) SHAREEDGE) { + shareedge++; + } + abcqo = tri_edge_inter_tail(A, B, C, Q, O, s_q, s_o); + if (abcqo == (int) INTERSECT) { + return (int) INTERSECT; + } else if (abcqo == (int) SHAREEDGE) { + shareedge++; + } + if (shareedge == 3) { + // opq are coincident with abc. + return (int) SHAREFACE; + } + + // Continue to detect whether opq and abc are intersecting or not. + int opqab, opqbc, opqca; + + opqab = tri_edge_inter_tail(O, P, Q, A, B, s_a, s_b); + if (opqab == (int) INTERSECT) { + return (int) INTERSECT; + } + opqbc = tri_edge_inter_tail(O, P, Q, B, C, s_b, s_c); + if (opqbc == (int) INTERSECT) { + return (int) INTERSECT; + } + opqca = tri_edge_inter_tail(O, P, Q, C, A, s_c, s_a); + if (opqca == (int) INTERSECT) { + return (int) INTERSECT; + } + + // At this point, two triangles are not intersecting and not coincident. + // They may be share an edge, or share a vertex, or disjoint. + if (abcop == (int) SHAREEDGE) { + // op is coincident with an edge of abc. + return (int) SHAREEDGE; + } + if (abcpq == (int) SHAREEDGE) { + // pq is coincident with an edge of abc. + return (int) SHAREEDGE; + } + if (abcqo == (int) SHAREEDGE) { + // qo is coincident with an edge of abc. + return (int) SHAREEDGE; + } + + // They may share a vertex or disjoint. + if (abcop == (int) SHAREVERT) { + return (int) SHAREVERT; + } + if (abcpq == (int) SHAREVERT) { + // q is the coincident vertex. + return (int) SHAREVERT; + } + + // They are disjoint. + return (int) DISJOINT; +} + +//============================================================================// +// // +// lu_decmp() Compute the LU decomposition of a matrix. // +// // +// Compute the LU decomposition of a (non-singular) square matrix A using // +// partial pivoting and implicit row exchanges. The result is: // +// A = P * L * U, // +// where P is a permutation matrix, L is unit lower triangular, and U is // +// upper triangular. The factored form of A is used in combination with // +// 'lu_solve()' to solve linear equations: Ax = b, or invert a matrix. // +// // +// The inputs are a square matrix 'lu[N..n+N-1][N..n+N-1]', it's size is 'n'. // +// On output, 'lu' is replaced by the LU decomposition of a rowwise permuta- // +// tion of itself, 'ps[N..n+N-1]' is an output vector that records the row // +// permutation effected by the partial pivoting, effectively, 'ps' array // +// tells the user what the permutation matrix P is; 'd' is output as +1/-1 // +// depending on whether the number of row interchanges was even or odd, // +// respectively. // +// // +// Return true if the LU decomposition is successfully computed, otherwise, // +// return false in case that A is a singular matrix. // +// // +//============================================================================// + +bool tetgenmesh::lu_decmp(REAL lu[4][4], int n, int* ps, REAL* d, int N) +{ + REAL scales[4]; + REAL pivot, biggest, mult, tempf; + int pivotindex = 0; + int i, j, k; + + *d = 1.0; // No row interchanges yet. + + for (i = N; i < n + N; i++) { // For each row. + // Find the largest element in each row for row equilibration + biggest = 0.0; + for (j = N; j < n + N; j++) + if (biggest < (tempf = fabs(lu[i][j]))) + biggest = tempf; + if (biggest != 0.0) + scales[i] = 1.0 / biggest; + else { + scales[i] = 0.0; + return false; // Zero row: singular matrix. + } + ps[i] = i; // Initialize pivot sequence. + } + + for (k = N; k < n + N - 1; k++) { // For each column. + // Find the largest element in each column to pivot around. + biggest = 0.0; + for (i = k; i < n + N; i++) { + if (biggest < (tempf = fabs(lu[ps[i]][k]) * scales[ps[i]])) { + biggest = tempf; + pivotindex = i; + } + } + if (biggest == 0.0) { + return false; // Zero column: singular matrix. + } + if (pivotindex != k) { // Update pivot sequence. + j = ps[k]; + ps[k] = ps[pivotindex]; + ps[pivotindex] = j; + *d = -(*d); // ...and change the parity of d. + } + + // Pivot, eliminating an extra variable each time + pivot = lu[ps[k]][k]; + for (i = k + 1; i < n + N; i++) { + lu[ps[i]][k] = mult = lu[ps[i]][k] / pivot; + if (mult != 0.0) { + for (j = k + 1; j < n + N; j++) + lu[ps[i]][j] -= mult * lu[ps[k]][j]; + } + } + } + + // (lu[ps[n + N - 1]][n + N - 1] == 0.0) ==> A is singular. + return lu[ps[n + N - 1]][n + N - 1] != 0.0; +} + +//============================================================================// +// // +// lu_solve() Solves the linear equation: Ax = b, after the matrix A // +// has been decomposed into the lower and upper triangular // +// matrices L and U, where A = LU. // +// // +// 'lu[N..n+N-1][N..n+N-1]' is input, not as the matrix 'A' but rather as // +// its LU decomposition, computed by the routine 'lu_decmp'; 'ps[N..n+N-1]' // +// is input as the permutation vector returned by 'lu_decmp'; 'b[N..n+N-1]' // +// is input as the right-hand side vector, and returns with the solution // +// vector. 'lu', 'n', and 'ps' are not modified by this routine and can be // +// left in place for successive calls with different right-hand sides 'b'. // +// // +//============================================================================// + +void tetgenmesh::lu_solve(REAL lu[4][4], int n, int* ps, REAL* b, int N) +{ + int i, j; + REAL X[4], dot; + + for (i = N; i < n + N; i++) X[i] = 0.0; + + // Vector reduction using U triangular matrix. + for (i = N; i < n + N; i++) { + dot = 0.0; + for (j = N; j < i + N; j++) + dot += lu[ps[i]][j] * X[j]; + X[i] = b[ps[i]] - dot; + } + + // Back substitution, in L triangular matrix. + for (i = n + N - 1; i >= N; i--) { + dot = 0.0; + for (j = i + 1; j < n + N; j++) + dot += lu[ps[i]][j] * X[j]; + X[i] = (X[i] - dot) / lu[ps[i]][i]; + } + + for (i = N; i < n + N; i++) b[i] = X[i]; +} + +//============================================================================// +// // +// incircle3d() 3D in-circle test. // +// // +// Return a negative value if pd is inside the circumcircle of the triangle // +// pa, pb, and pc. // +// // +// IMPORTANT: It assumes that [a,b] is the common edge, i.e., the two input // +// triangles are [a,b,c] and [b,a,d]. // +// // +//============================================================================// + +REAL tetgenmesh::incircle3d(point pa, point pb, point pc, point pd) +{ + REAL area2[2], n1[3], n2[3], c[3]; + REAL sign, r, d; + + // Calculate the areas of the two triangles [a, b, c] and [b, a, d]. + facenormal(pa, pb, pc, n1, 1, NULL); + area2[0] = dot(n1, n1); + facenormal(pb, pa, pd, n2, 1, NULL); + area2[1] = dot(n2, n2); + + if (area2[0] > area2[1]) { + // Choose [a, b, c] as the base triangle. + circumsphere(pa, pb, pc, NULL, c, &r); + d = distance(c, pd); + } else { + // Choose [b, a, d] as the base triangle. + if (area2[1] > 0) { + circumsphere(pb, pa, pd, NULL, c, &r); + d = distance(c, pc); + } else { + // The four points are collinear. This case only happens on the boundary. + return 0; // Return "not inside". + } + } + + sign = d - r; + if (fabs(sign) / r < b->epsilon) { + sign = 0; + } + + return sign; +} + +//============================================================================// +// // +// facenormal() Calculate the normal of the face. // +// // +// The normal of the face abc can be calculated by the cross product of 2 of // +// its 3 edge vectors. A better choice of two edge vectors will reduce the // +// numerical error during the calculation. Burdakov proved that the optimal // +// basis problem is equivalent to the minimum spanning tree problem with the // +// edge length be the functional, see Burdakov, "A greedy algorithm for the // +// optimal basis problem", BIT 37:3 (1997), 591-599. If 'pivot' > 0, the two // +// short edges in abc are chosen for the calculation. // +// // +// If 'lav' is not NULL and if 'pivot' is set, the average edge length of // +// the edges of the face [a,b,c] is returned. // +// // +//============================================================================// + +void tetgenmesh::facenormal(point pa, point pb, point pc, REAL *n, int pivot, + REAL* lav) +{ + REAL v1[3], v2[3], v3[3], *pv1, *pv2; + REAL L1, L2, L3; + + v1[0] = pb[0] - pa[0]; // edge vector v1: a->b + v1[1] = pb[1] - pa[1]; + v1[2] = pb[2] - pa[2]; + v2[0] = pa[0] - pc[0]; // edge vector v2: c->a + v2[1] = pa[1] - pc[1]; + v2[2] = pa[2] - pc[2]; + + // Default, normal is calculated by: v1 x (-v2) (see Fig. fnormal). + if (pivot > 0) { + // Choose edge vectors by Burdakov's algorithm. + v3[0] = pc[0] - pb[0]; // edge vector v3: b->c + v3[1] = pc[1] - pb[1]; + v3[2] = pc[2] - pb[2]; + L1 = dot(v1, v1); + L2 = dot(v2, v2); + L3 = dot(v3, v3); + // Sort the three edge lengths. + if (L1 < L2) { + if (L2 < L3) { + pv1 = v1; pv2 = v2; // n = v1 x (-v2). + } else { + pv1 = v3; pv2 = v1; // n = v3 x (-v1). + } + } else { + if (L1 < L3) { + pv1 = v1; pv2 = v2; // n = v1 x (-v2). + } else { + pv1 = v2; pv2 = v3; // n = v2 x (-v3). + } + } + if (lav) { + // return the average edge length. + *lav = (sqrt(L1) + sqrt(L2) + sqrt(L3)) / 3.0; + } + } else { + pv1 = v1; pv2 = v2; // n = v1 x (-v2). + } + + // Calculate the face normal. + cross(pv1, pv2, n); + // Inverse the direction; + n[0] = -n[0]; + n[1] = -n[1]; + n[2] = -n[2]; +} + +//============================================================================// +// // +// facedihedral() Return the dihedral angle (in radian) between two // +// adjoining faces. // +// // +// 'pa', 'pb' are the shared edge of these two faces, 'pc1', and 'pc2' are // +// apexes of these two faces. Return the angle (between 0 to 2*pi) between // +// the normal of face (pa, pb, pc1) and normal of face (pa, pb, pc2). // +// // +//============================================================================// + +REAL tetgenmesh::facedihedral(REAL* pa, REAL* pb, REAL* pc1, REAL* pc2) +{ + REAL n1[3], n2[3]; + REAL n1len, n2len; + REAL costheta, ori; + REAL theta; + + facenormal(pa, pb, pc1, n1, 1, NULL); + facenormal(pa, pb, pc2, n2, 1, NULL); + n1len = sqrt(dot(n1, n1)); + n2len = sqrt(dot(n2, n2)); + costheta = dot(n1, n2) / (n1len * n2len); + // Be careful rounding error! + if (costheta > 1.0) { + costheta = 1.0; + } else if (costheta < -1.0) { + costheta = -1.0; + } + theta = acos(costheta); + ori = orient3d(pa, pb, pc1, pc2); + if (ori > 0.0) { + theta = 2 * PI - theta; + } + + return theta; +} + +//============================================================================// +// // +// triarea() Return the area of a triangle. // +// // +//============================================================================// + +REAL tetgenmesh::triarea(REAL* pa, REAL* pb, REAL* pc) +{ + REAL A[4][4]; + + // Compute the coefficient matrix A (3x3). + A[0][0] = pb[0] - pa[0]; + A[0][1] = pb[1] - pa[1]; + A[0][2] = pb[2] - pa[2]; // vector V1 (pa->pb) + A[1][0] = pc[0] - pa[0]; + A[1][1] = pc[1] - pa[1]; + A[1][2] = pc[2] - pa[2]; // vector V2 (pa->pc) + + cross(A[0], A[1], A[2]); // vector V3 (V1 X V2) + + return 0.5 * sqrt(dot(A[2], A[2])); // The area of [a,b,c]. +} + +REAL tetgenmesh::orient3dfast(REAL *pa, REAL *pb, REAL *pc, REAL *pd) +{ + REAL adx, bdx, cdx; + REAL ady, bdy, cdy; + REAL adz, bdz, cdz; + + adx = pa[0] - pd[0]; + bdx = pb[0] - pd[0]; + cdx = pc[0] - pd[0]; + ady = pa[1] - pd[1]; + bdy = pb[1] - pd[1]; + cdy = pc[1] - pd[1]; + adz = pa[2] - pd[2]; + bdz = pb[2] - pd[2]; + cdz = pc[2] - pd[2]; + + return adx * (bdy * cdz - bdz * cdy) + + bdx * (cdy * adz - cdz * ady) + + cdx * (ady * bdz - adz * bdy); +} + +//============================================================================// +// // +// interiorangle() Return the interior angle (0 - 2 * PI) between vectors // +// o->p1 and o->p2. // +// // +// 'n' is the normal of the plane containing face (o, p1, p2). The interior // +// angle is the total angle rotating from o->p1 around n to o->p2. Exchange // +// the position of p1 and p2 will get the complement angle of the other one. // +// i.e., interiorangle(o, p1, p2) = 2 * PI - interiorangle(o, p2, p1). Set // +// 'n' be NULL if you only want the interior angle between 0 - PI. // +// // +//============================================================================// + +REAL tetgenmesh::interiorangle(REAL* o, REAL* p1, REAL* p2, REAL* n) +{ + REAL v1[3], v2[3], np[3]; + REAL theta, costheta, lenlen; + REAL ori, len1, len2; + + // Get the interior angle (0 - PI) between o->p1, and o->p2. + v1[0] = p1[0] - o[0]; + v1[1] = p1[1] - o[1]; + v1[2] = p1[2] - o[2]; + v2[0] = p2[0] - o[0]; + v2[1] = p2[1] - o[1]; + v2[2] = p2[2] - o[2]; + len1 = sqrt(dot(v1, v1)); + len2 = sqrt(dot(v2, v2)); + lenlen = len1 * len2; + + costheta = dot(v1, v2) / lenlen; + if (costheta > 1.0) { + costheta = 1.0; // Roundoff. + } else if (costheta < -1.0) { + costheta = -1.0; // Roundoff. + } + theta = acos(costheta); + if (n != NULL) { + // Get a point above the face (o, p1, p2); + np[0] = o[0] + n[0]; + np[1] = o[1] + n[1]; + np[2] = o[2] + n[2]; + // Adjust theta (0 - 2 * PI). + ori = orient3d(p1, o, np, p2); + if (ori > 0.0) { + theta = 2 * PI - theta; + } + } + + return theta; +} + +REAL tetgenmesh::cos_interiorangle(REAL* o, REAL* p1, REAL* p2) +{ + REAL v1[3], v2[3], np[3]; + REAL theta, costheta, lenlen; + REAL ori, len1, len2; + + // Get the interior angle (0 - PI) between o->p1, and o->p2. + v1[0] = p1[0] - o[0]; + v1[1] = p1[1] - o[1]; + v1[2] = p1[2] - o[2]; + v2[0] = p2[0] - o[0]; + v2[1] = p2[1] - o[1]; + v2[2] = p2[2] - o[2]; + len1 = sqrt(dot(v1, v1)); + len2 = sqrt(dot(v2, v2)); + lenlen = len1 * len2; + + costheta = dot(v1, v2) / lenlen; + + if (costheta > 1.0) { + costheta = 1.0; // Roundoff. + } else if (costheta < -1.0) { + costheta = -1.0; // Roundoff. + } + + return costheta; +} + +//============================================================================// +// // +// projpt2edge() Return the projection point from a point to an edge. // +// // +//============================================================================// + +void tetgenmesh::projpt2edge(REAL* p, REAL* e1, REAL* e2, REAL* prj) +{ + REAL v1[3], v2[3]; + REAL len, l_p; + + v1[0] = e2[0] - e1[0]; + v1[1] = e2[1] - e1[1]; + v1[2] = e2[2] - e1[2]; + v2[0] = p[0] - e1[0]; + v2[1] = p[1] - e1[1]; + v2[2] = p[2] - e1[2]; + + len = sqrt(dot(v1, v1)); + v1[0] /= len; + v1[1] /= len; + v1[2] /= len; + l_p = dot(v1, v2); + + prj[0] = e1[0] + l_p * v1[0]; + prj[1] = e1[1] + l_p * v1[1]; + prj[2] = e1[2] + l_p * v1[2]; +} + +//============================================================================// +// // +// projpt2face() Return the projection point from a point to a face. // +// // +//============================================================================// + +void tetgenmesh::projpt2face(REAL* p, REAL* f1, REAL* f2, REAL* f3, REAL* prj) +{ + REAL fnormal[3], v1[3]; + REAL len, dist; + + // Get the unit face normal. + facenormal(f1, f2, f3, fnormal, 1, NULL); + len = sqrt(fnormal[0]*fnormal[0] + fnormal[1]*fnormal[1] + + fnormal[2]*fnormal[2]); + fnormal[0] /= len; + fnormal[1] /= len; + fnormal[2] /= len; + // Get the vector v1 = |p - f1|. + v1[0] = p[0] - f1[0]; + v1[1] = p[1] - f1[1]; + v1[2] = p[2] - f1[2]; + // Get the project distance. + dist = dot(fnormal, v1); + + // Get the project point. + prj[0] = p[0] - dist * fnormal[0]; + prj[1] = p[1] - dist * fnormal[1]; + prj[2] = p[2] - dist * fnormal[2]; +} + +//============================================================================// +// // +// circumsphere() Calculate the smallest circumsphere (center and radius) // +// of the given three or four points. // +// // +// The circumsphere of four points (a tetrahedron) is unique if they are not // +// degenerate. If 'pd = NULL', the smallest circumsphere of three points is // +// the diametral sphere of the triangle if they are not degenerate. // +// // +// Return TRUE if the input points are not degenerate and the circumcenter // +// and circumradius are returned in 'cent' and 'radius' respectively if they // +// are not NULLs. Otherwise, return FALSE, the four points are co-planar. // +// // +//============================================================================// + +bool tetgenmesh::circumsphere(REAL* pa, REAL* pb, REAL* pc, REAL* pd, + REAL* cent, REAL* radius) +{ + REAL A[4][4], rhs[4], D; + int indx[4]; + + // Compute the coefficient matrix A (3x3). + A[0][0] = pb[0] - pa[0]; + A[0][1] = pb[1] - pa[1]; + A[0][2] = pb[2] - pa[2]; + A[1][0] = pc[0] - pa[0]; + A[1][1] = pc[1] - pa[1]; + A[1][2] = pc[2] - pa[2]; + if (pd != NULL) { + A[2][0] = pd[0] - pa[0]; + A[2][1] = pd[1] - pa[1]; + A[2][2] = pd[2] - pa[2]; + } else { + cross(A[0], A[1], A[2]); + } + + // Compute the right hand side vector b (3x1). + rhs[0] = 0.5 * dot(A[0], A[0]); + rhs[1] = 0.5 * dot(A[1], A[1]); + if (pd != NULL) { + rhs[2] = 0.5 * dot(A[2], A[2]); + } else { + rhs[2] = 0.0; + } + + // Solve the 3 by 3 equations use LU decomposition with partial pivoting + // and backward and forward substitute.. + if (!lu_decmp(A, 3, indx, &D, 0)) { + if (radius != (REAL *) NULL) *radius = 0.0; + return false; + } + lu_solve(A, 3, indx, rhs, 0); + if (cent != (REAL *) NULL) { + cent[0] = pa[0] + rhs[0]; + cent[1] = pa[1] + rhs[1]; + cent[2] = pa[2] + rhs[2]; + } + if (radius != (REAL *) NULL) { + *radius = sqrt(rhs[0] * rhs[0] + rhs[1] * rhs[1] + rhs[2] * rhs[2]); + } + return true; +} + +//============================================================================// +// // +// orthosphere() Calulcate the orthosphere of four weighted points. // +// // +// A weighted point (p, P^2) can be interpreted as a sphere centered at the // +// point 'p' with a radius 'P'. The 'height' of 'p' is pheight = p[0]^2 + // +// p[1]^2 + p[2]^2 - P^2. // +// // +//============================================================================// + +bool tetgenmesh::orthosphere(REAL* pa, REAL* pb, REAL* pc, REAL* pd, + REAL aheight, REAL bheight, REAL cheight, + REAL dheight, REAL* orthocent, REAL* radius) +{ + REAL A[4][4], rhs[4], D; + int indx[4]; + + // Set the coefficient matrix A (4 x 4). + A[0][0] = 1.0; A[0][1] = pa[0]; A[0][2] = pa[1]; A[0][3] = pa[2]; + A[1][0] = 1.0; A[1][1] = pb[0]; A[1][2] = pb[1]; A[1][3] = pb[2]; + A[2][0] = 1.0; A[2][1] = pc[0]; A[2][2] = pc[1]; A[2][3] = pc[2]; + A[3][0] = 1.0; A[3][1] = pd[0]; A[3][2] = pd[1]; A[3][3] = pd[2]; + + // Set the right hand side vector (4 x 1). + rhs[0] = 0.5 * aheight; + rhs[1] = 0.5 * bheight; + rhs[2] = 0.5 * cheight; + rhs[3] = 0.5 * dheight; + + // Solve the 4 by 4 equations use LU decomposition with partial pivoting + // and backward and forward substitute.. + if (!lu_decmp(A, 4, indx, &D, 0)) { + if (radius != (REAL *) NULL) *radius = 0.0; + return false; + } + lu_solve(A, 4, indx, rhs, 0); + + if (orthocent != (REAL *) NULL) { + orthocent[0] = rhs[1]; + orthocent[1] = rhs[2]; + orthocent[2] = rhs[3]; + } + if (radius != (REAL *) NULL) { + // rhs[0] = - rheight / 2; + // rheight = - 2 * rhs[0]; + // = r[0]^2 + r[1]^2 + r[2]^2 - radius^2 + // radius^2 = r[0]^2 + r[1]^2 + r[2]^2 -rheight + // = r[0]^2 + r[1]^2 + r[2]^2 + 2 * rhs[0] + *radius = sqrt(rhs[1] * rhs[1] + rhs[2] * rhs[2] + rhs[3] * rhs[3] + + 2.0 * rhs[0]); + } + return true; +} + +//============================================================================// +// // +// planelineint() Calculate the intersection of a line and a plane. // +// // +// The equation of a plane (points P are on the plane with normal N and P3 // +// on the plane) can be written as: N dot (P - P3) = 0. The equation of the // +// line (points P on the line passing through P1 and P2) can be written as: // +// P = P1 + u (P2 - P1). The intersection of these two occurs when: // +// N dot (P1 + u (P2 - P1)) = N dot P3. // +// Solving for u gives: // +// N dot (P3 - P1) // +// u = ------------------. // +// N dot (P2 - P1) // +// If the denominator is 0 then N (the normal to the plane) is perpendicular // +// to the line. Thus the line is either parallel to the plane and there are // +// no solutions or the line is on the plane in which case there are an infi- // +// nite number of solutions. // +// // +// The plane is given by three points pa, pb, and pc, e1 and e2 defines the // +// line. If u is non-zero, The intersection point (if exists) returns in ip. // +// // +//============================================================================// + +void tetgenmesh::planelineint(REAL* pa, REAL* pb, REAL* pc, REAL* e1, REAL* e2, + REAL* ip, REAL* u) +{ + REAL *U = e1, *V = e2; + REAL Vuv[3]; // vector U->V + + Vuv[0] = V[0] - U[0]; + Vuv[1] = V[1] - U[1]; + Vuv[2] = V[2] - U[2]; + + REAL A[4], B[4], C[4], D[4], O[4]; + + A[0] = pa[0]; A[1] = pb[0]; A[2] = pc[0]; A[3] = -Vuv[0]; + B[0] = pa[1]; B[1] = pb[1]; B[2] = pc[1]; B[3] = -Vuv[1]; + C[0] = pa[2]; C[1] = pb[2]; C[2] = pc[2]; C[3] = -Vuv[2]; + D[0] = 1.; D[1] = 1.; D[2] = 1.; D[3] = 0.; + O[0] = 0.; O[1] = 0.; O[2] = 0.; O[3] = 0.; + + REAL det, det1; + + det = orient4dexact(A, B, C, D, O, A[3], B[3], C[3], D[3], O[3]); + + if (det != 0.0) { + det1 = orient3dexact(pa, pb, pc, U); + + *u = det1 / det; + + ip[0] = U[0] + *u * Vuv[0]; // (V[0] - U[0]); + ip[1] = U[1] + *u * Vuv[1]; // (V[1] - U[1]); + ip[2] = U[2] + *u * Vuv[2]; // (V[2] - U[2]); + } else { + *u = 0.0; + ip[0] = ip[1] = ip[2] = 0.; + } + +} + +//============================================================================// +// // +// linelineint() Calculate the intersection(s) of two line segments. // +// // +// Calculate the line segment [P, Q] that is the shortest route between two // +// lines from A to B and C to D. Calculate also the values of tp and tq // +// where: P = A + tp (B - A), and Q = C + tq (D - C). // +// // +// Return 1 if the line segment exists. Otherwise, return 0. // +// // +//============================================================================// + +int tetgenmesh::linelineint(REAL* A, REAL* B, REAL* C, REAL* D, REAL* P, + REAL* Q, REAL* tp, REAL* tq) +{ + REAL vab[3], vcd[3], vca[3]; + REAL vab_vab, vcd_vcd, vab_vcd; + REAL vca_vab, vca_vcd; + REAL det, eps; + int i; + + for (i = 0; i < 3; i++) { + vab[i] = B[i] - A[i]; + vcd[i] = D[i] - C[i]; + vca[i] = A[i] - C[i]; + } + + vab_vab = dot(vab, vab); + vcd_vcd = dot(vcd, vcd); + vab_vcd = dot(vab, vcd); + + det = vab_vab * vcd_vcd - vab_vcd * vab_vcd; + // Round the result. + eps = det / (fabs(vab_vab * vcd_vcd) + fabs(vab_vcd * vab_vcd)); + if (eps < b->epsilon) { + return 0; + } + + vca_vab = dot(vca, vab); + vca_vcd = dot(vca, vcd); + + *tp = (vcd_vcd * (- vca_vab) + vab_vcd * vca_vcd) / det; + *tq = (vab_vcd * (- vca_vab) + vab_vab * vca_vcd) / det; + + for (i = 0; i < 3; i++) P[i] = A[i] + (*tp) * vab[i]; + for (i = 0; i < 3; i++) Q[i] = C[i] + (*tq) * vcd[i]; + + return 1; +} + +//============================================================================// +// // +// tetprismvol() Calculate the volume of a tetrahedral prism in 4D. // +// // +// A tetrahedral prism is a convex uniform polychoron (four dimensional poly- // +// tope). It has 6 polyhedral cells: 2 tetrahedra connected by 4 triangular // +// prisms. It has 14 faces: 8 triangular and 6 square. It has 16 edges and 8 // +// vertices. (Wikipedia). // +// // +// Let 'p0', ..., 'p3' be four affinely independent points in R^3. They form // +// the lower tetrahedral facet of the prism. The top tetrahedral facet is // +// formed by four vertices, 'p4', ..., 'p7' in R^4, which is obtained by // +// lifting each vertex of the lower facet into R^4 by a weight (height). A // +// canonical choice of the weights is the square of Euclidean norm of of the // +// points (vectors). // +// // +// // +// The return value is (4!) 24 times of the volume of the tetrahedral prism. // +// // +//============================================================================// + +REAL tetgenmesh::tetprismvol(REAL* p0, REAL* p1, REAL* p2, REAL* p3) +{ + REAL *p4, *p5, *p6, *p7; + REAL w4, w5, w6, w7; + REAL vol[4]; + + p4 = p0; + p5 = p1; + p6 = p2; + p7 = p3; + + // TO DO: these weights can be pre-calculated! + w4 = dot(p0, p0); + w5 = dot(p1, p1); + w6 = dot(p2, p2); + w7 = dot(p3, p3); + + // Calculate the volume of the tet-prism. + vol[0] = orient4d(p5, p6, p4, p3, p7, w5, w6, w4, 0, w7); + vol[1] = orient4d(p3, p6, p2, p0, p1, 0, w6, 0, 0, 0); + vol[2] = orient4d(p4, p6, p3, p0, p1, w4, w6, 0, 0, 0); + vol[3] = orient4d(p6, p5, p4, p3, p1, w6, w5, w4, 0, 0); + + return fabs(vol[0]) + fabs(vol[1]) + fabs(vol[2]) + fabs(vol[3]); +} + +//============================================================================// +// // +// calculateabovepoint() Calculate a point above a facet in 'dummypoint'. // +// // +//============================================================================// + +bool tetgenmesh::calculateabovepoint(arraypool *facpoints, point *ppa, + point *ppb, point *ppc) +{ + point *ppt, pa, pb, pc; + REAL v1[3], v2[3], n[3]; + REAL lab, len, A, area; + REAL x, y, z; + int i; + + ppt = (point *) fastlookup(facpoints, 0); + pa = *ppt; // a is the first point. + pb = pc = NULL; // Avoid compiler warnings. + + // Get a point b s.t. the length of [a, b] is maximal. + lab = 0; + for (i = 1; i < facpoints->objects; i++) { + ppt = (point *) fastlookup(facpoints, i); + x = (*ppt)[0] - pa[0]; + y = (*ppt)[1] - pa[1]; + z = (*ppt)[2] - pa[2]; + len = x * x + y * y + z * z; + if (len > lab) { + lab = len; + pb = *ppt; + } + } + lab = sqrt(lab); + if (lab == 0) { + if (!b->quiet) { + printf("Warning: All points of a facet are coincident with %d.\n", + pointmark(pa)); + } + return false; + } + + // Get a point c s.t. the area of [a, b, c] is maximal. + v1[0] = pb[0] - pa[0]; + v1[1] = pb[1] - pa[1]; + v1[2] = pb[2] - pa[2]; + A = 0; + for (i = 1; i < facpoints->objects; i++) { + ppt = (point *) fastlookup(facpoints, i); + v2[0] = (*ppt)[0] - pa[0]; + v2[1] = (*ppt)[1] - pa[1]; + v2[2] = (*ppt)[2] - pa[2]; + cross(v1, v2, n); + area = dot(n, n); + if (area > A) { + A = area; + pc = *ppt; + } + } + if (A == 0) { + // All points are collinear. No above point. + if (!b->quiet) { + printf("Warning: All points of a facet are collinaer with [%d, %d].\n", + pointmark(pa), pointmark(pb)); + } + return false; + } + + // Calculate an above point of this facet. + facenormal(pa, pb, pc, n, 1, NULL); + len = sqrt(dot(n, n)); + n[0] /= len; + n[1] /= len; + n[2] /= len; + lab /= 2.0; // Half the maximal length. + dummypoint[0] = pa[0] + lab * n[0]; + dummypoint[1] = pa[1] + lab * n[1]; + dummypoint[2] = pa[2] + lab * n[2]; + + if (ppa != NULL) { + // Return the three points. + *ppa = pa; + *ppb = pb; + *ppc = pc; + } + + return true; +} + +//============================================================================// +// // +// Calculate an above point. It lies above the plane containing the subface // +// [a,b,c], and save it in dummypoint. Moreover, the vector pa->dummypoint // +// is the normal of the plane. // +// // +//============================================================================// + +void tetgenmesh::calculateabovepoint4(point pa, point pb, point pc, point pd) +{ + REAL n1[3], n2[3], *norm; + REAL len, len1, len2; + + // Select a base. + facenormal(pa, pb, pc, n1, 1, NULL); + len1 = sqrt(dot(n1, n1)); + facenormal(pa, pb, pd, n2, 1, NULL); + len2 = sqrt(dot(n2, n2)); + if (len1 > len2) { + norm = n1; + len = len1; + } else { + norm = n2; + len = len2; + } + norm[0] /= len; + norm[1] /= len; + norm[2] /= len; + len = distance(pa, pb); + dummypoint[0] = pa[0] + len * norm[0]; + dummypoint[1] = pa[1] + len * norm[1]; + dummypoint[2] = pa[2] + len * norm[2]; +} + + +// // +// // +//== geom_cxx ================================================================// + +//== flip_cxx ================================================================// +// // +// // + +//============================================================================// +// // +// flippush() Push a face (possibly will be flipped) into flipstack. // +// // +// The face is marked. The flag is used to check the validity of the face on // +// its popup. Some other flips may change it already. // +// // +//============================================================================// + +void tetgenmesh::flippush(badface*& fstack, triface* flipface) +{ + if (!facemarked(*flipface)) { + badface *newflipface = (badface *) flippool->alloc(); + newflipface->tt = *flipface; + markface(newflipface->tt); + // Push this face into stack. + newflipface->nextitem = fstack; + fstack = newflipface; + } +} + +//============================================================================// +// // +// flip23() Perform a 2-to-3 flip (face-to-edge flip). // +// // +// 'fliptets' is an array of three tets (handles), where the [0] and [1] are // +// [a,b,c,d] and [b,a,c,e]. The three new tets: [e,d,a,b], [e,d,b,c], and // +// [e,d,c,a] are returned in [0], [1], and [2] of 'fliptets'. As a result, // +// the face [a,b,c] is removed, and the edge [d,e] is created. // +// // +// If 'hullflag' > 0, hull tets may be involved in this flip, i.e., one of // +// the five vertices may be 'dummypoint'. There are two canonical cases: // +// (1) d is 'dummypoint', then all three new tets are hull tets. If e is // +// 'dummypoint', we reconfigure e to d, i.e., to turn it up-side down. // +// (2) c is 'dummypoint', then two new tets: [e,d,b,c] and [e,d,c,a], are // +// hull tets. If a (or b) is 'dummypoint', we reconfigure it to c, // +// i.e., to rotate the three tets counterclockwisely (right-hand rule) // +// until a (or b) is in c's position. // +// // +// If 'fc->enqflag > 0', faces on the convex hull of {a,b,c,d,e} will be // +// queued for flipping. // +// In particular, if 'fc->enqflag = 1', it is called by incrementalflip() // +// after the insertion of a new point. It is assumed that 'd' is the new // +// point. In this case, only link faces of 'd' are queued. // +// // +//============================================================================// + +void tetgenmesh::flip23(triface* fliptets, int hullflag, flipconstraints *fc) +{ + triface topcastets[3], botcastets[3]; + triface newface, casface; + point pa, pb, pc, pd, pe; + REAL attrib, volume; + int dummyflag = 0; // range = {-1, 0, 1, 2}. + int i; + + if (hullflag > 0) { + // Check if e is dummypoint. + if (oppo(fliptets[1]) == dummypoint) { + // Swap the two old tets. + newface = fliptets[0]; + fliptets[0] = fliptets[1]; + fliptets[1] = newface; + dummyflag = -1; // d is dummypoint. + } else { + // Check if either a or b is dummypoint. + if (org(fliptets[0]) == dummypoint) { + dummyflag = 1; // a is dummypoint. + enextself(fliptets[0]); + eprevself(fliptets[1]); + } else if (dest(fliptets[0]) == dummypoint) { + dummyflag = 2; // b is dummypoint. + eprevself(fliptets[0]); + enextself(fliptets[1]); + } else { + dummyflag = 0; // either c or d may be dummypoint. + } + } + } + + pa = org(fliptets[0]); + pb = dest(fliptets[0]); + pc = apex(fliptets[0]); + pd = oppo(fliptets[0]); + pe = oppo(fliptets[1]); + + flip23count++; + + // Get the outer boundary faces. + for (i = 0; i < 3; i++) { + fnext(fliptets[0], topcastets[i]); + enextself(fliptets[0]); + } + for (i = 0; i < 3; i++) { + fnext(fliptets[1], botcastets[i]); + eprevself(fliptets[1]); + } + + // Re-use fliptets[0] and fliptets[1]. + fliptets[0].ver = 11; + fliptets[1].ver = 11; + setelemmarker(fliptets[0].tet, 0); // Clear all flags. + setelemmarker(fliptets[1].tet, 0); + // NOTE: the element attributes and volume constraint remain unchanged. + if (checksubsegflag) { + // Dealloc the space to subsegments. + if (fliptets[0].tet[8] != NULL) { + tet2segpool->dealloc((shellface *) fliptets[0].tet[8]); + fliptets[0].tet[8] = NULL; + } + if (fliptets[1].tet[8] != NULL) { + tet2segpool->dealloc((shellface *) fliptets[1].tet[8]); + fliptets[1].tet[8] = NULL; + } + } + if (checksubfaceflag) { + // Dealloc the space to subfaces. + if (fliptets[0].tet[9] != NULL) { + tet2subpool->dealloc((shellface *) fliptets[0].tet[9]); + fliptets[0].tet[9] = NULL; + } + if (fliptets[1].tet[9] != NULL) { + tet2subpool->dealloc((shellface *) fliptets[1].tet[9]); + fliptets[1].tet[9] = NULL; + } + } + // Create a new tet. + maketetrahedron(&(fliptets[2])); + // The new tet have the same attributes from the old tet. + for (i = 0; i < numelemattrib; i++) { + attrib = elemattribute(fliptets[0].tet, i); + setelemattribute(fliptets[2].tet, i, attrib); + } + if (b->varvolume) { + volume = volumebound(fliptets[0].tet); + setvolumebound(fliptets[2].tet, volume); + } + + if (hullflag > 0) { + // Check if d is dummytet. + if (pd != dummypoint) { + setvertices(fliptets[0], pe, pd, pa, pb); // [e,d,a,b] * + setvertices(fliptets[1], pe, pd, pb, pc); // [e,d,b,c] * + // Check if c is dummypoint. + if (pc != dummypoint) { + setvertices(fliptets[2], pe, pd, pc, pa); // [e,d,c,a] * + } else { + setvertices(fliptets[2], pd, pe, pa, pc); // [d,e,a,c] + esymself(fliptets[2]); // [e,d,c,a] * + } + // The hullsize does not change. + } else { + // d is dummypoint. + setvertices(fliptets[0], pa, pb, pe, pd); // [a,b,e,d] + setvertices(fliptets[1], pb, pc, pe, pd); // [b,c,e,d] + setvertices(fliptets[2], pc, pa, pe, pd); // [c,a,e,d] + // Adjust the faces to [e,d,a,b], [e,d,b,c], [e,d,c,a] * + for (i = 0; i < 3; i++) { + eprevesymself(fliptets[i]); + enextself(fliptets[i]); + } + // We deleted one hull tet, and created three hull tets. + hullsize += 2; + } + } else { + setvertices(fliptets[0], pe, pd, pa, pb); // [e,d,a,b] * + setvertices(fliptets[1], pe, pd, pb, pc); // [e,d,b,c] * + setvertices(fliptets[2], pe, pd, pc, pa); // [e,d,c,a] * + } + + if (fc->remove_ndelaunay_edge) { // calc_tetprism_vol + REAL volneg[2], volpos[3], vol_diff; + if (pd != dummypoint) { + if (pc != dummypoint) { + volpos[0] = tetprismvol(pe, pd, pa, pb); + volpos[1] = tetprismvol(pe, pd, pb, pc); + volpos[2] = tetprismvol(pe, pd, pc, pa); + volneg[0] = tetprismvol(pa, pb, pc, pd); + volneg[1] = tetprismvol(pb, pa, pc, pe); + } else { // pc == dummypoint + volpos[0] = tetprismvol(pe, pd, pa, pb); + volpos[1] = 0.; + volpos[2] = 0.; + volneg[0] = 0.; + volneg[1] = 0.; + } + } else { // pd == dummypoint. + volpos[0] = 0.; + volpos[1] = 0.; + volpos[2] = 0.; + volneg[0] = 0.; + volneg[1] = tetprismvol(pb, pa, pc, pe); + } + vol_diff = volpos[0] + volpos[1] + volpos[2] - volneg[0] - volneg[1]; + fc->tetprism_vol_sum += vol_diff; // Update the total sum. + } + + // Bond three new tets together. + for (i = 0; i < 3; i++) { + esym(fliptets[i], newface); + bond(newface, fliptets[(i + 1) % 3]); + } + // Bond to top outer boundary faces (at [a,b,c,d]). + for (i = 0; i < 3; i++) { + eorgoppo(fliptets[i], newface); // At edges [b,a], [c,b], [a,c]. + bond(newface, topcastets[i]); + } + // Bond bottom outer boundary faces (at [b,a,c,e]). + for (i = 0; i < 3; i++) { + edestoppo(fliptets[i], newface); // At edges [a,b], [b,c], [c,a]. + bond(newface, botcastets[i]); + } + + if (checksubsegflag) { + // Bond subsegments if there are. + // Each new tet has 5 edges to be checked (except the edge [e,d]). + face checkseg; + // The middle three: [a,b], [b,c], [c,a]. + for (i = 0; i < 3; i++) { + if (issubseg(topcastets[i])) { + tsspivot1(topcastets[i], checkseg); + eorgoppo(fliptets[i], newface); + tssbond1(newface, checkseg); + sstbond1(checkseg, newface); + if (fc->chkencflag & 1) { + enqueuesubface(badsubsegs, &checkseg); + } + } + } + // The top three: [d,a], [d,b], [d,c]. Two tets per edge. + for (i = 0; i < 3; i++) { + eprev(topcastets[i], casface); + if (issubseg(casface)) { + tsspivot1(casface, checkseg); + enext(fliptets[i], newface); + tssbond1(newface, checkseg); + sstbond1(checkseg, newface); + esym(fliptets[(i + 2) % 3], newface); + eprevself(newface); + tssbond1(newface, checkseg); + sstbond1(checkseg, newface); + if (fc->chkencflag & 1) { + enqueuesubface(badsubsegs, &checkseg); + } + } + } + // The bot three: [a,e], [b,e], [c,e]. Two tets per edge. + for (i = 0; i < 3; i++) { + enext(botcastets[i], casface); + if (issubseg(casface)) { + tsspivot1(casface, checkseg); + eprev(fliptets[i], newface); + tssbond1(newface, checkseg); + sstbond1(checkseg, newface); + esym(fliptets[(i + 2) % 3], newface); + enextself(newface); + tssbond1(newface, checkseg); + sstbond1(checkseg, newface); + if (fc->chkencflag & 1) { + enqueuesubface(badsubsegs, &checkseg); + } + } + } + } // if (checksubsegflag) + + if (checksubfaceflag) { + // Bond 6 subfaces if there are. + face checksh; + for (i = 0; i < 3; i++) { + if (issubface(topcastets[i])) { + tspivot(topcastets[i], checksh); + eorgoppo(fliptets[i], newface); + sesymself(checksh); + tsbond(newface, checksh); + if (fc->chkencflag & 2) { + enqueuesubface(badsubfacs, &checksh); + } + } + } + for (i = 0; i < 3; i++) { + if (issubface(botcastets[i])) { + tspivot(botcastets[i], checksh); + edestoppo(fliptets[i], newface); + sesymself(checksh); + tsbond(newface, checksh); + if (fc->chkencflag & 2) { + enqueuesubface(badsubfacs, &checksh); + } + } + } + } // if (checksubfaceflag) + + if (fc->chkencflag & 4) { + // Put three new tets into check list. + for (i = 0; i < 3; i++) { + enqueuetetrahedron(&(fliptets[i])); + } + } + + // Update the point-to-tet map. + setpoint2tet(pa, (tetrahedron) fliptets[0].tet); + setpoint2tet(pb, (tetrahedron) fliptets[0].tet); + setpoint2tet(pc, (tetrahedron) fliptets[1].tet); + setpoint2tet(pd, (tetrahedron) fliptets[0].tet); + setpoint2tet(pe, (tetrahedron) fliptets[0].tet); + + if (hullflag > 0) { + if (dummyflag != 0) { + // Restore the original position of the points (for flipnm()). + if (dummyflag == -1) { + // Reverse the edge. + for (i = 0; i < 3; i++) { + esymself(fliptets[i]); + } + // Swap the last two new tets. + newface = fliptets[1]; + fliptets[1] = fliptets[2]; + fliptets[2] = newface; + } else { + // either a or b were swapped. + if (dummyflag == 1) { + // a is dummypoint. + newface = fliptets[0]; + fliptets[0] = fliptets[2]; + fliptets[2] = fliptets[1]; + fliptets[1] = newface; + } else { // dummyflag == 2 + // b is dummypoint. + newface = fliptets[0]; + fliptets[0] = fliptets[1]; + fliptets[1] = fliptets[2]; + fliptets[2] = newface; + } + } + } + } + + if (fc->enqflag > 0) { + // Queue faces which may be locally non-Delaunay. + for (i = 0; i < 3; i++) { + eprevesym(fliptets[i], newface); + flippush(flipstack, &newface); + } + if (fc->enqflag > 1) { + for (i = 0; i < 3; i++) { + enextesym(fliptets[i], newface); + flippush(flipstack, &newface); + } + } + } + + recenttet = fliptets[0]; +} + +//============================================================================// +// // +// flip32() Perform a 3-to-2 flip (edge-to-face flip). // +// // +// 'fliptets' is an array of three tets (handles), which are [e,d,a,b], // +// [e,d,b,c], and [e,d,c,a]. The two new tets: [a,b,c,d] and [b,a,c,e] are // +// returned in [0] and [1] of 'fliptets'. As a result, the edge [e,d] is // +// replaced by the face [a,b,c]. // +// // +// If 'hullflag' > 0, hull tets may be involved in this flip, i.e., one of // +// the five vertices may be 'dummypoint'. There are two canonical cases: // +// (1) d is 'dummypoint', then [a,b,c,d] is hull tet. If e is 'dummypoint', // +// we reconfigure e to d, i.e., turnover it. // +// (2) c is 'dummypoint' then both [a,b,c,d] and [b,a,c,e] are hull tets. // +// If a or b is 'dummypoint', we reconfigure it to c, i.e., rotate the // +// three old tets counterclockwisely (right-hand rule) until a or b // +// is in c's position. // +// // +// If 'fc->enqflag' is set, convex hull faces will be queued for flipping. // +// In particular, if 'fc->enqflag' is 1, it is called by incrementalflip() // +// after the insertion of a new point. It is assumed that 'a' is the new // +// point. In this case, only link faces of 'a' are queued. // +// // +// If 'checksubfaceflag' is on (global variable), and assume [e,d] is not a // +// segment. There may be two (interior) subfaces sharing at [e,d], which are // +// [e,d,p] and [e,d,q], where the pair (p,q) may be either (a,b), or (b,c), // +// or (c,a) In such case, a 2-to-2 flip is performed on these two subfaces // +// and two new subfaces [p,q,e] and [p,q,d] are created. They are inserted // +// back into the tetrahedralization. // +// // +//============================================================================// + +void tetgenmesh::flip32(triface* fliptets, int hullflag, flipconstraints *fc) +{ + triface topcastets[3], botcastets[3]; + triface newface, casface; + face flipshs[3]; + face checkseg; + point pa, pb, pc, pd, pe; + REAL attrib, volume; + int dummyflag = 0; // Rangle = {-1, 0, 1, 2} + int spivot = -1, scount = 0; // for flip22() + int t1ver; + int i, j; + + if (hullflag > 0) { + // Check if e is 'dummypoint'. + if (org(fliptets[0]) == dummypoint) { + // Reverse the edge. + for (i = 0; i < 3; i++) { + esymself(fliptets[i]); + } + // Swap the last two tets. + newface = fliptets[1]; + fliptets[1] = fliptets[2]; + fliptets[2] = newface; + dummyflag = -1; // e is dummypoint. + } else { + // Check if a or b is the 'dummypoint'. + if (apex(fliptets[0]) == dummypoint) { + dummyflag = 1; // a is dummypoint. + newface = fliptets[0]; + fliptets[0] = fliptets[1]; + fliptets[1] = fliptets[2]; + fliptets[2] = newface; + } else if (apex(fliptets[1]) == dummypoint) { + dummyflag = 2; // b is dummypoint. + newface = fliptets[0]; + fliptets[0] = fliptets[2]; + fliptets[2] = fliptets[1]; + fliptets[1] = newface; + } else { + dummyflag = 0; // either c or d may be dummypoint. + } + } + } + + pa = apex(fliptets[0]); + pb = apex(fliptets[1]); + pc = apex(fliptets[2]); + pd = dest(fliptets[0]); + pe = org(fliptets[0]); + + flip32count++; + + // Get the outer boundary faces. + for (i = 0; i < 3; i++) { + eorgoppo(fliptets[i], casface); + fsym(casface, topcastets[i]); + } + for (i = 0; i < 3; i++) { + edestoppo(fliptets[i], casface); + fsym(casface, botcastets[i]); + } + + if (checksubfaceflag) { + // Check if there are interior subfaces at the edge [e,d]. + for (i = 0; i < 3; i++) { + tspivot(fliptets[i], flipshs[i]); + if (flipshs[i].sh != NULL) { + // Found an interior subface. + stdissolve(flipshs[i]); // Disconnect the sub-tet bond. + scount++; + } else { + spivot = i; + } + } + } + + // Re-use fliptets[0] and fliptets[1]. + fliptets[0].ver = 11; + fliptets[1].ver = 11; + setelemmarker(fliptets[0].tet, 0); // Clear all flags. + setelemmarker(fliptets[1].tet, 0); + if (checksubsegflag) { + // Dealloc the space to subsegments. + if (fliptets[0].tet[8] != NULL) { + tet2segpool->dealloc((shellface *) fliptets[0].tet[8]); + fliptets[0].tet[8] = NULL; + } + if (fliptets[1].tet[8] != NULL) { + tet2segpool->dealloc((shellface *) fliptets[1].tet[8]); + fliptets[1].tet[8] = NULL; + } + } + if (checksubfaceflag) { + // Dealloc the space to subfaces. + if (fliptets[0].tet[9] != NULL) { + tet2subpool->dealloc((shellface *) fliptets[0].tet[9]); + fliptets[0].tet[9] = NULL; + } + if (fliptets[1].tet[9] != NULL) { + tet2subpool->dealloc((shellface *) fliptets[1].tet[9]); + fliptets[1].tet[9] = NULL; + } + } + if (checksubfaceflag) { + if (scount > 0) { + // The element attributes and volume constraint must be set correctly. + // There are two subfaces involved in this flip. The three tets are + // separated into two different regions, one may be exterior. The + // first region has two tets, and the second region has only one. + // The two created tets must be in the same region as the first region. + // The element attributes and volume constraint must be set correctly. + //assert(spivot != -1); + // The tet fliptets[spivot] is in the first region. + for (j = 0; j < 2; j++) { + for (i = 0; i < numelemattrib; i++) { + attrib = elemattribute(fliptets[spivot].tet, i); + setelemattribute(fliptets[j].tet, i, attrib); + } + if (b->varvolume) { + volume = volumebound(fliptets[spivot].tet); + setvolumebound(fliptets[j].tet, volume); + } + } + } + } + // Delete an old tet. + tetrahedrondealloc(fliptets[2].tet); + + if (hullflag > 0) { + // Check if c is dummypointc. + if (pc != dummypoint) { + // Check if d is dummypoint. + if (pd != dummypoint) { + // No hull tet is involved. + } else { + // We deleted three hull tets, and created one hull tet. + hullsize -= 2; + } + setvertices(fliptets[0], pa, pb, pc, pd); + setvertices(fliptets[1], pb, pa, pc, pe); + } else { + // c is dummypoint. The two new tets are hull tets. + setvertices(fliptets[0], pb, pa, pd, pc); + setvertices(fliptets[1], pa, pb, pe, pc); + // Adjust badc -> abcd. + esymself(fliptets[0]); + // Adjust abec -> bace. + esymself(fliptets[1]); + // The hullsize does not change. + } + } else { + setvertices(fliptets[0], pa, pb, pc, pd); + setvertices(fliptets[1], pb, pa, pc, pe); + } + + if (fc->remove_ndelaunay_edge) { // calc_tetprism_vol + REAL volneg[3], volpos[2], vol_diff; + if (pc != dummypoint) { + if (pd != dummypoint) { + volneg[0] = tetprismvol(pe, pd, pa, pb); + volneg[1] = tetprismvol(pe, pd, pb, pc); + volneg[2] = tetprismvol(pe, pd, pc, pa); + volpos[0] = tetprismvol(pa, pb, pc, pd); + volpos[1] = tetprismvol(pb, pa, pc, pe); + } else { // pd == dummypoint + volneg[0] = 0.; + volneg[1] = 0.; + volneg[2] = 0.; + volpos[0] = 0.; + volpos[1] = tetprismvol(pb, pa, pc, pe); + } + } else { // pc == dummypoint. + volneg[0] = tetprismvol(pe, pd, pa, pb); + volneg[1] = 0.; + volneg[2] = 0.; + volpos[0] = 0.; + volpos[1] = 0.; + } + vol_diff = volpos[0] + volpos[1] - volneg[0] - volneg[1] - volneg[2]; + fc->tetprism_vol_sum += vol_diff; // Update the total sum. + } + + // Bond abcd <==> bace. + bond(fliptets[0], fliptets[1]); + // Bond new faces to top outer boundary faces (at abcd). + for (i = 0; i < 3; i++) { + esym(fliptets[0], newface); + bond(newface, topcastets[i]); + enextself(fliptets[0]); + } + // Bond new faces to bottom outer boundary faces (at bace). + for (i = 0; i < 3; i++) { + esym(fliptets[1], newface); + bond(newface, botcastets[i]); + eprevself(fliptets[1]); + } + + if (checksubsegflag) { + // Bond 9 segments to new (flipped) tets. + for (i = 0; i < 3; i++) { // edges a->b, b->c, c->a. + if (issubseg(topcastets[i])) { + tsspivot1(topcastets[i], checkseg); + tssbond1(fliptets[0], checkseg); + sstbond1(checkseg, fliptets[0]); + tssbond1(fliptets[1], checkseg); + sstbond1(checkseg, fliptets[1]); + if (fc->chkencflag & 1) { + enqueuesubface(badsubsegs, &checkseg); + } + } + enextself(fliptets[0]); + eprevself(fliptets[1]); + } + // The three top edges. + for (i = 0; i < 3; i++) { // edges b->d, c->d, a->d. + esym(fliptets[0], newface); + eprevself(newface); + enext(topcastets[i], casface); + if (issubseg(casface)) { + tsspivot1(casface, checkseg); + tssbond1(newface, checkseg); + sstbond1(checkseg, newface); + if (fc->chkencflag & 1) { + enqueuesubface(badsubsegs, &checkseg); + } + } + enextself(fliptets[0]); + } + // The three bot edges. + for (i = 0; i < 3; i++) { // edges b<-e, c<-e, a<-e. + esym(fliptets[1], newface); + enextself(newface); + eprev(botcastets[i], casface); + if (issubseg(casface)) { + tsspivot1(casface, checkseg); + tssbond1(newface, checkseg); + sstbond1(checkseg, newface); + if (fc->chkencflag & 1) { + enqueuesubface(badsubsegs, &checkseg); + } + } + eprevself(fliptets[1]); + } + } // if (checksubsegflag) + + if (checksubfaceflag) { + face checksh; + // Bond the top three casing subfaces. + for (i = 0; i < 3; i++) { // At edges [b,a], [c,b], [a,c] + if (issubface(topcastets[i])) { + tspivot(topcastets[i], checksh); + esym(fliptets[0], newface); + sesymself(checksh); + tsbond(newface, checksh); + if (fc->chkencflag & 2) { + enqueuesubface(badsubfacs, &checksh); + } + } + enextself(fliptets[0]); + } + // Bond the bottom three casing subfaces. + for (i = 0; i < 3; i++) { // At edges [a,b], [b,c], [c,a] + if (issubface(botcastets[i])) { + tspivot(botcastets[i], checksh); + esym(fliptets[1], newface); + sesymself(checksh); + tsbond(newface, checksh); + if (fc->chkencflag & 2) { + enqueuesubface(badsubfacs, &checksh); + } + } + eprevself(fliptets[1]); + } + + if (scount > 0) { + face flipfaces[2]; + // Perform a 2-to-2 flip in subfaces. + flipfaces[0] = flipshs[(spivot + 1) % 3]; + flipfaces[1] = flipshs[(spivot + 2) % 3]; + sesymself(flipfaces[1]); + flip22(flipfaces, 0, fc->chkencflag); + // Connect the flipped subfaces to flipped tets. + // First go to the corresponding flipping edge. + // Re-use top- and botcastets[0]. + topcastets[0] = fliptets[0]; + botcastets[0] = fliptets[1]; + for (i = 0; i < ((spivot + 1) % 3); i++) { + enextself(topcastets[0]); + eprevself(botcastets[0]); + } + // Connect the top subface to the top tets. + esymself(topcastets[0]); + sesymself(flipfaces[0]); + // Check if there already exists a subface. + tspivot(topcastets[0], checksh); + if (checksh.sh == NULL) { + tsbond(topcastets[0], flipfaces[0]); + fsymself(topcastets[0]); + sesymself(flipfaces[0]); + tsbond(topcastets[0], flipfaces[0]); + } else { + // An invalid 2-to-2 flip. Report a bug. + terminatetetgen(this, 2); + } + // Connect the bot subface to the bottom tets. + esymself(botcastets[0]); + sesymself(flipfaces[1]); + // Check if there already exists a subface. + tspivot(botcastets[0], checksh); + if (checksh.sh == NULL) { + tsbond(botcastets[0], flipfaces[1]); + fsymself(botcastets[0]); + sesymself(flipfaces[1]); + tsbond(botcastets[0], flipfaces[1]); + } else { + // An invalid 2-to-2 flip. Report a bug. + terminatetetgen(this, 2); + } + } // if (scount > 0) + } // if (checksubfaceflag) + + if (fc->chkencflag & 4) { + // Put two new tets into check list. + for (i = 0; i < 2; i++) { + enqueuetetrahedron(&(fliptets[i])); + } + } + + setpoint2tet(pa, (tetrahedron) fliptets[0].tet); + setpoint2tet(pb, (tetrahedron) fliptets[0].tet); + setpoint2tet(pc, (tetrahedron) fliptets[0].tet); + setpoint2tet(pd, (tetrahedron) fliptets[0].tet); + setpoint2tet(pe, (tetrahedron) fliptets[1].tet); + + if (hullflag > 0) { + if (dummyflag != 0) { + // Restore the original position of the points (for flipnm()). + if (dummyflag == -1) { + // e were dummypoint. Swap the two new tets. + newface = fliptets[0]; + fliptets[0] = fliptets[1]; + fliptets[1] = newface; + } else { + // a or b was dummypoint. + if (dummyflag == 1) { + eprevself(fliptets[0]); + enextself(fliptets[1]); + } else { // dummyflag == 2 + enextself(fliptets[0]); + eprevself(fliptets[1]); + } + } + } + } + + if (fc->enqflag > 0) { + // Queue faces which may be locally non-Delaunay. + // pa = org(fliptets[0]); // 'a' may be a new vertex. + enextesym(fliptets[0], newface); + flippush(flipstack, &newface); + eprevesym(fliptets[1], newface); + flippush(flipstack, &newface); + if (fc->enqflag > 1) { + //pb = dest(fliptets[0]); + eprevesym(fliptets[0], newface); + flippush(flipstack, &newface); + enextesym(fliptets[1], newface); + flippush(flipstack, &newface); + //pc = apex(fliptets[0]); + esym(fliptets[0], newface); + flippush(flipstack, &newface); + esym(fliptets[1], newface); + flippush(flipstack, &newface); + } + } + + recenttet = fliptets[0]; +} + +//============================================================================// +// // +// flip41() Perform a 4-to-1 flip (Remove a vertex). // +// // +// 'fliptets' is an array of four tetrahedra in the star of the removing // +// vertex 'p'. Let the four vertices in the star of p be a, b, c, and d. The // +// four tets in 'fliptets' are: [p,d,a,b], [p,d,b,c], [p,d,c,a], and [a,b,c, // +// p]. On return, 'fliptets[0]' is the new tet [a,b,c,d]. // +// // +// If 'hullflag' is set (> 0), one of the five vertices may be 'dummypoint'. // +// The 'hullsize' may be changed. Note that p may be dummypoint. In this // +// case, four hull tets are replaced by one real tet. // +// // +// If 'checksubface' flag is set (>0), it is possible that there are three // +// interior subfaces connecting at p. If so, a 3-to-1 flip is performed to // +// to remove p from the surface triangulation. // +// // +// If it is called by the routine incrementalflip(), we assume that d is the // +// newly inserted vertex. // +// // +//============================================================================// + +void tetgenmesh::flip41(triface* fliptets, int hullflag, flipconstraints *fc) +{ + triface topcastets[3], botcastet; + triface newface, neightet; + face flipshs[4]; + point pa, pb, pc, pd, pp; + int dummyflag = 0; // in {0, 1, 2, 3, 4} + int spivot = -1, scount = 0; + int t1ver; + int i; + + pa = org(fliptets[3]); + pb = dest(fliptets[3]); + pc = apex(fliptets[3]); + pd = dest(fliptets[0]); + pp = org(fliptets[0]); // The removing vertex. + + flip41count++; + + // Get the outer boundary faces. + for (i = 0; i < 3; i++) { + enext(fliptets[i], topcastets[i]); + fnextself(topcastets[i]); // [d,a,b,#], [d,b,c,#], [d,c,a,#] + enextself(topcastets[i]); // [a,b,d,#], [b,c,d,#], [c,a,d,#] + } + fsym(fliptets[3], botcastet); // [b,a,c,#] + + if (checksubfaceflag) { + // Check if there are three subfaces at 'p'. + // Re-use 'newface'. + for (i = 0; i < 3; i++) { + fnext(fliptets[3], newface); // [a,b,p,d],[b,c,p,d],[c,a,p,d]. + tspivot(newface, flipshs[i]); + if (flipshs[i].sh != NULL) { + spivot = i; // Remember this subface. + scount++; + } + enextself(fliptets[3]); + } + if (scount > 0) { + // There are three subfaces connecting at p. + if (scount < 3) { + // The new subface is one of {[a,b,d], [b,c,d], [c,a,d]}. + // Go to the tet containing the three subfaces. + fsym(topcastets[spivot], neightet); + // Get the three subfaces connecting at p. + for (i = 0; i < 3; i++) { + esym(neightet, newface); + tspivot(newface, flipshs[i]); + eprevself(neightet); + } + } else { + spivot = 3; // The new subface is [a,b,c]. + } + } + } // if (checksubfaceflag) + + + // Re-use fliptets[0] for [a,b,c,d]. + fliptets[0].ver = 11; + setelemmarker(fliptets[0].tet, 0); // Clean all flags. + // NOTE: the element attributes and volume constraint remain unchanged. + if (checksubsegflag) { + // Dealloc the space to subsegments. + if (fliptets[0].tet[8] != NULL) { + tet2segpool->dealloc((shellface *) fliptets[0].tet[8]); + fliptets[0].tet[8] = NULL; + } + } + if (checksubfaceflag) { + // Dealloc the space to subfaces. + if (fliptets[0].tet[9] != NULL) { + tet2subpool->dealloc((shellface *) fliptets[0].tet[9]); + fliptets[0].tet[9] = NULL; + } + } + // Delete the other three tets. + for (i = 1; i < 4; i++) { + tetrahedrondealloc(fliptets[i].tet); + } + + if (pp != dummypoint) { + // Mark the point pp as unused. + setpointtype(pp, UNUSEDVERTEX); + unuverts++; + } + + // Create the new tet [a,b,c,d]. + if (hullflag > 0) { + // One of the five vertices may be 'dummypoint'. + if (pa == dummypoint) { + // pa is dummypoint. + setvertices(fliptets[0], pc, pb, pd, pa); + esymself(fliptets[0]); // [b,c,a,d] + eprevself(fliptets[0]); // [a,b,c,d] + dummyflag = 1; + } else if (pb == dummypoint) { + setvertices(fliptets[0], pa, pc, pd, pb); + esymself(fliptets[0]); // [c,a,b,d] + enextself(fliptets[0]); // [a,b,c,d] + dummyflag = 2; + } else if (pc == dummypoint) { + setvertices(fliptets[0], pb, pa, pd, pc); + esymself(fliptets[0]); // [a,b,c,d] + dummyflag = 3; + } else if (pd == dummypoint) { + setvertices(fliptets[0], pa, pb, pc, pd); + dummyflag = 4; + } else { + setvertices(fliptets[0], pa, pb, pc, pd); + if (pp == dummypoint) { + dummyflag = -1; + } else { + dummyflag = 0; + } + } + if (dummyflag > 0) { + // We deleted 3 hull tets, and create 1 hull tet. + hullsize -= 2; + } else if (dummyflag < 0) { + // We deleted 4 hull tets. + hullsize -= 4; + // meshedges does not change. + } + } else { + setvertices(fliptets[0], pa, pb, pc, pd); + } + + if (fc->remove_ndelaunay_edge) { // calc_tetprism_vol + REAL volneg[4], volpos[1], vol_diff; + if (dummyflag > 0) { + if (pa == dummypoint) { + volneg[0] = 0.; + volneg[1] = tetprismvol(pp, pd, pb, pc); + volneg[2] = 0.; + volneg[3] = 0.; + } else if (pb == dummypoint) { + volneg[0] = 0.; + volneg[1] = 0.; + volneg[2] = tetprismvol(pp, pd, pc, pa); + volneg[3] = 0.; + } else if (pc == dummypoint) { + volneg[0] = tetprismvol(pp, pd, pa, pb); + volneg[1] = 0.; + volneg[2] = 0.; + volneg[3] = 0.; + } else { // pd == dummypoint + volneg[0] = 0.; + volneg[1] = 0.; + volneg[2] = 0.; + volneg[3] = tetprismvol(pa, pb, pc, pp); + } + volpos[0] = 0.; + } else if (dummyflag < 0) { + volneg[0] = 0.; + volneg[1] = 0.; + volneg[2] = 0.; + volneg[3] = 0.; + volpos[0] = tetprismvol(pa, pb, pc, pd); + } else { + volneg[0] = tetprismvol(pp, pd, pa, pb); + volneg[1] = tetprismvol(pp, pd, pb, pc); + volneg[2] = tetprismvol(pp, pd, pc, pa); + volneg[3] = tetprismvol(pa, pb, pc, pp); + volpos[0] = tetprismvol(pa, pb, pc, pd); + } + vol_diff = volpos[0] - volneg[0] - volneg[1] - volneg[2] - volneg[3]; + fc->tetprism_vol_sum += vol_diff; // Update the total sum. + } + + // Bond the new tet to adjacent tets. + for (i = 0; i < 3; i++) { + esym(fliptets[0], newface); // At faces [b,a,d], [c,b,d], [a,c,d]. + bond(newface, topcastets[i]); + enextself(fliptets[0]); + } + bond(fliptets[0], botcastet); + + if (checksubsegflag) { + face checkseg; + // Bond 6 segments (at edges of [a,b,c,d]) if there there are. + for (i = 0; i < 3; i++) { + eprev(topcastets[i], newface); // At edges [d,a],[d,b],[d,c]. + if (issubseg(newface)) { + tsspivot1(newface, checkseg); + esym(fliptets[0], newface); + enextself(newface); // At edges [a,d], [b,d], [c,d]. + tssbond1(newface, checkseg); + sstbond1(checkseg, newface); + if (fc->chkencflag & 1) { + enqueuesubface(badsubsegs, &checkseg); + } + } + enextself(fliptets[0]); + } + for (i = 0; i < 3; i++) { + if (issubseg(topcastets[i])) { + tsspivot1(topcastets[i], checkseg); // At edges [a,b],[b,c],[c,a]. + tssbond1(fliptets[0], checkseg); + sstbond1(checkseg, fliptets[0]); + if (fc->chkencflag & 1) { + enqueuesubface(badsubsegs, &checkseg); + } + } + enextself(fliptets[0]); + } + } + + if (checksubfaceflag) { + face checksh; + // Bond 4 subfaces (at faces of [a,b,c,d]) if there are. + for (i = 0; i < 3; i++) { + if (issubface(topcastets[i])) { + tspivot(topcastets[i], checksh); // At faces [a,b,d],[b,c,d],[c,a,d] + esym(fliptets[0], newface); // At faces [b,a,d],[c,b,d],[a,c,d] + sesymself(checksh); + tsbond(newface, checksh); + if (fc->chkencflag & 2) { + enqueuesubface(badsubfacs, &checksh); + } + } + enextself(fliptets[0]); + } + if (issubface(botcastet)) { + tspivot(botcastet, checksh); // At face [b,a,c] + sesymself(checksh); + tsbond(fliptets[0], checksh); + if (fc->chkencflag & 2) { + enqueuesubface(badsubfacs, &checksh); + } + } + + if (spivot >= 0) { + // Perform a 3-to-1 flip in surface triangulation. + // Depending on the value of 'spivot', the three subfaces are: + // - 0: [a,b,p], [b,d,p], [d,a,p] + // - 1: [b,c,p], [c,d,p], [d,b,p] + // - 2: [c,a,p], [a,d,p], [d,c,p] + // - 3: [a,b,p], [b,c,p], [c,a,p] + // Adjust the three subfaces such that their origins are p, i.e., + // - 3: [p,a,b], [p,b,c], [p,c,a]. (Required by the flip31()). + for (i = 0; i < 3; i++) { + senext2self(flipshs[i]); + } + flip31(flipshs, 0); + // Delete the three old subfaces. + for (i = 0; i < 3; i++) { + shellfacedealloc(subfaces, flipshs[i].sh); + } + if (spivot < 3) { + // // Bond the new subface to the new tet [a,b,c,d]. + tsbond(topcastets[spivot], flipshs[3]); + fsym(topcastets[spivot], newface); + sesym(flipshs[3], checksh); + tsbond(newface, checksh); + } else { + // Bound the new subface [a,b,c] to the new tet [a,b,c,d]. + tsbond(fliptets[0], flipshs[3]); + fsym(fliptets[0], newface); + sesym(flipshs[3], checksh); + tsbond(newface, checksh); + } + } // if (spivot > 0) + } // if (checksubfaceflag) + + if (fc->chkencflag & 4) { + enqueuetetrahedron(&(fliptets[0])); + } + + // Update the point-to-tet map. + setpoint2tet(pa, (tetrahedron) fliptets[0].tet); + setpoint2tet(pb, (tetrahedron) fliptets[0].tet); + setpoint2tet(pc, (tetrahedron) fliptets[0].tet); + setpoint2tet(pd, (tetrahedron) fliptets[0].tet); + + if (fc->enqflag > 0) { + // Queue faces which may be locally non-Delaunay. + flippush(flipstack, &(fliptets[0])); // [a,b,c] (opposite to new point). + if (fc->enqflag > 1) { + for (i = 0; i < 3; i++) { + esym(fliptets[0], newface); + flippush(flipstack, &newface); + enextself(fliptets[0]); + } + } + } + + recenttet = fliptets[0]; +} + +//============================================================================// +// // +// flipnm() Flip an edge through a sequence of elementary flips. // +// // +// 'abtets' is an array of 'n' tets in the star of edge [a,b].These tets are // +// ordered in a counterclockwise cycle with respect to the vector a->b, i.e., // +// use the right-hand rule. // +// // +// 'level' (>= 0) indicates the current link level. If 'level > 0', we are // +// flipping a link edge of an edge [a',b'], and 'abedgepivot' indicates // +// which link edge, i.e., [c',b'] or [a',c'], is [a,b] These two parameters // +// allow us to determine the new tets after a 3-to-2 flip, i.e., tets that // +// do not inside the reduced star of edge [a',b']. // +// // +// If the flag 'fc->unflip' is set, this routine un-does the flips performed // +// in flipnm([a,b]) so that the mesh is returned to its original state // +// before doing the flipnm([a,b]) operation. // +// // +// The return value is an integer nn, where nn <= n. If nn is 2, then the // +// edge is flipped. The first and the second tets in 'abtets' are new tets. // +// Otherwise, nn > 2, the edge is not flipped, and nn is the number of tets // +// in the current star of [a,b]. // +// // +// ASSUMPTIONS: // +// - Neither a nor b is 'dummypoint'. // +// - [a,b] must not be a segment. // +// // +//============================================================================// + +int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, + flipconstraints* fc) +{ + triface fliptets[3], spintet, flipedge; + triface *tmpabtets, *parytet; + point pa, pb, pc, pd, pe, pf; + REAL ori; + int hullflag, hulledgeflag; + int reducflag, rejflag; + int reflexlinkedgecount; + int edgepivot; + int n1, nn; + int t1ver; + int i, j; + + pa = org(abtets[0]); + pb = dest(abtets[0]); + + if (n > 3) { + // Try to reduce the size of the Star(ab) by flipping a face in it. + reflexlinkedgecount = 0; + + for (i = 0; i < n; i++) { + // Let the face of 'abtets[i]' be [a,b,c]. + if (checksubfaceflag) { + if (issubface(abtets[i])) { + continue; // Skip a subface. + } + } + // Do not flip this face if it is involved in two Stars. + if ((elemcounter(abtets[i]) > 1) || + (elemcounter(abtets[(i - 1 + n) % n]) > 1)) { + continue; + } + + pc = apex(abtets[i]); + pd = apex(abtets[(i + 1) % n]); + pe = apex(abtets[(i - 1 + n) % n]); + if ((pd == dummypoint) || (pe == dummypoint)) { + continue; // [a,b,c] is a hull face. + } + + + // Decide whether [a,b,c] is flippable or not. + reducflag = 0; + + hullflag = (pc == dummypoint); // pc may be dummypoint. + hulledgeflag = 0; + if (hullflag == 0) { + ori = orient3d(pb, pc, pd, pe); // Is [b,c] locally convex? + if (ori > 0) { + ori = orient3d(pc, pa, pd, pe); // Is [c,a] locally convex? + if (ori > 0) { + // Test if [a,b] is locally convex OR flat. + ori = orient3d(pa, pb, pd, pe); + if (ori > 0) { + // Found a 2-to-3 flip: [a,b,c] => [e,d] + reducflag = 1; + } else if (ori == 0) { + // [a,b] is flat. + if (n == 4) { + // The "flat" tet can be removed immediately by a 3-to-2 flip. + reducflag = 1; + // Check if [e,d] is a hull edge. + pf = apex(abtets[(i + 2) % n]); + hulledgeflag = (pf == dummypoint); + } + } + } + } + if (!reducflag) { + reflexlinkedgecount++; + } + } else { + // 'c' is dummypoint. + if (n == 4) { + // Let the vertex opposite to 'c' is 'f'. + // A 4-to-4 flip is possible if the two tets [d,e,f,a] and [e,d,f,b] + // are valid tets. + // Note: When the mesh is not convex, it is possible that [a,b] is + // locally non-convex (at hull faces [a,b,e] and [b,a,d]). + // In this case, an edge flip [a,b] to [e,d] is still possible. + pf = apex(abtets[(i + 2) % n]); + ori = orient3d(pd, pe, pf, pa); + if (ori < 0) { + ori = orient3d(pe, pd, pf, pb); + if (ori < 0) { + // Found a 4-to-4 flip: [a,b] => [e,d] + reducflag = 1; + ori = 0; // Signal as a 4-to-4 flip (like a co-planar case). + hulledgeflag = 1; // [e,d] is a hull edge. + } + } + } + } // if (hullflag) + + if (reducflag) { + if (nonconvex && hulledgeflag) { + // We will create a hull edge [e,d]. Make sure it does not exist. + if (getedge(pe, pd, &spintet)) { + // The 2-to-3 flip is not a topological valid flip. + reducflag = 0; + } + } + } + + + + if (reducflag) { + triface checktet = abtets[i]; + if (!valid_constrained_f23(checktet, pd, pe)) { + reducflag = 0; + } + } + + if (reducflag) { + // [a,b,c] could be removed by a 2-to-3 flip. + rejflag = 0; + if (fc->checkflipeligibility) { + // Check if the flip can be performed. + rejflag = checkflipeligibility(1, pa, pb, pc, pd, pe, level, + abedgepivot, fc); + } + if (!rejflag) { + // Do flip: [a,b,c] => [e,d]. + fliptets[0] = abtets[i]; + fsym(fliptets[0], fliptets[1]); // abtets[i-1]. + flip23(fliptets, hullflag, fc); + + // Shrink the array 'abtets', maintain the original order. + // Two tets 'abtets[i-1] ([a,b,e,c])' and 'abtets[i] ([a,b,c,d])' + // are flipped, i.e., they do not in Star(ab) anymore. + // 'fliptets[0]' ([e,d,a,b]) is in Star(ab), it is saved in + // 'abtets[i-1]' (adjust it to be [a,b,e,d]), see below: + // + // before after + // [0] |___________| [0] |___________| + // ... |___________| ... |___________| + // [i-1] |_[a,b,e,c]_| [i-1] |_[a,b,e,d]_| + // [i] |_[a,b,c,d]_| --> [i] |_[a,b,d,#]_| + // [i+1] |_[a,b,d,#]_| [i+1] |_[a,b,#,*]_| + // ... |___________| ... |___________| + // [n-2] |___________| [n-2] |___________| + // [n-1] |___________| [n-1] |_[i]_2-t-3_| + // + edestoppoself(fliptets[0]); // [a,b,e,d] + // Increase the counter of this new tet (it is in Star(ab)). + increaseelemcounter(fliptets[0]); + abtets[(i - 1 + n) % n] = fliptets[0]; + for (j = i; j < n - 1; j++) { + abtets[j] = abtets[j + 1]; // Upshift + } + // The last entry 'abtets[n-1]' is empty. It is used in two ways: + // (i) it remembers the vertex 'c' (in 'abtets[n-1].tet'), and + // (ii) it remembers the position [i] where this flip took place. + // These information let us to either undo this flip or recover + // the original edge link (for collecting new created tets). + abtets[n - 1].tet = (tetrahedron *) pc; + abtets[n - 1].ver = 0; // Clear it. + // 'abtets[n - 1].ver' is in range [0,11] -- only uses 4 bits. + // Use the 5th bit in 'abtets[n - 1].ver' to signal a 2-to-3 flip. + abtets[n - 1].ver |= (1 << 4); + // The poisition [i] of this flip is saved above the 7th bit. + abtets[n - 1].ver |= (i << 6); + + if (fc->collectnewtets) { + // Push the two new tets [e,d,b,c] and [e,d,c,a] into a stack. + // Re-use the global array 'cavetetlist'. + for (j = 1; j < 3; j++) { + cavetetlist->newindex((void **) &parytet); + *parytet = fliptets[j]; // fliptets[1], fliptets[2]. + } + } + + // Star(ab) is reduced. Try to flip the edge [a,b]. + nn = flipnm(abtets, n - 1, level, abedgepivot, fc); + + if (nn == 2) { + // The edge has been flipped. + return nn; + } else { // if (nn > 2) + // The edge is not flipped. + if (fc->unflip || (ori == 0)) { + // Undo the previous 2-to-3 flip, i.e., do a 3-to-2 flip to + // transform [e,d] => [a,b,c]. + // 'ori == 0' means that the previous flip created a degenerated + // tet. It must be removed. + // Remember that 'abtets[i-1]' is [a,b,e,d]. We can use it to + // find another two tets [e,d,b,c] and [e,d,c,a]. + fliptets[0] = abtets[(i-1 + (n-1)) % (n-1)]; // [a,b,e,d] + edestoppoself(fliptets[0]); // [e,d,a,b] + fnext(fliptets[0], fliptets[1]); // [1] is [e,d,b,c] + fnext(fliptets[1], fliptets[2]); // [2] is [e,d,c,a] + // Restore the two original tets in Star(ab). + flip32(fliptets, hullflag, fc); + // Marktest the two restored tets in Star(ab). + for (j = 0; j < 2; j++) { + increaseelemcounter(fliptets[j]); + } + // Expand the array 'abtets', maintain the original order. + for (j = n - 2; j>= i; j--) { + abtets[j + 1] = abtets[j]; // Downshift + } + // Insert the two new tets 'fliptets[0]' [a,b,c,d] and + // 'fliptets[1]' [b,a,c,e] into the (i-1)-th and i-th entries, + // respectively. + esym(fliptets[1], abtets[(i - 1 + n) % n]); // [a,b,e,c] + abtets[i] = fliptets[0]; // [a,b,c,d] + nn++; + if (fc->collectnewtets) { + // Pop two (flipped) tets from the stack. + cavetetlist->objects -= 2; + } + } // if (unflip || (ori == 0)) + } // if (nn > 2) + + if (!fc->unflip) { + // The flips are not reversed. The current Star(ab) can not be + // further reduced. Return its current size (# of tets). + return nn; + } + // unflip is set. + // Continue the search for flips. + } + } // if (reducflag) + } // i + + // The Star(ab) is not reduced. + if (reflexlinkedgecount > 0) { + // There are reflex edges in the Link(ab). + if (((b->fliplinklevel < 0) && (level < autofliplinklevel)) || + ((b->fliplinklevel >= 0) && (level < b->fliplinklevel))) { + // Try to reduce the Star(ab) by flipping a reflex edge in Link(ab). + for (i = 0; i < n; i++) { + // Do not flip this face [a,b,c] if there are two Stars involved. + if ((elemcounter(abtets[i]) > 1) || + (elemcounter(abtets[(i - 1 + n) % n]) > 1)) { + continue; + } + pc = apex(abtets[i]); + if (pc == dummypoint) { + continue; // [a,b] is a hull edge. + } + pd = apex(abtets[(i + 1) % n]); + pe = apex(abtets[(i - 1 + n) % n]); + if ((pd == dummypoint) || (pe == dummypoint)) { + continue; // [a,b,c] is a hull face. + } + + + edgepivot = 0; // No edge is selected yet. + + // Test if [b,c] is locally convex or flat. + ori = orient3d(pb, pc, pd, pe); + if (ori <= 0) { + // Select the edge [c,b]. + enext(abtets[i], flipedge); // [b,c,a,d] + edgepivot = 1; + } + if (!edgepivot) { + // Test if [c,a] is locally convex or flat. + ori = orient3d(pc, pa, pd, pe); + if (ori <= 0) { + // Select the edge [a,c]. + eprev(abtets[i], flipedge); // [c,a,b,d]. + edgepivot = 2; + } + } + + if (!edgepivot) continue; + + // An edge is selected. + if (checksubsegflag) { + // Do not flip it if it is a segment. + if (issubseg(flipedge)) { + if (fc->collectencsegflag) { + face checkseg, *paryseg; + tsspivot1(flipedge, checkseg); + if (!sinfected(checkseg)) { + // Queue this segment in list. + sinfect(checkseg); + caveencseglist->newindex((void **) &paryseg); + *paryseg = checkseg; + } + } + continue; + } + } + + // Try to flip the selected edge ([c,b] or [a,c]). + esymself(flipedge); + // Count the number of tets at the edge. + int subface_count = 0; + n1 = 0; + j = 0; // Sum of the star counters. + spintet = flipedge; + while (1) { + if (issubface(spintet)) subface_count++; + n1++; + j += (elemcounter(spintet)); + fnextself(spintet); + if (spintet.tet == flipedge.tet) break; + } + if (n1 < 3) { + // This is only possible when the mesh contains inverted + // elements. Reprot a bug. + terminatetetgen(this, 2); + } + if (j > 2) { + // The Star(flipedge) overlaps other Stars. + continue; // Do not flip this edge. + } + + if (fc->noflip_in_surface) { + if (subface_count > 0) { + continue; + } + } + + if ((b->flipstarsize > 0) && (n1 > b->flipstarsize)) { + // The star size exceeds the given limit. + continue; // Do not flip it. + } + + // Allocate spaces for Star(flipedge). + tmpabtets = new triface[n1]; + // Form the Star(flipedge). + spintet = flipedge; + for (j = 0; j < n1; j++) { + tmpabtets[j] = spintet; + // Increase the star counter of this tet. + increaseelemcounter(tmpabtets[j]); + fnextself(spintet); + } + + // Try to flip the selected edge away. + nn = flipnm(tmpabtets, n1, level + 1, edgepivot, fc); + + if (nn == 2) { + // The edge is flipped. Star(ab) is reduced. + // Shrink the array 'abtets', maintain the original order. + if (edgepivot == 1) { + // 'tmpabtets[0]' is [d,a,e,b] => contains [a,b]. + spintet = tmpabtets[0]; // [d,a,e,b] + enextself(spintet); + esymself(spintet); + enextself(spintet); // [a,b,e,d] + } else { + // 'tmpabtets[1]' is [b,d,e,a] => contains [a,b]. + spintet = tmpabtets[1]; // [b,d,e,a] + eprevself(spintet); + esymself(spintet); + eprevself(spintet); // [a,b,e,d] + } // edgepivot == 2 + increaseelemcounter(spintet); // It is in Star(ab). + // Put the new tet at [i-1]-th entry. + abtets[(i - 1 + n) % n] = spintet; + for (j = i; j < n - 1; j++) { + abtets[j] = abtets[j + 1]; // Upshift + } + // Remember the flips in the last entry of the array 'abtets'. + // They can be used to recover the flipped edge. + abtets[n - 1].tet = (tetrahedron *) tmpabtets; // The star(fedge). + abtets[n - 1].ver = 0; // Clear it. + // Use the 1st and 2nd bit to save 'edgepivot' (1 or 2). + abtets[n - 1].ver |= edgepivot; + // Use the 6th bit to signal this n1-to-m1 flip. + abtets[n - 1].ver |= (1 << 5); + // The poisition [i] of this flip is saved from 7th to 19th bit. + abtets[n - 1].ver |= (i << 6); + // The size of the star 'n1' is saved from 20th bit. + abtets[n - 1].ver |= (n1 << 19); + + // Remember the flipped link vertex 'c'. It can be used to recover + // the original edge link of [a,b], and to collect new tets. + tmpabtets[0].tet = (tetrahedron *) pc; + tmpabtets[0].ver = (1 << 5); // Flag it as a vertex handle. + + // Continue to flip the edge [a,b]. + nn = flipnm(abtets, n - 1, level, abedgepivot, fc); + + if (nn == 2) { + // The edge has been flipped. + return nn; + } else { // if (nn > 2) { + // The edge is not flipped. + if (fc->unflip) { + // Recover the flipped edge ([c,b] or [a,c]). + // The sequence of flips are saved in 'tmpabtets'. + // abtets[(i-1) % (n-1)] is [a,b,e,d], i.e., the tet created by + // the flipping of edge [c,b] or [a,c].It must still exist in + // Star(ab). It is the start tet to recover the flipped edge. + if (edgepivot == 1) { + // The flip edge is [c,b]. + tmpabtets[0] = abtets[((i-1)+(n-1))%(n-1)]; // [a,b,e,d] + eprevself(tmpabtets[0]); + esymself(tmpabtets[0]); + eprevself(tmpabtets[0]); // [d,a,e,b] + fsym(tmpabtets[0], tmpabtets[1]); // [a,d,e,c] + } else { + // The flip edge is [a,c]. + tmpabtets[1] = abtets[((i-1)+(n-1))%(n-1)]; // [a,b,e,d] + enextself(tmpabtets[1]); + esymself(tmpabtets[1]); + enextself(tmpabtets[1]); // [b,d,e,a] + fsym(tmpabtets[1], tmpabtets[0]); // [d,b,e,c] + } // if (edgepivot == 2) + + // Recover the flipped edge ([c,b] or [a,c]). + flipnm_post(tmpabtets, n1, 2, edgepivot, fc); + + // Insert the two recovered tets into Star(ab). + for (j = n - 2; j >= i; j--) { + abtets[j + 1] = abtets[j]; // Downshift + } + if (edgepivot == 1) { + // tmpabtets[0] is [c,b,d,a] ==> contains [a,b] + // tmpabtets[1] is [c,b,a,e] ==> contains [a,b] + // tmpabtets[2] is [c,b,e,d] + fliptets[0] = tmpabtets[1]; + enextself(fliptets[0]); + esymself(fliptets[0]); // [a,b,e,c] + fliptets[1] = tmpabtets[0]; + esymself(fliptets[1]); + eprevself(fliptets[1]); // [a,b,c,d] + } else { + // tmpabtets[0] is [a,c,d,b] ==> contains [a,b] + // tmpabtets[1] is [a,c,b,e] ==> contains [a,b] + // tmpabtets[2] is [a,c,e,d] + fliptets[0] = tmpabtets[1]; + eprevself(fliptets[0]); + esymself(fliptets[0]); // [a,b,e,c] + fliptets[1] = tmpabtets[0]; + esymself(fliptets[1]); + enextself(fliptets[1]); // [a,b,c,d] + } // edgepivot == 2 + for (j = 0; j < 2; j++) { + increaseelemcounter(fliptets[j]); + } + // Insert the two recovered tets into Star(ab). + abtets[(i - 1 + n) % n] = fliptets[0]; + abtets[i] = fliptets[1]; + nn++; + // Release the allocated spaces. + delete [] tmpabtets; + } // if (unflip) + } // if (nn > 2) + + if (!fc->unflip) { + // The flips are not reversed. The current Star(ab) can not be + // further reduced. Return its size (# of tets). + return nn; + } + // unflip is set. + // Continue the search for flips. + } else { + // The selected edge is not flipped. + if (!fc->unflip) { + // Release the memory used in this attempted flip. + flipnm_post(tmpabtets, n1, nn, edgepivot, fc); + } + // Decrease the star counters of tets in Star(flipedge). + for (j = 0; j < nn; j++) { + decreaseelemcounter(tmpabtets[j]); + } + // Release the allocated spaces. + delete [] tmpabtets; + } + } // i + } // if (level...) + } // if (reflexlinkedgecount > 0) + } else { + // Check if a 3-to-2 flip is possible. + // Let the three apexes be c, d,and e. Hull tets may be involved. If so, + // we rearrange them such that the vertex e is dummypoint. + hullflag = 0; + + if (apex(abtets[0]) == dummypoint) { + pc = apex(abtets[1]); + pd = apex(abtets[2]); + pe = apex(abtets[0]); + hullflag = 1; + } else if (apex(abtets[1]) == dummypoint) { + pc = apex(abtets[2]); + pd = apex(abtets[0]); + pe = apex(abtets[1]); + hullflag = 2; + } else { + pc = apex(abtets[0]); + pd = apex(abtets[1]); + pe = apex(abtets[2]); + hullflag = (pe == dummypoint) ? 3 : 0; + } + + reducflag = 0; + rejflag = 0; + + + if (hullflag == 0) { + // Make sure that no inverted tet will be created, i.e. the new tets + // [d,c,e,a] and [c,d,e,b] must be valid tets. + ori = orient3d(pd, pc, pe, pa); + if (ori < 0) { + ori = orient3d(pc, pd, pe, pb); + if (ori < 0) { + reducflag = 1; + } + } + } else { + // [a,b] is a hull edge. + // Note: This can happen when it is in the middle of a 4-to-4 flip. + // Note: [a,b] may even be a non-convex hull edge. + if (!nonconvex) { + // The mesh is convex, only do flip if it is a coplanar hull edge. + ori = orient3d(pa, pb, pc, pd); + if (ori == 0) { + reducflag = 1; + } + } else { // nonconvex + reducflag = 1; + } + if (reducflag == 1) { + // [a,b], [a,b,c] and [a,b,d] are on the convex hull. + // Make sure that no inverted tet will be created. + point searchpt = NULL, chkpt; + REAL bigvol = 0.0, ori1, ori2; + // Search an interior vertex which is an apex of edge [c,d]. + // In principle, it can be arbitrary interior vertex. To avoid + // numerical issue, we choose the vertex which belongs to a tet + // 't' at edge [c,d] and 't' has the biggest volume. + fliptets[0] = abtets[hullflag % 3]; // [a,b,c,d]. + eorgoppoself(fliptets[0]); // [d,c,b,a] + spintet = fliptets[0]; + while (1) { + fnextself(spintet); + chkpt = oppo(spintet); + if (chkpt == pb) break; + if ((chkpt != dummypoint) && (apex(spintet) != dummypoint)) { + ori = -orient3d(pd, pc, apex(spintet), chkpt); + if (ori > bigvol) { + bigvol = ori; + searchpt = chkpt; + } + } + } + if (searchpt != NULL) { + // Now valid the configuration. + ori1 = orient3d(pd, pc, searchpt, pa); + ori2 = orient3d(pd, pc, searchpt, pb); + if (ori1 * ori2 >= 0.0) { + reducflag = 0; // Not valid. + } else { + ori1 = orient3d(pa, pb, searchpt, pc); + ori2 = orient3d(pa, pb, searchpt, pd); + if (ori1 * ori2 >= 0.0) { + reducflag = 0; // Not valid. + } + } + } else { + // No valid searchpt is found. + reducflag = 0; // Do not flip it. + } + } // if (reducflag == 1) + } // if (hullflag == 1) + + if (reducflag) { + // A 3-to-2 flip is possible. + if (checksubfaceflag) { + // This edge (must not be a segment) can be flipped ONLY IF it belongs + // to either 0 or 2 subfaces. In the latter case, a 2-to-2 flip in + // the surface mesh will be automatically performed within the + // 3-to-2 flip. + nn = 0; + edgepivot = -1; // Re-use it. + for (j = 0; j < 3; j++) { + if (issubface(abtets[j])) { + nn++; // Found a subface. + } else { + edgepivot = j; + } + } + if (nn == 1) { + // Found only 1 subface containing this edge. This can happen in + // the boundary recovery phase. The neighbor subface is not yet + // recovered. This edge should not be flipped at this moment. + rejflag = 1; + } else if (nn == 2) { + // Found two subfaces. A 2-to-2 flip is possible. Validate it. + // Below we check if the two faces [p,q,a] and [p,q,b] are subfaces. + eorgoppo(abtets[(edgepivot + 1) % 3], spintet); // [q,p,b,a] + if (issubface(spintet)) { + rejflag = 1; // Conflict to a 2-to-2 flip. + } else { + esymself(spintet); + if (issubface(spintet)) { + rejflag = 1; // Conflict to a 2-to-2 flip. + } + } + } else if (nn == 3) { + // Report a bug. + terminatetetgen(this, 2); + } + } + + if (!rejflag) { + if (!valid_constrained_f32(abtets, pa, pb)) { + rejflag = 1; + } + } + + if (!rejflag && fc->checkflipeligibility) { + // Here we must exchange 'a' and 'b'. Since in the check... function, + // we assume the following point sequence, 'a,b,c,d,e', where + // the face [a,b,c] will be flipped and the edge [e,d] will be + // created. The two new tets are [a,b,c,d] and [b,a,c,e]. + rejflag = checkflipeligibility(2, pc, pd, pe, pb, pa, level, + abedgepivot, fc); + } + if (!rejflag) { + // Do flip: [a,b] => [c,d,e] + flip32(abtets, hullflag, fc); + if (fc->remove_ndelaunay_edge) { + if (level == 0) { + // It is the desired removing edge. Check if we have improved + // the objective function. + if ((fc->tetprism_vol_sum >= 0.0) || + (fabs(fc->tetprism_vol_sum) < fc->bak_tetprism_vol)) { + // No improvement! flip back: [c,d,e] => [a,b]. + flip23(abtets, hullflag, fc); + // Increase the element counter -- They are in cavity. + for (j = 0; j < 3; j++) { + increaseelemcounter(abtets[j]); + } + return 3; + } + } // if (level == 0) + } + if (fc->collectnewtets) { + // Collect new tets. + if (level == 0) { + // Push the two new tets into stack. + for (j = 0; j < 2; j++) { + cavetetlist->newindex((void **) &parytet); + *parytet = abtets[j]; + } + } else { + // Only one of the new tets is collected. The other one is inside + // the reduced edge star. 'abedgepivot' is either '1' or '2'. + cavetetlist->newindex((void **) &parytet); + if (abedgepivot == 1) { // [c,b] + *parytet = abtets[1]; + } else { + *parytet = abtets[0]; + } + } + } // if (fc->collectnewtets) + return 2; + } + } // if (reducflag) + } // if (n == 3) + + // The current (reduced) Star size. + return n; +} + +//============================================================================// +// // +// flipnm_post() Post process a n-to-m flip. // +// // +// IMPORTANT: This routine only works when there is no other flip operation // +// is done after flipnm([a,b]) which attempts to remove an edge [a,b]. // +// // +// 'abtets' is an array of 'n' (>= 3) tets which are in the original star of // +// [a,b] before flipnm([a,b]). 'nn' (< n) is the value returned by flipnm. // +// If 'nn == 2', the edge [a,b] has been flipped. 'abtets[0]' and 'abtets[1]' // +// are [c,d,e,b] and [d,c,e,a], i.e., a 2-to-3 flip can recover the edge [a, // +// b] and its initial Star([a,b]). If 'nn >= 3' edge [a,b] still exists in // +// current mesh and 'nn' is the current number of tets in Star([a,b]). // +// // +// Each 'abtets[i]', where nn <= i < n, saves either a 2-to-3 flip or a // +// flipnm([p1,p2]) operation ([p1,p2] != [a,b]) which created the tet // +// 'abtets[t-1]', where '0 <= t <= i'. These information can be used to // +// undo the flips performed in flipnm([a,b]) or to collect new tets created // +// by the flipnm([a,b]) operation. // +// // +// Default, this routine only walks through the flips and frees the spaces // +// allocated during the flipnm([a,b]) operation. // +// // +// If the flag 'fc->unflip' is set, this routine un-does the flips performed // +// in flipnm([a,b]) so that the mesh is returned to its original state // +// before doing the flipnm([a,b]) operation. // +// // +// // +//============================================================================// + +int tetgenmesh::flipnm_post(triface* abtets, int n, int nn, int abedgepivot, + flipconstraints* fc) +{ + triface fliptets[3], flipface; + triface *tmpabtets; + int fliptype; + int edgepivot; + int t, n1; + int i, j; + + + if (nn == 2) { + // The edge [a,b] has been flipped. + // 'abtets[0]' is [c,d,e,b] or [#,#,#,b]. + // 'abtets[1]' is [d,c,e,a] or [#,#,#,a]. + if (fc->unflip) { + // Do a 2-to-3 flip to recover the edge [a,b]. There may be hull tets. + flip23(abtets, 1, fc); + if (fc->collectnewtets) { + // Pop up new (flipped) tets from the stack. + if (abedgepivot == 0) { + // Two new tets were collected. + cavetetlist->objects -= 2; + } else { + // Only one of the two new tets was collected. + cavetetlist->objects -= 1; + } + } + } + // The initial size of Star(ab) is 3. + nn++; + } + + // Walk through the performed flips. + for (i = nn; i < n; i++) { + // At the beginning of each step 'i', the size of the Star([a,b]) is 'i'. + // At the end of this step, the size of the Star([a,b]) is 'i+1'. + // The sizes of the Link([a,b]) are the same. + fliptype = ((abtets[i].ver >> 4) & 3); // 0, 1, or 2. + if (fliptype == 1) { + // It was a 2-to-3 flip: [a,b,c]->[e,d]. + t = (abtets[i].ver >> 6); + if (fc->unflip) { + if (b->verbose > 3) { + printf(" Recover a 2-to-3 flip at f[%d].\n", t); + } + // 'abtets[(t-1)%i]' is the tet [a,b,e,d] in current Star(ab), i.e., + // it is created by a 2-to-3 flip [a,b,c] => [e,d]. + fliptets[0] = abtets[((t - 1) + i) % i]; // [a,b,e,d] + eprevself(fliptets[0]); + esymself(fliptets[0]); + enextself(fliptets[0]); // [e,d,a,b] + fnext(fliptets[0], fliptets[1]); // [e,d,b,c] + fnext(fliptets[1], fliptets[2]); // [e,d,c,a] + // Do a 3-to-2 flip: [e,d] => [a,b,c]. + // NOTE: hull tets may be invloved. + flip32(fliptets, 1, fc); + // Expand the array 'abtets', maintain the original order. + // The new array length is (i+1). + for (j = i - 1; j >= t; j--) { + abtets[j + 1] = abtets[j]; // Downshift + } + // The tet abtets[(t-1)%i] is deleted. Insert the two new tets + // 'fliptets[0]' [a,b,c,d] and 'fliptets[1]' [b,a,c,e] into + // the (t-1)-th and t-th entries, respectively. + esym(fliptets[1], abtets[((t-1) + (i+1)) % (i+1)]); // [a,b,e,c] + abtets[t] = fliptets[0]; // [a,b,c,d] + if (fc->collectnewtets) { + // Pop up two (flipped) tets from the stack. + cavetetlist->objects -= 2; + } + } + } else if (fliptype == 2) { + tmpabtets = (triface *) (abtets[i].tet); + n1 = ((abtets[i].ver >> 19) & 8191); // \sum_{i=0^12}{2^i} = 8191 + edgepivot = (abtets[i].ver & 3); + t = ((abtets[i].ver >> 6) & 8191); + if (fc->unflip) { + if (b->verbose > 3) { + printf(" Recover a %d-to-m flip at e[%d] of f[%d].\n", n1, + edgepivot, t); + } + // Recover the flipped edge ([c,b] or [a,c]). + // abtets[(t - 1 + i) % i] is [a,b,e,d], i.e., the tet created by + // the flipping of edge [c,b] or [a,c]. It must still exist in + // Star(ab). Use it to recover the flipped edge. + if (edgepivot == 1) { + // The flip edge is [c,b]. + tmpabtets[0] = abtets[(t - 1 + i) % i]; // [a,b,e,d] + eprevself(tmpabtets[0]); + esymself(tmpabtets[0]); + eprevself(tmpabtets[0]); // [d,a,e,b] + fsym(tmpabtets[0], tmpabtets[1]); // [a,d,e,c] + } else { + // The flip edge is [a,c]. + tmpabtets[1] = abtets[(t - 1 + i) % i]; // [a,b,e,d] + enextself(tmpabtets[1]); + esymself(tmpabtets[1]); + enextself(tmpabtets[1]); // [b,d,e,a] + fsym(tmpabtets[1], tmpabtets[0]); // [d,b,e,c] + } // if (edgepivot == 2) + + // Do a n1-to-m1 flip to recover the flipped edge ([c,b] or [a,c]). + flipnm_post(tmpabtets, n1, 2, edgepivot, fc); + + // Insert the two recovered tets into the original Star(ab). + for (j = i - 1; j >= t; j--) { + abtets[j + 1] = abtets[j]; // Downshift + } + if (edgepivot == 1) { + // tmpabtets[0] is [c,b,d,a] ==> contains [a,b] + // tmpabtets[1] is [c,b,a,e] ==> contains [a,b] + // tmpabtets[2] is [c,b,e,d] + fliptets[0] = tmpabtets[1]; + enextself(fliptets[0]); + esymself(fliptets[0]); // [a,b,e,c] + fliptets[1] = tmpabtets[0]; + esymself(fliptets[1]); + eprevself(fliptets[1]); // [a,b,c,d] + } else { + // tmpabtets[0] is [a,c,d,b] ==> contains [a,b] + // tmpabtets[1] is [a,c,b,e] ==> contains [a,b] + // tmpabtets[2] is [a,c,e,d] + fliptets[0] = tmpabtets[1]; + eprevself(fliptets[0]); + esymself(fliptets[0]); // [a,b,e,c] + fliptets[1] = tmpabtets[0]; + esymself(fliptets[1]); + enextself(fliptets[1]); // [a,b,c,d] + } // edgepivot == 2 + // Insert the two recovered tets into Star(ab). + abtets[((t-1) + (i+1)) % (i+1)] = fliptets[0]; + abtets[t] = fliptets[1]; + } + else { + // Only free the spaces. + flipnm_post(tmpabtets, n1, 2, edgepivot, fc); + } // if (!unflip) + if (b->verbose > 3) { + printf(" Release %d spaces at f[%d].\n", n1, i); + } + delete [] tmpabtets; + } + } // i + + return 1; +} + +//============================================================================// +// // +// insertpoint() Insert a point into current tetrahedralization. // +// // +// The Bowyer-Watson (B-W) algorithm is used to add a new point p into the // +// tetrahedralization T. It first finds a "cavity", denoted as C, in T, C // +// consists of tetrahedra in T that "conflict" with p. If T is a Delaunay // +// tetrahedralization, then all boundary faces (triangles) of C are visible // +// by p, i.e.,C is star-shaped. We can insert p into T by first deleting all // +// tetrahedra in C, then creating new tetrahedra formed by boundary faces of // +// C and p. If T is not a DT, then C may be not star-shaped. It must be // +// modified so that it becomes star-shaped. // +// // +//============================================================================// + +int tetgenmesh::insertpoint(point insertpt, triface *searchtet, face *splitsh, + face *splitseg, insertvertexflags *ivf) +{ + arraypool *swaplist; + triface *cavetet, spintet, neightet, neineitet, *parytet; + triface oldtet, newtet, newneitet; + face checksh, neighsh, *parysh; + face checkseg, *paryseg; + point *pts, pa, pb, pc, *parypt; + enum locateresult loc = OUTSIDE; + REAL sign, ori; + REAL attrib, volume; + bool enqflag; + int t1ver; + int i, j, k, s; + + if (b->verbose > 2) { + printf(" Insert point %d\n", pointmark(insertpt)); + } + + // Locate the point. + if (searchtet->tet != NULL) { + loc = (enum locateresult) ivf->iloc; + } + + if (loc == OUTSIDE) { + if (searchtet->tet == NULL) { + if (!b->weighted) { + randomsample(insertpt, searchtet); + } else { + // Weighted DT. There may exist dangling vertex. + *searchtet = recenttet; + } + } + // Locate the point. + loc = locate(insertpt, searchtet); + } + + ivf->iloc = (int) loc; // The return value. + + if (b->weighted) { + if (loc != OUTSIDE) { + // Check if this vertex is regular. + pts = (point *) searchtet->tet; + sign = orient4d_s(pts[4], pts[5], pts[6], pts[7], insertpt, + pts[4][3], pts[5][3], pts[6][3], pts[7][3], + insertpt[3]); + if (sign > 0) { + // This new vertex lies above the lower hull. Do not insert it. + ivf->iloc = (int) NONREGULAR; + return 0; + } + } + } + + // Create the initial cavity C(p) which contains all tetrahedra that + // intersect p. It may include 1, 2, or n tetrahedra. + // If p lies on a segment or subface, also create the initial sub-cavity + // sC(p) which contains all subfaces (and segment) which intersect p. + + if (loc == OUTSIDE) { + flip14count++; + // The current hull will be enlarged. + // Add four adjacent boundary tets into list. + for (i = 0; i < 4; i++) { + decode(searchtet->tet[i], neightet); + neightet.ver = epivot[neightet.ver]; + cavebdrylist->newindex((void **) &parytet); + *parytet = neightet; + } + infect(*searchtet); + caveoldtetlist->newindex((void **) &parytet); + *parytet = *searchtet; + } else if (loc == INTETRAHEDRON) { + flip14count++; + // Add four adjacent boundary tets into list. + for (i = 0; i < 4; i++) { + decode(searchtet->tet[i], neightet); + neightet.ver = epivot[neightet.ver]; + cavebdrylist->newindex((void **) &parytet); + *parytet = neightet; + } + infect(*searchtet); + caveoldtetlist->newindex((void **) &parytet); + *parytet = *searchtet; + } else if (loc == ONFACE) { + flip26count++; + // Add six adjacent boundary tets into list. + j = (searchtet->ver & 3); // The current face number. + for (i = 1; i < 4; i++) { + decode(searchtet->tet[(j + i) % 4], neightet); + neightet.ver = epivot[neightet.ver]; + cavebdrylist->newindex((void **) &parytet); + *parytet = neightet; + } + decode(searchtet->tet[j], spintet); + j = (spintet.ver & 3); // The current face number. + for (i = 1; i < 4; i++) { + decode(spintet.tet[(j + i) % 4], neightet); + neightet.ver = epivot[neightet.ver]; + cavebdrylist->newindex((void **) &parytet); + *parytet = neightet; + } + infect(spintet); + caveoldtetlist->newindex((void **) &parytet); + *parytet = spintet; + infect(*searchtet); + caveoldtetlist->newindex((void **) &parytet); + *parytet = *searchtet; + + if (ivf->splitbdflag) { + if ((splitsh != NULL) && (splitsh->sh != NULL)) { + // Create the initial sub-cavity sC(p). + smarktest(*splitsh); + caveshlist->newindex((void **) &parysh); + *parysh = *splitsh; + } + } // if (splitbdflag) + } else if (loc == ONEDGE) { + flipn2ncount++; + // Add all adjacent boundary tets into list. + spintet = *searchtet; + while (1) { + eorgoppo(spintet, neightet); + decode(neightet.tet[neightet.ver & 3], neightet); + neightet.ver = epivot[neightet.ver]; + cavebdrylist->newindex((void **) &parytet); + *parytet = neightet; + edestoppo(spintet, neightet); + decode(neightet.tet[neightet.ver & 3], neightet); + neightet.ver = epivot[neightet.ver]; + cavebdrylist->newindex((void **) &parytet); + *parytet = neightet; + infect(spintet); + caveoldtetlist->newindex((void **) &parytet); + *parytet = spintet; + fnextself(spintet); + if (spintet.tet == searchtet->tet) break; + } // while (1) + + if (ivf->splitbdflag) { + // Create the initial sub-cavity sC(p). + if ((splitseg != NULL) && (splitseg->sh != NULL)) { + smarktest(*splitseg); + splitseg->shver = 0; + spivot(*splitseg, *splitsh); + } + if (splitsh != NULL) { + if (splitsh->sh != NULL) { + // Collect all subfaces share at this edge. + pa = sorg(*splitsh); + neighsh = *splitsh; + while (1) { + // Adjust the origin of its edge to be 'pa'. + if (sorg(neighsh) != pa) { + sesymself(neighsh); + } + // Add this face into list (in B-W cavity). + smarktest(neighsh); + caveshlist->newindex((void **) &parysh); + *parysh = neighsh; + // Add this face into face-at-splitedge list. + cavesegshlist->newindex((void **) &parysh); + *parysh = neighsh; + // Go to the next face at the edge. + spivotself(neighsh); + // Stop if all faces at the edge have been visited. + if (neighsh.sh == splitsh->sh) break; + if (neighsh.sh == NULL) break; + } // while (1) + } // if (not a dangling segment) + } + } // if (splitbdflag) + } else if (loc == INSTAR) { + // We assume that all tets in the star are given in 'caveoldtetlist', + // and they are all infected. + if (cavebdrylist->objects == 0) { + // Collect the boundary faces of the star. + for (i = 0; i < caveoldtetlist->objects; i++) { + cavetet = (triface *) fastlookup(caveoldtetlist, i); + // Check its 4 neighbor tets. + for (j = 0; j < 4; j++) { + decode(cavetet->tet[j], neightet); + if (!infected(neightet)) { + // It's a boundary face. + neightet.ver = epivot[neightet.ver]; + cavebdrylist->newindex((void **) &parytet); + *parytet = neightet; + } + } + } + } // if (cavebdrylist->objects == 0) + } else if (loc == ONVERTEX) { + // The point already exist. Do nothing and return. + return 0; + } else if (loc == ENCSUBFACE) { + ivf->iloc = (int) ENCSUBFACE; + return 0; + } else { + // Unknown case + terminatetetgen(this, 2); + } + + if (ivf->collect_inial_cavity_flag) { + tetrahedron **ptptr; + for (i = 0; i < caveoldtetlist->objects; i++) { + cavetet = (triface *) fastlookup(caveoldtetlist, i); + cave_oldtet_list->newindex((void **) &ptptr); + *ptptr = cavetet->tet; + } + // Do not insert this point. + insertpoint_abort(splitseg, ivf); + // ivf->iloc = NULLCAVITY; + return 0; + } // if (ivf->collect_inial_cavity_flag) + + if ((b->plc || b->quality) && (loc != INSTAR)) { + // Reject the new point if it lies too close to an existing point (b->plc), + // or it lies inside a protecting ball of near vertex (ivf->rejflag & 4). + // Collect the list of vertices of the initial cavity. + if (loc == OUTSIDE) { + pts = (point *) &(searchtet->tet[4]); + for (i = 0; i < 3; i++) { + cavetetvertlist->newindex((void **) &parypt); + *parypt = pts[i]; + } + } else if (loc == INTETRAHEDRON) { + pts = (point *) &(searchtet->tet[4]); + for (i = 0; i < 4; i++) { + cavetetvertlist->newindex((void **) &parypt); + *parypt = pts[i]; + } + } else if (loc == ONFACE) { + pts = (point *) &(searchtet->tet[4]); + for (i = 0; i < 3; i++) { + cavetetvertlist->newindex((void **) &parypt); + *parypt = pts[i]; + } + if (pts[3] != dummypoint) { + cavetetvertlist->newindex((void **) &parypt); + *parypt = pts[3]; + } + fsym(*searchtet, spintet); + if (oppo(spintet) != dummypoint) { + cavetetvertlist->newindex((void **) &parypt); + *parypt = oppo(spintet); + } + } else if (loc == ONEDGE) { + spintet = *searchtet; + cavetetvertlist->newindex((void **) &parypt); + *parypt = org(spintet); + cavetetvertlist->newindex((void **) &parypt); + *parypt = dest(spintet); + while (1) { + if (apex(spintet) != dummypoint) { + cavetetvertlist->newindex((void **) &parypt); + *parypt = apex(spintet); + } + fnextself(spintet); + if (spintet.tet == searchtet->tet) break; + } + } + + int rejptflag = (ivf->rejflag & 4); + REAL rd, ins_radius; + pts = NULL; + + for (i = 0; i < cavetetvertlist->objects; i++) { + parypt = (point *) fastlookup(cavetetvertlist, i); + rd = distance(*parypt, insertpt); + // Is the point very close to an existing point? + if (rd < minedgelength) { + if ((!create_a_shorter_edge(insertpt, *parypt)) && + (!ivf->ignore_near_vertex)) { + pts = parypt; + loc = NEARVERTEX; + break; + } + } + if (ivf->check_insert_radius) { //if (useinsertradius) { + ins_radius = getpointinsradius(*parypt); + if (ins_radius > 0.0) { + if (rd < ins_radius) { + if (!create_a_shorter_edge(insertpt, *parypt)) { + // Reject the isnertion of this vertex. + pts = parypt; + loc = ENCVERTEX; + break; + } + } + } + } + if (rejptflag) { + // Is the point encroaches upon an existing point? + if (rd < (0.5 * (*parypt)[pointmtrindex])) { + pts = parypt; + loc = ENCVERTEX; + break; + } + } + } + cavetetvertlist->restart(); // Clear the work list. + + if (pts != NULL) { + // The point is either too close to an existing vertex (NEARVERTEX) + // or encroaches upon (inside the protecting ball) of that vertex. + if (loc == NEARVERTEX) { + point2tetorg(*pts, *searchtet); + insertpoint_abort(splitseg, ivf); + ivf->iloc = (int) loc; + return 0; + } else { // loc == ENCVERTEX + // The point lies inside the protection ball. + point2tetorg(*pts, *searchtet); + insertpoint_abort(splitseg, ivf); + ivf->iloc = (int) loc; + return 0; + } + } + } // if ((b->plc || b->quality) && (loc != INSTAR)) + + + if (ivf->assignmeshsize) { + // Assign mesh size for the new point. + if (bgm != NULL) { + // Interpolate the mesh size from the background mesh. + bgm->decode(point2bgmtet(org(*searchtet)), neightet); + int bgmloc = (int) bgm->scout_point(insertpt, &neightet, 0); + if (bgmloc != (int) OUTSIDE) { + insertpt[pointmtrindex] = + bgm->getpointmeshsize(insertpt, &neightet, bgmloc); + setpoint2bgmtet(insertpt, bgm->encode(neightet)); + } + } else { + insertpt[pointmtrindex] = getpointmeshsize(insertpt,searchtet,(int)loc); + } + } // if (assignmeshsize) + + if (ivf->bowywat) { + // Update the cavity C(p) using the Bowyer-Watson algorithm. + swaplist = cavetetlist; + cavetetlist = cavebdrylist; + cavebdrylist = swaplist; + for (i = 0; i < cavetetlist->objects; i++) { + // 'cavetet' is an adjacent tet at outside of the cavity. + cavetet = (triface *) fastlookup(cavetetlist, i); + // The tet may be tested and included in the (enlarged) cavity. + if (!infected(*cavetet)) { + // Check for two possible cases for this tet: + // (1) It is a cavity tet, or + // (2) it is a cavity boundary face. + enqflag = false; + if (!marktested(*cavetet)) { + // Do Delaunay (in-sphere) test. + pts = (point *) cavetet->tet; + if (pts[7] != dummypoint) { + // A volume tet. Operate on it. + if (b->weighted) { + sign = orient4d_s(pts[4], pts[5], pts[6], pts[7], insertpt, + pts[4][3], pts[5][3], pts[6][3], pts[7][3], + insertpt[3]); + } else { + sign = insphere_s(pts[4], pts[5], pts[6], pts[7], insertpt); + } + enqflag = (sign < 0.0); + } else { + if (!nonconvex) { + // Test if this hull face is visible by the new point. + ori = orient3d(pts[4], pts[5], pts[6], insertpt); + if (ori < 0) { + // A visible hull face. + // Include it in the cavity. The convex hull will be enlarged. + enqflag = true; + } else if (ori == 0.0) { + // A coplanar hull face. We need to test if this hull face is + // Delaunay or not. We test if the adjacent tet (not faked) + // of this hull face is Delaunay or not. + decode(cavetet->tet[3], neineitet); + if (!infected(neineitet)) { + if (!marktested(neineitet)) { + // Do Delaunay test on this tet. + pts = (point *) neineitet.tet; + if (b->weighted) { + sign = orient4d_s(pts[4],pts[5],pts[6],pts[7], insertpt, + pts[4][3], pts[5][3], pts[6][3], + pts[7][3], insertpt[3]); + } else { + sign = insphere_s(pts[4],pts[5],pts[6],pts[7], insertpt); + } + enqflag = (sign < 0.0); + } + } else { + // The adjacent tet is non-Delaunay. The hull face is non- + // Delaunay as well. Include it in the cavity. + enqflag = true; + } // if (!infected(neineitet)) + } // if (ori == 0.0) + } else { + // A hull face (must be a subface). + // We FIRST include it in the initial cavity if the adjacent tet + // (not faked) of this hull face is not Delaunay wrt p. + // Whether it belongs to the final cavity will be determined + // during the validation process. 'validflag'. + decode(cavetet->tet[3], neineitet); + if (!infected(neineitet)) { + if (!marktested(neineitet)) { + // Do Delaunay test on this tet. + pts = (point *) neineitet.tet; + if (b->weighted) { + sign = orient4d_s(pts[4],pts[5],pts[6],pts[7], insertpt, + pts[4][3], pts[5][3], pts[6][3], + pts[7][3], insertpt[3]); + } else { + sign = insphere_s(pts[4],pts[5],pts[6],pts[7], insertpt); + } + enqflag = (sign < 0.0); + } + } else { + // The adjacent tet is non-Delaunay. The hull face is non- + // Delaunay as well. Include it in the cavity. + enqflag = true; + } // if (infected(neineitet)) + } // if (nonconvex) + } // if (pts[7] != dummypoint) + marktest(*cavetet); // Only test it once. + } // if (!marktested(*cavetet)) + + if (enqflag) { + // Found a tet in the cavity. Put other three faces in check list. + k = (cavetet->ver & 3); // The current face number + for (j = 1; j < 4; j++) { + decode(cavetet->tet[(j + k) % 4], neightet); + cavetetlist->newindex((void **) &parytet); + *parytet = neightet; + } + infect(*cavetet); + caveoldtetlist->newindex((void **) &parytet); + *parytet = *cavetet; + } else { + // Found a boundary face of the cavity. + cavetet->ver = epivot[cavetet->ver]; + cavebdrylist->newindex((void **) &parytet); + *parytet = *cavetet; + } + } // if (!infected(*cavetet)) + } // i + + cavetetlist->restart(); // Clear the working list. + } // if (ivf->bowywat) + + if (ivf->refineflag > 0) { + // The new point is inserted by Delaunay refinement, i.e., it is the + // circumcenter of a tetrahedron, or a subface, or a segment. + // Do not insert this point if the tetrahedron, or subface, or segment + // is not inside the final cavity. + if (((ivf->refineflag == 1) && !infected(ivf->refinetet))) { + insertpoint_abort(splitseg, ivf); + ivf->iloc = (int) BADELEMENT; + return 0; + } + } // if (ivf->refineflag) + + if (checksubsegflag) { + // Collect all segments of C(p). + shellface *ssptr; + for (i = 0; i < caveoldtetlist->objects; i++) { + cavetet = (triface *) fastlookup(caveoldtetlist, i); + if ((ssptr = (shellface*) cavetet->tet[8]) != NULL) { + for (j = 0; j < 6; j++) { + if (ssptr[j]) { + sdecode(ssptr[j], checkseg); + if (!sinfected(checkseg)) { + sinfect(checkseg); + cavetetseglist->newindex((void **) &paryseg); + *paryseg = checkseg; + } + } + } // j + } + } // i + // Uninfect collected segments. + for (i = 0; i < cavetetseglist->objects; i++) { + paryseg = (face *) fastlookup(cavetetseglist, i); + suninfect(*paryseg); + } + + if (ivf->rejflag & 1) { + // Reject this point if it encroaches upon any segment. + face *paryseg1; + for (i = 0; i < cavetetseglist->objects; i++) { + paryseg1 = (face *) fastlookup(cavetetseglist, i); + point *ppt = (point *) &(paryseg1->sh[3]); + if (check_encroachment(ppt[0], ppt[1], insertpt)) { + badface *bf = NULL; + encseglist->newindex((void **) &bf); + bf->init(); + bf->ss = *paryseg1; + bf->forg = sorg(bf->ss); + bf->fdest = sdest(bf->ss); + } + } // i + if ((ivf->rejflag & 1) && (encseglist->objects > 0)) { + insertpoint_abort(splitseg, ivf); + ivf->iloc = (int) ENCSEGMENT; + return 0; + } + } + } // if (checksubsegflag) + + if (checksubfaceflag) { + // Collect all subfaces of C(p). + shellface *sptr; + for (i = 0; i < caveoldtetlist->objects; i++) { + cavetet = (triface *) fastlookup(caveoldtetlist, i); + if ((sptr = (shellface*) cavetet->tet[9]) != NULL) { + for (j = 0; j < 4; j++) { + if (sptr[j]) { + sdecode(sptr[j], checksh); + if (!sinfected(checksh)) { + sinfect(checksh); + cavetetshlist->newindex((void **) &parysh); + *parysh = checksh; + } + } + } // j + } + } // i + // Uninfect collected subfaces. + for (i = 0; i < cavetetshlist->objects; i++) { + parysh = (face *) fastlookup(cavetetshlist, i); + suninfect(*parysh); + } + + if (ivf->rejflag & 2) { + REAL ccent[3], radius; + badface *bface; + // Reject this point if it encroaches upon any subface. + for (i = 0; i < cavetetshlist->objects; i++) { + parysh = (face *) fastlookup(cavetetshlist, i); + if (get_subface_ccent(parysh, ccent)) { + point encpt = insertpt; + if (check_enc_subface(parysh, &encpt, ccent, &radius)) { + encshlist->newindex((void **) &bface); + bface->ss = *parysh; + bface->forg = sorg(*parysh); + bface->fdest = sdest(*parysh); + bface->fapex = sapex(*parysh); + bface->noppo = NULL; // no existing encroaching vertex. + for (j = 0; j < 3; j++) bface->cent[j] = ccent[j]; + for (j = 3; j < 6; j++) bface->cent[j] = 0.; + bface->key = radius; + } + } + } + if (encshlist->objects > 0) { + insertpoint_abort(splitseg, ivf); + ivf->iloc = (int) ENCSUBFACE; + return 0; + } + } + } // if (checksubfaceflag) + + if ((ivf->iloc == (int) OUTSIDE) && ivf->refineflag) { + // The vertex lies outside of the domain. And it does not encroach + // upon any boundary segment or subface. Do not insert it. + insertpoint_abort(splitseg, ivf); + return 0; + } + + if (ivf->splitbdflag) { + // The new point locates in surface mesh. Update the sC(p). + // We have already 'smarktested' the subfaces which directly intersect + // with p in 'caveshlist'. From them, we 'smarktest' their neighboring + // subfaces which are included in C(p). Do not across a segment. + for (i = 0; i < caveshlist->objects; i++) { + parysh = (face *) fastlookup(caveshlist, i); + checksh = *parysh; + for (j = 0; j < 3; j++) { + if (!isshsubseg(checksh)) { + spivot(checksh, neighsh); + if (!smarktested(neighsh)) { + stpivot(neighsh, neightet); + if (infected(neightet)) { + fsymself(neightet); + if (infected(neightet)) { + // This subface is inside C(p). + // Check if its diametrical circumsphere encloses 'p'. + // The purpose of this check is to avoid forming invalid + // subcavity in surface mesh. + sign = incircle3d(sorg(neighsh), sdest(neighsh), + sapex(neighsh), insertpt); + if (sign < 0) { + smarktest(neighsh); + caveshlist->newindex((void **) &parysh); + *parysh = neighsh; + } + } + } + } + } + senextself(checksh); + } // j + } // i + } // if (ivf->splitbdflag) + + if (ivf->validflag) { + // Validate C(p) and update it if it is not star-shaped. + int cutcount = 0; + + if (ivf->respectbdflag) { + // The initial cavity may include subfaces which are not on the facets + // being splitting. Find them and make them as boundary of C(p). + // Comment: We have already 'smarktested' the subfaces in sC(p). They + // are completely inside C(p). + for (i = 0; i < cavetetshlist->objects; i++) { + parysh = (face *) fastlookup(cavetetshlist, i); + stpivot(*parysh, neightet); + if (infected(neightet)) { + fsymself(neightet); + if (infected(neightet)) { + // Found a subface inside C(p). + if (!smarktested(*parysh)) { + // It is possible that this face is a boundary subface. + // Check if it is a hull face. + //assert(apex(neightet) != dummypoint); + if (oppo(neightet) != dummypoint) { + fsymself(neightet); + } + if (oppo(neightet) != dummypoint) { + ori = orient3d(org(neightet), dest(neightet), apex(neightet), + insertpt); + if (ori < 0) { + // A visible face, get its neighbor face. + fsymself(neightet); + ori = -ori; // It must be invisible by p. + } + } else { + // A hull tet. It needs to be cut. + ori = 1; + } + // Cut this tet if it is either invisible by or coplanar with p. + if (ori >= 0) { + uninfect(neightet); + unmarktest(neightet); + cutcount++; + neightet.ver = epivot[neightet.ver]; + cavebdrylist->newindex((void **) &parytet); + *parytet = neightet; + // Add three new faces to find new boundaries. + for (j = 0; j < 3; j++) { + esym(neightet, neineitet); + neineitet.ver = epivot[neineitet.ver]; + cavebdrylist->newindex((void **) &parytet); + *parytet = neineitet; + enextself(neightet); + } + } // if (ori >= 0) + } + } + } + } // i + + // The initial cavity may include segments in its interior. We need to + // Update the cavity so that these segments are on the boundary of + // the cavity. + for (i = 0; i < cavetetseglist->objects; i++) { + paryseg = (face *) fastlookup(cavetetseglist, i); + // Check this segment if it is not a splitting segment. + if (!smarktested(*paryseg)) { + sstpivot1(*paryseg, neightet); + spintet = neightet; + while (1) { + if (!infected(spintet)) break; + fnextself(spintet); + if (spintet.tet == neightet.tet) break; + } + if (infected(spintet)) { + // Find an adjacent tet at this segment such that both faces + // at this segment are not visible by p. + pa = org(neightet); + pb = dest(neightet); + spintet = neightet; + j = 0; + while (1) { + // Check if this face is visible by p. + pc = apex(spintet); + if (pc != dummypoint) { + ori = orient3d(pa, pb, pc, insertpt); + if (ori >= 0) { + // Not visible. Check another face in this tet. + esym(spintet, neineitet); + pc = apex(neineitet); + if (pc != dummypoint) { + ori = orient3d(pb, pa, pc, insertpt); + if (ori >= 0) { + // Not visible. Found this face. + j = 1; // Flag that it is found. + break; + } + } + } + } + fnextself(spintet); + if (spintet.tet == neightet.tet) break; + } + if (j == 0) { + // Not found such a face. + terminatetetgen(this, 2); + } + neightet = spintet; + if (b->verbose > 4) { + printf(" Cut tet (%d, %d, %d, %d)\n", + pointmark(org(neightet)), pointmark(dest(neightet)), + pointmark(apex(neightet)), pointmark(oppo(neightet))); + } + uninfect(neightet); + unmarktest(neightet); + cutcount++; + neightet.ver = epivot[neightet.ver]; + cavebdrylist->newindex((void **) &parytet); + *parytet = neightet; + // Add three new faces to find new boundaries. + for (j = 0; j < 3; j++) { + esym(neightet, neineitet); + neineitet.ver = epivot[neineitet.ver]; + cavebdrylist->newindex((void **) &parytet); + *parytet = neineitet; + enextself(neightet); + } + } + } + } // i + } // if (ivf->respectbdflag) + + // Update the cavity by removing invisible faces until it is star-shaped. + for (i = 0; i < cavebdrylist->objects; i++) { + cavetet = (triface *) fastlookup(cavebdrylist, i); + // 'cavetet' is an exterior tet adjacent to the cavity. + // Check if its neighbor is inside C(p). + fsym(*cavetet, neightet); + if (infected(neightet)) { + if (apex(*cavetet) != dummypoint) { + // It is a cavity boundary face. Check its visibility. + if (oppo(neightet) != dummypoint) { + // Check if this face is visible by the new point. + if (issubface(neightet)) { + // Re-use 'volume' and 'attrib'. + pa = org(*cavetet); + pb = dest(*cavetet); + pc = apex(*cavetet); + volume = orient3dfast(pa, pb, pc, insertpt); + attrib = distance(pa, pb) * distance(pb, pc) * distance(pc, pa); + if ((fabs(volume) / attrib) < b->epsilon) { + ori = 0.0; + } else { + ori = orient3d(pa, pb, pc, insertpt); + } + } else { + ori = orient3d(org(*cavetet), dest(*cavetet), apex(*cavetet), + insertpt); + } + enqflag = (ori > 0); + // Comment: if ori == 0 (coplanar case), we also cut the tet. + } else { + // It is a hull face. And its adjacent tet (at inside of the + // domain) has been cut from the cavity. Cut it as well. + //assert(nonconvex); + enqflag = false; + } + } else { + enqflag = true; // A hull edge. + } + if (enqflag) { + // This face is valid, save it. + cavetetlist->newindex((void **) &parytet); + *parytet = *cavetet; + } else { + uninfect(neightet); + unmarktest(neightet); + cutcount++; + // Add three new faces to find new boundaries. + for (j = 0; j < 3; j++) { + esym(neightet, neineitet); + neineitet.ver = epivot[neineitet.ver]; + cavebdrylist->newindex((void **) &parytet); + *parytet = neineitet; + enextself(neightet); + } + // 'cavetet' is not on the cavity boundary anymore. + unmarktest(*cavetet); + } + } else { + // 'cavetet' is not on the cavity boundary anymore. + unmarktest(*cavetet); + } + } // i + + if (cutcount > 0) { + // The cavity has been updated. + // Update the cavity boundary faces. + cavebdrylist->restart(); + for (i = 0; i < cavetetlist->objects; i++) { + cavetet = (triface *) fastlookup(cavetetlist, i); + // 'cavetet' was an exterior tet adjacent to the cavity. + fsym(*cavetet, neightet); + if (infected(neightet)) { + // It is a cavity boundary face. + cavebdrylist->newindex((void **) &parytet); + *parytet = *cavetet; + } else { + // Not a cavity boundary face. + unmarktest(*cavetet); + } + } + + // Update the list of old tets. + cavetetlist->restart(); + for (i = 0; i < caveoldtetlist->objects; i++) { + cavetet = (triface *) fastlookup(caveoldtetlist, i); + if (infected(*cavetet)) { + cavetetlist->newindex((void **) &parytet); + *parytet = *cavetet; + } + } + // Swap 'cavetetlist' and 'caveoldtetlist'. + swaplist = caveoldtetlist; + caveoldtetlist = cavetetlist; + cavetetlist = swaplist; + + // The cavity should contain at least one tet. + if (caveoldtetlist->objects == 0l) { + insertpoint_abort(splitseg, ivf); + ivf->iloc = (int) NULLCAVITY; // BADELEMENT; + return 0; + } + + if (ivf->splitbdflag) { + int cutshcount = 0; + // Update the sub-cavity sC(p). + for (i = 0; i < caveshlist->objects; i++) { + parysh = (face *) fastlookup(caveshlist, i); + if (smarktested(*parysh)) { + enqflag = false; + stpivot(*parysh, neightet); + if (infected(neightet)) { + fsymself(neightet); + if (infected(neightet)) { + enqflag = true; + } + } + if (!enqflag) { + sunmarktest(*parysh); + // Use the last entry of this array to fill this entry. + j = caveshlist->objects - 1; + checksh = * (face *) fastlookup(caveshlist, j); + *parysh = checksh; + cutshcount++; + caveshlist->objects--; // The list is shrinked. + i--; + } + } + } + + if (cutshcount > 0) { + i = 0; // Count the number of invalid subfaces/segments. + // Valid the updated sub-cavity sC(p). + if (loc == ONFACE) { + if ((splitsh != NULL) && (splitsh->sh != NULL)) { + // The to-be split subface should be in sC(p). + if (!smarktested(*splitsh)) i++; + } + } else if (loc == ONEDGE) { + if ((splitseg != NULL) && (splitseg->sh != NULL)) { + // The to-be split segment should be in sC(p). + if (!smarktested(*splitseg)) i++; + } + if ((splitsh != NULL) && (splitsh->sh != NULL)) { + // All subfaces at this edge should be in sC(p). + pa = sorg(*splitsh); + neighsh = *splitsh; + while (1) { + // Adjust the origin of its edge to be 'pa'. + if (sorg(neighsh) != pa) { + sesymself(neighsh); + } + // Add this face into list (in B-W cavity). + if (!smarktested(neighsh)) i++; + // Go to the next face at the edge. + spivotself(neighsh); + // Stop if all faces at the edge have been visited. + if (neighsh.sh == splitsh->sh) break; + if (neighsh.sh == NULL) break; + } // while (1) + } + } + + if (i > 0) { + // The updated sC(p) is invalid. Do not insert this vertex. + insertpoint_abort(splitseg, ivf); + ivf->iloc = (int) NULLCAVITY; // BADELEMENT; + return 0; + } + } // if (cutshcount > 0) + } // if (ivf->splitbdflag) + } // if (cutcount > 0) + + } // if (ivf->validflag) + + if (ivf->refineflag) { + // The new point is inserted by Delaunay refinement, i.e., it is the + // circumcenter of a tetrahedron, or a subface, or a segment. + // Do not insert this point if the tetrahedron, or subface, or segment + // is not inside the final cavity. + if (((ivf->refineflag == 1) && !infected(ivf->refinetet)) || + ((ivf->refineflag == 2) && !smarktested(ivf->refinesh))) { + insertpoint_abort(splitseg, ivf); + ivf->iloc = (int) BADELEMENT; + return 0; + } else { + // The following options are used in boundary recovery when we try to + // remove a crossing face (ivf->refineflag == 4) or a crossing edge + // (ivf->refineflag == 8). Reject this point if the face(or edge) + // survives after inserting this vertex. + bool bflag = false; + if (ivf->refineflag == 4) { + // Check if the face (ivf.refinetet) is removed. + // Both tets at this face should be in the cavity. + triface adjtet; + fsym(ivf->refinetet, adjtet); + if (!infected(ivf->refinetet) || !infected(adjtet)) { + bflag = true; + } + } else if (ivf->refineflag == 8) { + // Check if the edge (ivf.refinetet) is removed. + // All tets at this edge should be in the cavity. + triface spintet = ivf->refinetet; + while (true) { + if (!infected(spintet)) { + bflag = true; break; + } + fnextself(spintet); + if (spintet.tet == ivf->refinetet.tet) break; + } + } + if (bflag) { + // Reject this new point. + insertpoint_abort(splitseg, ivf); + ivf->iloc = (int) BADELEMENT; + return 0; + } + } + } // if (ivf->refineflag) + + if ((splitseg != NULL) && (splitseg->sh != NULL)) { + // A segment will be split. It muts lie inside of the cavity. + sstpivot1(*splitseg, neightet); + if (neightet.tet != NULL) { + // This is an existing segment. + bool bflag = false; + spintet = neightet; + while (true) { + if (!infected(spintet)) { + bflag = true; break; + } + fnextself(spintet); + if (spintet.tet == neightet.tet) break; + } + if (bflag) { + // Reject this new point. + insertpoint_abort(splitseg, ivf); + ivf->iloc = (int) BADELEMENT; + return 0; + } + } // if (neightet.tet != NULL) + } + + if (b->weighted || ivf->cdtflag || ivf->smlenflag || ivf->validflag) { + // There may be other vertices inside C(p). We need to find them. + // Collect all vertices of C(p). + for (i = 0; i < caveoldtetlist->objects; i++) { + cavetet = (triface *) fastlookup(caveoldtetlist, i); + //assert(infected(*cavetet)); + pts = (point *) &(cavetet->tet[4]); + for (j = 0; j < 4; j++) { + if (pts[j] != dummypoint) { + if (!pinfected(pts[j])) { + pinfect(pts[j]); + cavetetvertlist->newindex((void **) &parypt); + *parypt = pts[j]; + } + } + } // j + } // i + // Uninfect all collected (cavity) vertices. + for (i = 0; i < cavetetvertlist->objects; i++) { + parypt = (point *) fastlookup(cavetetvertlist, i); + puninfect(*parypt); + } + if (ivf->smlenflag) { + REAL len; + // Get the length of the shortest edge connecting to 'newpt'. + parypt = (point *) fastlookup(cavetetvertlist, 0); + ivf->smlen = distance(*parypt, insertpt); + ivf->parentpt = *parypt; + for (i = 1; i < cavetetvertlist->objects; i++) { + parypt = (point *) fastlookup(cavetetvertlist, i); + len = distance(*parypt, insertpt); + if (len < ivf->smlen) { + ivf->smlen = len; + ivf->parentpt = *parypt; + } + } + } + } + + + if (ivf->cdtflag) { + // Unmark tets. + for (i = 0; i < caveoldtetlist->objects; i++) { + cavetet = (triface *) fastlookup(caveoldtetlist, i); + unmarktest(*cavetet); + } + for (i = 0; i < cavebdrylist->objects; i++) { + cavetet = (triface *) fastlookup(cavebdrylist, i); + unmarktest(*cavetet); + } + // Clean up arrays which are not needed. + cavetetlist->restart(); + if (checksubsegflag) { + cavetetseglist->restart(); + } + if (checksubfaceflag) { + cavetetshlist->restart(); + } + ivf->iloc = (int) INSTAR; + return 1; + } + + // Before re-mesh C(p). Process the segments and subfaces which are on the + // boundary of C(p). Make sure that each such segment or subface is + // connecting to a tet outside C(p). So we can re-connect them to the + // new tets inside the C(p) later. + + if (checksubsegflag) { + for (i = 0; i < cavetetseglist->objects; i++) { + paryseg = (face *) fastlookup(cavetetseglist, i); + // Operate on it if it is not the splitting segment, i.e., in sC(p). + if (!smarktested(*paryseg)) { + // Check if the segment is inside the cavity. + // 'j' counts the num of adjacent tets of this seg. + // 'k' counts the num of adjacent tets which are 'sinfected'. + j = k = 0; + sstpivot1(*paryseg, neightet); + spintet = neightet; + while (1) { + j++; + if (!infected(spintet)) { + neineitet = spintet; // An outer tet. Remember it. + } else { + k++; // An in tet. + } + fnextself(spintet); + if (spintet.tet == neightet.tet) break; + } + // assert(j > 0); + if (k == 0) { + // The segment is not connect to C(p) anymore. Remove it by + // Replacing it by the last entry of this list. + s = cavetetseglist->objects - 1; + checkseg = * (face *) fastlookup(cavetetseglist, s); + *paryseg = checkseg; + cavetetseglist->objects--; + i--; + } else if (k < j) { + // The segment is on the boundary of C(p). + sstbond1(*paryseg, neineitet); + } else { // k == j + // The segment is inside C(p). + if (!ivf->splitbdflag) { + checkseg = *paryseg; + sinfect(checkseg); // Flag it as an interior segment. + caveencseglist->newindex((void **) &paryseg); + *paryseg = checkseg; + } else { + //assert(0); // Not possible. + terminatetetgen(this, 2); + } + } + } else { + // assert(smarktested(*paryseg)); + // Flag it as an interior segment. Do not queue it, since it will + // be deleted after the segment splitting. + sinfect(*paryseg); + } + } // i + } // if (checksubsegflag) + + if (checksubfaceflag) { + for (i = 0; i < cavetetshlist->objects; i++) { + parysh = (face *) fastlookup(cavetetshlist, i); + // Operate on it if it is not inside the sub-cavity sC(p). + if (!smarktested(*parysh)) { + // Check if this subface is inside the cavity. + k = 0; + for (j = 0; j < 2; j++) { + stpivot(*parysh, neightet); + if (!infected(neightet)) { + checksh = *parysh; // Remember this side. + } else { + k++; + } + sesymself(*parysh); + } + if (k == 0) { + // The subface is not connected to C(p). Remove it. + s = cavetetshlist->objects - 1; + checksh = * (face *) fastlookup(cavetetshlist, s); + *parysh = checksh; + cavetetshlist->objects--; + i--; + } else if (k == 1) { + // This side is the outer boundary of C(p). + *parysh = checksh; + } else { // k == 2 + if (!ivf->splitbdflag) { + checksh = *parysh; + sinfect(checksh); // Flag it. + caveencshlist->newindex((void **) &parysh); + *parysh = checksh; + } else { + //assert(0); // Not possible. + terminatetetgen(this, 2); + } + } + } else { + // assert(smarktested(*parysh)); + // Flag it as an interior subface. Do not queue it. It will be + // deleted after the facet point insertion. + sinfect(*parysh); + } + } // i + } // if (checksubfaceflag) + + // Create new tetrahedra to fill the cavity. + + for (i = 0; i < cavebdrylist->objects; i++) { + cavetet = (triface *) fastlookup(cavebdrylist, i); + neightet = *cavetet; + unmarktest(neightet); // Unmark it. + // Get the oldtet (inside the cavity). + fsym(neightet, oldtet); + if (apex(neightet) != dummypoint) { + // Create a new tet in the cavity. + maketetrahedron(&newtet); + setorg(newtet, dest(neightet)); + setdest(newtet, org(neightet)); + setapex(newtet, apex(neightet)); + setoppo(newtet, insertpt); + } else { + // Create a new hull tet. + hullsize++; + maketetrahedron(&newtet); + setorg(newtet, org(neightet)); + setdest(newtet, dest(neightet)); + setapex(newtet, insertpt); + setoppo(newtet, dummypoint); // It must opposite to face 3. + // Adjust back to the cavity bounday face. + esymself(newtet); + } + // The new tet inherits attribtes from the old tet. + for (j = 0; j < numelemattrib; j++) { + attrib = elemattribute(oldtet.tet, j); + setelemattribute(newtet.tet, j, attrib); + } + if (b->varvolume) { + volume = volumebound(oldtet.tet); + setvolumebound(newtet.tet, volume); + } + // Connect newtet <==> neightet, this also disconnect the old bond. + bond(newtet, neightet); + // oldtet still connects to neightet. + *cavetet = oldtet; // *cavetet = newtet; + } // i + + // Set a handle for speeding point location. + recenttet = newtet; + //setpoint2tet(insertpt, encode(newtet)); + setpoint2tet(insertpt, (tetrahedron) (newtet.tet)); + + // Re-use this list to save new interior cavity faces. + cavetetlist->restart(); + + // Connect adjacent new tetrahedra together. + for (i = 0; i < cavebdrylist->objects; i++) { + cavetet = (triface *) fastlookup(cavebdrylist, i); + // cavtet is an oldtet, get the newtet at this face. + oldtet = *cavetet; + fsym(oldtet, neightet); + fsym(neightet, newtet); + // Comment: oldtet and newtet must be at the same directed edge. + // Connect the three other faces of this newtet. + for (j = 0; j < 3; j++) { + esym(newtet, neightet); // Go to the face. + if (neightet.tet[neightet.ver & 3] == NULL) { + // Find the adjacent face of this newtet. + spintet = oldtet; + while (1) { + fnextself(spintet); + if (!infected(spintet)) break; + } + fsym(spintet, newneitet); + esymself(newneitet); + bond(neightet, newneitet); + if (ivf->lawson > 1) { + cavetetlist->newindex((void **) &parytet); + *parytet = neightet; + } + } + //setpoint2tet(org(newtet), encode(newtet)); + setpoint2tet(org(newtet), (tetrahedron) (newtet.tet)); + enextself(newtet); + enextself(oldtet); + } + *cavetet = newtet; // Save the new tet. + } // i + + if (checksubfaceflag) { + // Connect subfaces on the boundary of the cavity to the new tets. + for (i = 0; i < cavetetshlist->objects; i++) { + parysh = (face *) fastlookup(cavetetshlist, i); + // Connect it if it is not a missing subface. + if (!sinfected(*parysh)) { + stpivot(*parysh, neightet); + fsym(neightet, spintet); + sesymself(*parysh); + tsbond(spintet, *parysh); + } + } + } + + if (checksubsegflag) { + // Connect segments on the boundary of the cavity to the new tets. + for (i = 0; i < cavetetseglist->objects; i++) { + paryseg = (face *) fastlookup(cavetetseglist, i); + // Connect it if it is not a missing segment. + if (!sinfected(*paryseg)) { + sstpivot1(*paryseg, neightet); + spintet = neightet; + while (1) { + tssbond1(spintet, *paryseg); + fnextself(spintet); + if (spintet.tet == neightet.tet) break; + } + } + } + } + + if (((splitsh != NULL) && (splitsh->sh != NULL)) || + ((splitseg != NULL) && (splitseg->sh != NULL))) { + // Split a subface or a segment. + sinsertvertex(insertpt, splitsh, splitseg, ivf->sloc, ivf->sbowywat, 0); + } + + if (checksubfaceflag) { + if (ivf->splitbdflag) { + // Recover new subfaces in C(p). + for (i = 0; i < caveshbdlist->objects; i++) { + // Get an old subface at edge [a, b]. + parysh = (face *) fastlookup(caveshbdlist, i); + spivot(*parysh, checksh); // The new subface [a, b, p]. + // Do not recover a deleted new face (degenerated). + if (checksh.sh[3] != NULL) { + // Note that the old subface still connects to adjacent old tets + // of C(p), which still connect to the tets outside C(p). + stpivot(*parysh, neightet); + // Find the adjacent tet containing the edge [a,b] outside C(p). + spintet = neightet; + while (1) { + fnextself(spintet); + if (!infected(spintet)) break; + if (spintet.tet == neightet.tet) { + terminatetetgen(this, 2); + } + } + // The adjacent tet connects to a new tet in C(p). + fsym(spintet, neightet); + // Find the tet containing the face [a, b, p]. + spintet = neightet; + while (1) { + fnextself(spintet); + if (apex(spintet) == insertpt) break; + } + // Adjust the edge direction in spintet and checksh. + if (sorg(checksh) != org(spintet)) { + sesymself(checksh); + } + // Connect the subface to two adjacent tets. + tsbond(spintet, checksh); + fsymself(spintet); + sesymself(checksh); + tsbond(spintet, checksh); + } // if (checksh.sh[3] != NULL) + } + } else { + // The Boundary recovery phase. + // Put all new subfaces into stack for recovery. + for (i = 0; i < caveshbdlist->objects; i++) { + // Get an old subface at edge [a, b]. + parysh = (face *) fastlookup(caveshbdlist, i); + spivot(*parysh, checksh); // The new subface [a, b, p]. + // Do not recover a deleted new face (degenerated). + if (checksh.sh[3] != NULL) { + subfacstack->newindex((void **) &parysh); + *parysh = checksh; + } + } + // Put all interior subfaces into stack for recovery. + for (i = 0; i < caveencshlist->objects; i++) { + parysh = (face *) fastlookup(caveencshlist, i); + // Some subfaces inside C(p) might be split in sinsertvertex(). + // Only queue those faces which are not split. + if (!smarktested(*parysh)) { + checksh = *parysh; + suninfect(checksh); + stdissolve(checksh); // Detach connections to old tets. + subfacstack->newindex((void **) &parysh); + *parysh = checksh; + } + } + } + } // if (checksubfaceflag) + + if (checksubsegflag) { + if (ivf->splitbdflag) { + if (splitseg != NULL) { + // Recover the two new subsegments in C(p). + for (i = 0; i < cavesegshlist->objects; i++) { + paryseg = (face *) fastlookup(cavesegshlist, i); + // Insert this subsegment into C(p). + checkseg = *paryseg; + // Get the adjacent new subface. + checkseg.shver = 0; + spivot(checkseg, checksh); + if (checksh.sh != NULL) { + // Get the adjacent new tetrahedron. + stpivot(checksh, neightet); + } else { + // It's a dangling segment. + point2tetorg(sorg(checkseg), neightet); + finddirection(&neightet, sdest(checkseg)); + } + if (isdeadtet(neightet)) { + terminatetetgen(this, 2); + } + sstbond1(checkseg, neightet); + spintet = neightet; + while (1) { + tssbond1(spintet, checkseg); + fnextself(spintet); + if (spintet.tet == neightet.tet) break; + } + } + } // if (splitseg != NULL) + } else { + // The Boundary Recovery Phase. + // Queue missing segments in C(p) for recovery. + if (splitseg != NULL) { + // Queue two new subsegments in C(p) for recovery. + for (i = 0; i < cavesegshlist->objects; i++) { + paryseg = (face *) fastlookup(cavesegshlist, i); + checkseg = *paryseg; + //sstdissolve1(checkseg); // It has not been connected yet. + subsegstack->newindex((void **) &paryseg); + *paryseg = checkseg; + } + } // if (splitseg != NULL) + for (i = 0; i < caveencseglist->objects; i++) { + paryseg = (face *) fastlookup(caveencseglist, i); + if (!smarktested(*paryseg)) { // It may be split. + checkseg = *paryseg; + suninfect(checkseg); + sstdissolve1(checkseg); // Detach connections to old tets. + s = randomnation(subsegstack->objects + 1); + subsegstack->newindex((void **) &paryseg); + *paryseg = * (face *) fastlookup(subsegstack, s); + paryseg = (face *) fastlookup(subsegstack, s); + *paryseg = checkseg; + } + } + } + } // if (checksubsegflag) + + if (b->weighted || ivf->validflag) { + // Some vertices may be completed inside the cavity. They must be + // detected and added to recovering list. + for (i = 0; i < cavetetvertlist->objects; i++) { + pts = (point *) fastlookup(cavetetvertlist, i); + decode(point2tet(*pts), *searchtet); + if (infected(*searchtet)) { + if (b->weighted) { + if (b->verbose > 4) { + printf(" Point #%d is non-regular after the insertion of #%d.\n", + pointmark(*pts), pointmark(insertpt)); + } + setpointtype(*pts, NREGULARVERTEX); + nonregularcount++; + } else { + if (b->verbose > 4) { + printf(" Deleting an interior vertex %d.\n", pointmark(*pts)); + } + // The cavity is updated such that no constrained segments and + // subfaces are in its interior. Interior vertices must be + // inside volume or on a boundary facet. + // The point has been removed. + point steinerpt = *pts; + enum verttype vt = pointtype(steinerpt); + if (vt != UNUSEDVERTEX) { + setpointtype(steinerpt, UNUSEDVERTEX); + unuverts++; + } + if (vt != VOLVERTEX) { + // Update the correspinding counters. + if (vt == FREESEGVERTEX) { + st_segref_count--; + } else if (vt == FREEFACETVERTEX) { + st_facref_count--; + } else if (vt == FREEVOLVERTEX) { + st_volref_count--; + } + if (steinerleft > 0) steinerleft++; + } + } + } + } + } + + if (ivf->chkencflag & 1) { + // Queue all segment outside C(p). + for (i = 0; i < cavetetseglist->objects; i++) { + paryseg = (face *) fastlookup(cavetetseglist, i); + // Skip if it is the split segment. + if (!sinfected(*paryseg)) { + enqueuesubface(badsubsegs, paryseg); + } + } + if (splitseg != NULL) { + // Queue the two new subsegments inside C(p). + for (i = 0; i < cavesegshlist->objects; i++) { + paryseg = (face *) fastlookup(cavesegshlist, i); + enqueuesubface(badsubsegs, paryseg); + } + } + } // if (chkencflag & 1) + + if (ivf->chkencflag & 2) { + // Queue all subfaces outside C(p). + for (i = 0; i < cavetetshlist->objects; i++) { + parysh = (face *) fastlookup(cavetetshlist, i); + // Skip if it is a split subface. + if (!sinfected(*parysh)) { + enqueuesubface(badsubfacs, parysh); + } + } + // Queue all new subfaces inside C(p). + for (i = 0; i < caveshbdlist->objects; i++) { + // Get an old subface at edge [a, b]. + parysh = (face *) fastlookup(caveshbdlist, i); + spivot(*parysh, checksh); // checksh is a new subface [a, b, p]. + // Do not recover a deleted new face (degenerated). + if (checksh.sh[3] != NULL) { + enqueuesubface(badsubfacs, &checksh); + } + } + } // if (chkencflag & 2) + + if (ivf->chkencflag & 4) { + // Queue all new tetrahedra in C(p). + for (i = 0; i < cavebdrylist->objects; i++) { + cavetet = (triface *) fastlookup(cavebdrylist, i); + enqueuetetrahedron(cavetet); + } + } + + // C(p) is re-meshed successfully. + + // Delete the old tets in C(p). + for (i = 0; i < caveoldtetlist->objects; i++) { + searchtet = (triface *) fastlookup(caveoldtetlist, i); + if (ishulltet(*searchtet)) { + hullsize--; + } + tetrahedrondealloc(searchtet->tet); + } + + if (((splitsh != NULL) && (splitsh->sh != NULL)) || + ((splitseg != NULL) && (splitseg->sh != NULL))) { + // Delete the old subfaces in sC(p). + for (i = 0; i < caveshlist->objects; i++) { + parysh = (face *) fastlookup(caveshlist, i); + if (checksubfaceflag) {//if (bowywat == 2) { + // It is possible that this subface still connects to adjacent + // tets which are not in C(p). If so, clear connections in the + // adjacent tets at this subface. + stpivot(*parysh, neightet); + if (neightet.tet != NULL) { + if (neightet.tet[4] != NULL) { + // Found an adjacent tet. It must be not in C(p). + tsdissolve(neightet); + fsymself(neightet); + tsdissolve(neightet); + } + } + } + shellfacedealloc(subfaces, parysh->sh); + } + if ((splitseg != NULL) && (splitseg->sh != NULL)) { + // Delete the old segment in sC(p). + shellfacedealloc(subsegs, splitseg->sh); + } + } + + if (ivf->lawson) { + for (i = 0; i < cavebdrylist->objects; i++) { + searchtet = (triface *) fastlookup(cavebdrylist, i); + flippush(flipstack, searchtet); + } + if (ivf->lawson > 1) { + for (i = 0; i < cavetetlist->objects; i++) { + searchtet = (triface *) fastlookup(cavetetlist, i); + flippush(flipstack, searchtet); + } + } + } + + + // Clean the working lists. + + caveoldtetlist->restart(); + cavebdrylist->restart(); + cavetetlist->restart(); + + if (checksubsegflag) { + cavetetseglist->restart(); + caveencseglist->restart(); + } + + if (checksubfaceflag) { + cavetetshlist->restart(); + caveencshlist->restart(); + } + + if (b->weighted || ivf->smlenflag || ivf->validflag) { + cavetetvertlist->restart(); + } + + if (((splitsh != NULL) && (splitsh->sh != NULL)) || + ((splitseg != NULL) && (splitseg->sh != NULL))) { + caveshlist->restart(); + caveshbdlist->restart(); + cavesegshlist->restart(); + } + + return 1; // Point is inserted. +} + +//============================================================================// +// // +// insertpoint_abort() Abort the insertion of a new vertex. // +// // +// The cavity will be restored. All working lists are cleared. // +// // +//============================================================================// + +void tetgenmesh::insertpoint_abort(face *splitseg, insertvertexflags *ivf) +{ + triface *cavetet; + face *parysh; + int i; + + for (i = 0; i < caveoldtetlist->objects; i++) { + cavetet = (triface *) fastlookup(caveoldtetlist, i); + uninfect(*cavetet); + unmarktest(*cavetet); + } + for (i = 0; i < cavebdrylist->objects; i++) { + cavetet = (triface *) fastlookup(cavebdrylist, i); + unmarktest(*cavetet); + } + cavetetlist->restart(); + cavebdrylist->restart(); + caveoldtetlist->restart(); + cavetetseglist->restart(); + cavetetshlist->restart(); + if (ivf->splitbdflag) { + if ((splitseg != NULL) && (splitseg->sh != NULL)) { + sunmarktest(*splitseg); + } + for (i = 0; i < caveshlist->objects; i++) { + parysh = (face *) fastlookup(caveshlist, i); + sunmarktest(*parysh); + } + caveshlist->restart(); + cavesegshlist->restart(); + } +} + +// // +// // +//== flip_cxx ================================================================// + +//== delaunay_cxx ============================================================// +// // +// // + +//============================================================================// +// // +// transfernodes() Read the vertices from the input (tetgenio). // +// // +// Transferring all points from input ('in->pointlist') to TetGen's 'points'. // +// All points are indexed (the first point index is 'in->firstnumber'). Each // +// point's type is initialized as UNUSEDVERTEX. The bounding box (xmax, xmin, // +// ...) and the diameter (longest) of the point set are calculated. // +// // +//============================================================================// + +void tetgenmesh::transfernodes() +{ + point pointloop; + REAL x, y, z, w, mtr; + int coordindex; + int attribindex; + int mtrindex; + int i, j; + + // Read the points. + coordindex = 0; + attribindex = 0; + mtrindex = 0; + for (i = 0; i < in->numberofpoints; i++) { + makepoint(&pointloop, UNUSEDVERTEX); + // Read the point coordinates. + x = pointloop[0] = in->pointlist[coordindex++]; + y = pointloop[1] = in->pointlist[coordindex++]; + z = pointloop[2] = in->pointlist[coordindex++]; + // Read the point attributes. (Including point weights.) + for (j = 0; j < in->numberofpointattributes; j++) { + pointloop[3 + j] = in->pointattributelist[attribindex++]; + } + // Read the point metric tensor. + for (j = 0; j < in->numberofpointmtrs; j++) { + mtr = in->pointmtrlist[mtrindex++] * b->metric_scale; + pointloop[pointmtrindex + j] = mtr; // in->pointmtrlist[mtrindex++]; + } + if (b->weighted) { // -w option + if (in->numberofpointattributes > 0) { + // The first point attribute is its weight. + //w = in->pointattributelist[in->numberofpointattributes * i]; + w = pointloop[3]; + } else { + // No given weight available. Default choose the maximum + // absolute value among its coordinates. + w = fabs(x); + if (w < fabs(y)) w = fabs(y); + if (w < fabs(z)) w = fabs(z); + } + if (b->weighted_param == 0) { + pointloop[3] = x * x + y * y + z * z - w; // Weighted DT. + } else { // -w1 option + pointloop[3] = w; // Regular tetrahedralization. + } + } + // Determine the smallest and largest x, y and z coordinates. + if (i == 0) { + xmin = xmax = x; + ymin = ymax = y; + zmin = zmax = z; + } else { + xmin = (x < xmin) ? x : xmin; + xmax = (x > xmax) ? x : xmax; + ymin = (y < ymin) ? y : ymin; + ymax = (y > ymax) ? y : ymax; + zmin = (z < zmin) ? z : zmin; + zmax = (z > zmax) ? z : zmax; + } + } + + x = xmax - xmin; + y = ymax - ymin; + z = zmax - zmin; + + exactinit(b->verbose, b->noexact, b->nostaticfilter, x, y, z); + + // Use the number of points as the random seed. + srand(in->numberofpoints); + + // 'longest' is the largest possible edge length formed by input vertices. + longest = sqrt(x * x + y * y + z * z); + if (longest == 0.0) { + printf("Error: The point set is trivial.\n"); + terminatetetgen(this, 10); + } + // Two identical points are distinguished by 'minedgelength'. + minedgelength = longest * b->epsilon; + +#ifndef TETLIBRARY + /* + // Release the memory from the input data strutcure + delete [] in->pointlist; + in->pointlist = NULL; + if (in->pointattributelist != NULL) { + delete [] in->pointattributelist; + in->pointattributelist = NULL; + } + if (in->pointmtrlist != NULL) { + delete [] in->pointmtrlist; + in->pointmtrlist = NULL; + } + */ +#endif +} + +//============================================================================// +// // +// hilbert_init() Initialize the Gray code permutation table. // +// // +// The table 'transgc' has 8 x 3 x 8 entries. It contains all possible Gray // +// code sequences traveled by the 1st order Hilbert curve in 3 dimensions. // +// The first column is the Gray code of the entry point of the curve, and // +// the second column is the direction (0, 1, or 2, 0 means the x-axis) where // +// the exit point of curve lies. // +// // +// The table 'tsb1mod3' contains the numbers of trailing set '1' bits of the // +// indices from 0 to 7, modulo by '3'. The code for generating this table is // +// from: http://graphics.stanford.edu/~seander/bithacks.html. // +// // +//============================================================================// + +void tetgenmesh::hilbert_init(int n) +{ + int gc[8], N, mask, travel_bit; + int e, d, f, k, g; + int v, c; + int i; + + N = (n == 2) ? 4 : 8; + mask = (n == 2) ? 3 : 7; + + // Generate the Gray code sequence. + for (i = 0; i < N; i++) { + gc[i] = i ^ (i >> 1); + } + + for (e = 0; e < N; e++) { + for (d = 0; d < n; d++) { + // Calculate the end point (f). + f = e ^ (1 << d); // Toggle the d-th bit of 'e'. + // travel_bit = 2**p, the bit we want to travel. + travel_bit = e ^ f; + for (i = 0; i < N; i++) { + // // Rotate gc[i] left by (p + 1) % n bits. + k = gc[i] * (travel_bit * 2); + g = ((k | (k / N)) & mask); + // Calculate the permuted Gray code by xor with the start point (e). + transgc[e][d][i] = (g ^ e); + } + } // d + } // e + + // Count the consecutive '1' bits (trailing) on the right. + tsb1mod3[0] = 0; + for (i = 1; i < N; i++) { + v = ~i; // Count the 0s. + v = (v ^ (v - 1)) >> 1; // Set v's trailing 0s to 1s and zero rest + for (c = 0; v; c++) { + v >>= 1; + } + tsb1mod3[i] = c % n; + } +} + +//============================================================================// +// // +// hilbert_sort3() Sort points using the 3d Hilbert curve. // +// // +//============================================================================// + +int tetgenmesh::hilbert_split(point* vertexarray,int arraysize,int gc0,int gc1, + REAL bxmin, REAL bxmax, REAL bymin, REAL bymax, + REAL bzmin, REAL bzmax) +{ + point swapvert; + int axis, d; + REAL split; + int i, j; + + + // Find the current splitting axis. 'axis' is a value 0, or 1, or 2, which + // correspoding to x-, or y- or z-axis. + axis = (gc0 ^ gc1) >> 1; + + // Calulate the split position along the axis. + if (axis == 0) { + split = 0.5 * (bxmin + bxmax); + } else if (axis == 1) { + split = 0.5 * (bymin + bymax); + } else { // == 2 + split = 0.5 * (bzmin + bzmax); + } + + // Find the direction (+1 or -1) of the axis. If 'd' is +1, the direction + // of the axis is to the positive of the axis, otherwise, it is -1. + d = ((gc0 & (1< 0) { + do { + for (; i < arraysize; i++) { + if (vertexarray[i][axis] >= split) break; + } + for (; j >= 0; j--) { + if (vertexarray[j][axis] < split) break; + } + // Is the partition finished? + if (i == (j + 1)) break; + // Swap i-th and j-th vertices. + swapvert = vertexarray[i]; + vertexarray[i] = vertexarray[j]; + vertexarray[j] = swapvert; + // Continue patitioning the array; + } while (true); + } else { + do { + for (; i < arraysize; i++) { + if (vertexarray[i][axis] <= split) break; + } + for (; j >= 0; j--) { + if (vertexarray[j][axis] > split) break; + } + // Is the partition finished? + if (i == (j + 1)) break; + // Swap i-th and j-th vertices. + swapvert = vertexarray[i]; + vertexarray[i] = vertexarray[j]; + vertexarray[j] = swapvert; + // Continue patitioning the array; + } while (true); + } + + return i; +} + +void tetgenmesh::hilbert_sort3(point* vertexarray, int arraysize, int e, int d, + REAL bxmin, REAL bxmax, REAL bymin, REAL bymax, + REAL bzmin, REAL bzmax, int depth) +{ + REAL x1, x2, y1, y2, z1, z2; + int p[9], w, e_w, d_w, k, ei, di; + int n = 3, mask = 7; + + p[0] = 0; + p[8] = arraysize; + + // Sort the points according to the 1st order Hilbert curve in 3d. + p[4] = hilbert_split(vertexarray, p[8], transgc[e][d][3], transgc[e][d][4], + bxmin, bxmax, bymin, bymax, bzmin, bzmax); + p[2] = hilbert_split(vertexarray, p[4], transgc[e][d][1], transgc[e][d][2], + bxmin, bxmax, bymin, bymax, bzmin, bzmax); + p[1] = hilbert_split(vertexarray, p[2], transgc[e][d][0], transgc[e][d][1], + bxmin, bxmax, bymin, bymax, bzmin, bzmax); + p[3] = hilbert_split(&(vertexarray[p[2]]), p[4] - p[2], + transgc[e][d][2], transgc[e][d][3], + bxmin, bxmax, bymin, bymax, bzmin, bzmax) + p[2]; + p[6] = hilbert_split(&(vertexarray[p[4]]), p[8] - p[4], + transgc[e][d][5], transgc[e][d][6], + bxmin, bxmax, bymin, bymax, bzmin, bzmax) + p[4]; + p[5] = hilbert_split(&(vertexarray[p[4]]), p[6] - p[4], + transgc[e][d][4], transgc[e][d][5], + bxmin, bxmax, bymin, bymax, bzmin, bzmax) + p[4]; + p[7] = hilbert_split(&(vertexarray[p[6]]), p[8] - p[6], + transgc[e][d][6], transgc[e][d][7], + bxmin, bxmax, bymin, bymax, bzmin, bzmax) + p[6]; + + if (b->hilbert_order > 0) { + // A maximum order is prescribed. + if ((depth + 1) == b->hilbert_order) { + // The maximum prescribed order is reached. + return; + } + } + + // Recursively sort the points in sub-boxes. + for (w = 0; w < 8; w++) { + // w is the local Hilbert index (NOT Gray code). + // Sort into the sub-box either there are more than 2 points in it, or + // the prescribed order of the curve is not reached yet. + //if ((p[w+1] - p[w] > b->hilbert_limit) || (b->hilbert_order > 0)) { + if ((p[w+1] - p[w]) > b->hilbert_limit) { + // Calculcate the start point (ei) of the curve in this sub-box. + // update e = e ^ (e(w) left_rotate (d+1)). + if (w == 0) { + e_w = 0; + } else { + // calculate e(w) = gc(2 * floor((w - 1) / 2)). + k = 2 * ((w - 1) / 2); + e_w = k ^ (k >> 1); // = gc(k). + } + k = e_w; + e_w = ((k << (d+1)) & mask) | ((k >> (n-d-1)) & mask); + ei = e ^ e_w; + // Calulcate the direction (di) of the curve in this sub-box. + // update d = (d + d(w) + 1) % n + if (w == 0) { + d_w = 0; + } else { + d_w = ((w % 2) == 0) ? tsb1mod3[w - 1] : tsb1mod3[w]; + } + di = (d + d_w + 1) % n; + // Calculate the bounding box of the sub-box. + if (transgc[e][d][w] & 1) { // x-axis + x1 = 0.5 * (bxmin + bxmax); + x2 = bxmax; + } else { + x1 = bxmin; + x2 = 0.5 * (bxmin + bxmax); + } + if (transgc[e][d][w] & 2) { // y-axis + y1 = 0.5 * (bymin + bymax); + y2 = bymax; + } else { + y1 = bymin; + y2 = 0.5 * (bymin + bymax); + } + if (transgc[e][d][w] & 4) { // z-axis + z1 = 0.5 * (bzmin + bzmax); + z2 = bzmax; + } else { + z1 = bzmin; + z2 = 0.5 * (bzmin + bzmax); + } + hilbert_sort3(&(vertexarray[p[w]]), p[w+1] - p[w], ei, di, + x1, x2, y1, y2, z1, z2, depth+1); + } // if (p[w+1] - p[w] > 1) + } // w +} + +//============================================================================// +// // +// brio_multiscale_sort() Sort the points using BRIO and Hilbert curve. // +// // +//============================================================================// + +void tetgenmesh::brio_multiscale_sort(point* vertexarray, int arraysize, + int threshold, REAL ratio, int *depth) +{ + int middle; + + middle = 0; + if (arraysize >= threshold) { + (*depth)++; + middle = arraysize * ratio; + brio_multiscale_sort(vertexarray, middle, threshold, ratio, depth); + } + // Sort the right-array (rnd-th round) using the Hilbert curve. + hilbert_sort3(&(vertexarray[middle]), arraysize - middle, 0, 0, // e, d + xmin, xmax, ymin, ymax, zmin, zmax, 0); // depth. +} + +//============================================================================// +// // +// randomnation() Generate a random number between 0 and 'choices' - 1. // +// // +//============================================================================// + +unsigned long tetgenmesh::randomnation(unsigned int choices) +{ + unsigned long newrandom; + + if (choices >= 714025l) { + newrandom = (randomseed * 1366l + 150889l) % 714025l; + randomseed = (newrandom * 1366l + 150889l) % 714025l; + newrandom = newrandom * (choices / 714025l) + randomseed; + if (newrandom >= choices) { + return newrandom - choices; + } else { + return newrandom; + } + } else { + randomseed = (randomseed * 1366l + 150889l) % 714025l; + return randomseed % choices; + } +} + +//============================================================================// +// // +// randomsample() Randomly sample the tetrahedra for point loation. // +// // +// Searching begins from one of handles: the input 'searchtet', a recently // +// encountered tetrahedron 'recenttet', or from one chosen from a random // +// sample. The choice is made by determining which one's origin is closest // +// to the point we are searching for. // +// // +//============================================================================// + +void tetgenmesh::randomsample(point searchpt,triface *searchtet) +{ + tetrahedron *firsttet, *tetptr; + point torg; + void **sampleblock; + uintptr_t alignptr; + long sampleblocks, samplesperblock, samplenum; + long tetblocks, i, j; + REAL searchdist, dist; + + if (b->verbose > 2) { + printf(" Random sampling tetrahedra for searching point %d.\n", + pointmark(searchpt)); + } + + if (!nonconvex) { + if (searchtet->tet == NULL) { + // A null tet. Choose the recenttet as the starting tet. + *searchtet = recenttet; + } + + // 'searchtet' should be a valid tetrahedron. Choose the base face + // whose vertices must not be 'dummypoint'. + searchtet->ver = 3; + // Record the distance from its origin to the searching point. + torg = org(*searchtet); + searchdist = (searchpt[0] - torg[0]) * (searchpt[0] - torg[0]) + + (searchpt[1] - torg[1]) * (searchpt[1] - torg[1]) + + (searchpt[2] - torg[2]) * (searchpt[2] - torg[2]); + + // If a recently encountered tetrahedron has been recorded and has not + // been deallocated, test it as a good starting point. + if (recenttet.tet != searchtet->tet) { + recenttet.ver = 3; + torg = org(recenttet); + dist = (searchpt[0] - torg[0]) * (searchpt[0] - torg[0]) + + (searchpt[1] - torg[1]) * (searchpt[1] - torg[1]) + + (searchpt[2] - torg[2]) * (searchpt[2] - torg[2]); + if (dist < searchdist) { + *searchtet = recenttet; + searchdist = dist; + } + } + } else { + // The mesh is non-convex. Do not use 'recenttet'. + searchdist = longest; + } + + // Select "good" candidate using k random samples, taking the closest one. + // The number of random samples taken is proportional to the fourth root + // of the number of tetrahedra in the mesh. + while (samples * samples * samples * samples < tetrahedrons->items) { + samples++; + } + // Find how much blocks in current tet pool. + tetblocks = (tetrahedrons->maxitems + b->tetrahedraperblock - 1) + / b->tetrahedraperblock; + // Find the average samples per block. Each block at least have 1 sample. + samplesperblock = 1 + (samples / tetblocks); + sampleblocks = samples / samplesperblock; + if (sampleblocks == 0) { + sampleblocks = 1; // at least one sample block is needed. + } + sampleblock = tetrahedrons->firstblock; + for (i = 0; i < sampleblocks; i++) { + alignptr = (uintptr_t) (sampleblock + 1); + firsttet = (tetrahedron *) + (alignptr + (uintptr_t) tetrahedrons->alignbytes + - (alignptr % (uintptr_t) tetrahedrons->alignbytes)); + for (j = 0; j < samplesperblock; j++) { + if (i == tetblocks - 1) { + // This is the last block. + samplenum = randomnation((int) + (tetrahedrons->maxitems - (i * b->tetrahedraperblock))); + } else { + samplenum = randomnation(b->tetrahedraperblock); + } + tetptr = (tetrahedron *) + (firsttet + (samplenum * tetrahedrons->itemwords)); + torg = (point) tetptr[4]; + if (torg != (point) NULL) { + dist = (searchpt[0] - torg[0]) * (searchpt[0] - torg[0]) + + (searchpt[1] - torg[1]) * (searchpt[1] - torg[1]) + + (searchpt[2] - torg[2]) * (searchpt[2] - torg[2]); + if (dist < searchdist) { + searchtet->tet = tetptr; + searchtet->ver = 11; // torg = org(t); + searchdist = dist; + } + } else { + // A dead tet. Re-sample it. + if (i != tetblocks - 1) j--; + } + } + sampleblock = (void **) *sampleblock; + } +} + +//============================================================================// +// // +// locate() Find a tetrahedron containing a given point. // +// // +// Begins its search from 'searchtet', assume there is a line segment L from // +// a vertex of 'searchtet' to the query point 'searchpt', and simply walk // +// towards 'searchpt' by traversing all faces intersected by L. // +// // +// On completion, 'searchtet' is a tetrahedron that contains 'searchpt'. The // +// returned value indicates one of the following cases: // +// - ONVERTEX, the search point lies on the origin of 'searchtet'. // +// - ONEDGE, the search point lies on an edge of 'searchtet'. // +// - ONFACE, the search point lies on a face of 'searchtet'. // +// - INTET, the search point lies in the interior of 'searchtet'. // +// - OUTSIDE, the search point lies outside the mesh. 'searchtet' is a // +// hull face which is visible by the search point. // +// // +// WARNING: This routine is designed for convex triangulations, and will not // +// generally work after the holes and concavities have been carved. // +// // +//============================================================================// + +enum tetgenmesh::locateresult + tetgenmesh::locate_dt(point searchpt, triface* searchtet) +{ + //enum {ORGMOVE, DESTMOVE, APEXMOVE} nextmove; + REAL ori, oriorg, oridest, oriapex; + enum locateresult loc = OUTSIDE; + point toppo; + int s, i; + + if (searchtet->tet == NULL) { + searchtet->tet = recenttet.tet; + } + + if (ishulltet(*searchtet)) { + // Get its adjacent tet (inside the hull). + searchtet->tet = decode_tet_only(searchtet->tet[3]); + } + + // Let searchtet be the face such that 'searchpt' lies above to it. + for (searchtet->ver = 0; searchtet->ver < 4; searchtet->ver++) { + ori = orient3d(org(*searchtet), dest(*searchtet), apex(*searchtet), searchpt); + if (ori < 0.0) break; + } + + if (searchtet->ver == 4) { + terminatetetgen(this, 2); + } + + // Walk through tetrahedra to locate the point. + do { + + toppo = oppo(*searchtet); + + // Check if the vertex is we seek. + if (toppo == searchpt) { + // Adjust the origin of searchtet to be searchpt. + esymself(*searchtet); + eprevself(*searchtet); + loc = ONVERTEX; // return ONVERTEX; + break; + } + + // We enter from one of serarchtet's faces, which face do we exit? + // Randomly choose one of three faces (containig toppo) of this tet. + s = rand() % 3; // s \in \{0,1,2\} + for (i = 0; i < s; i++) enextself(*searchtet); + + oriorg = orient3d(dest(*searchtet), apex(*searchtet), toppo, searchpt); + if (oriorg < 0) { + //nextmove = ORGMOVE; + enextesymself(*searchtet); + } else { + oridest = orient3d(apex(*searchtet), org(*searchtet), toppo, searchpt); + if (oridest < 0) { + //nextmove = DESTMOVE; + eprevesymself(*searchtet); + } else { + oriapex = orient3d(org(*searchtet), dest(*searchtet), toppo, searchpt); + if (oriapex < 0) { + //nextmove = APEXMOVE; + esymself(*searchtet); + } else { + // oriorg >= 0, oridest >= 0, oriapex >= 0 ==> found the point. + // The point we seek must be on the boundary of or inside this + // tetrahedron. Check for boundary cases first. + if (oriorg == 0) { + // Go to the face opposite to origin. + enextesymself(*searchtet); + if (oridest == 0) { + eprevself(*searchtet); // edge oppo->apex + if (oriapex == 0) { + // oppo is duplicated with p. + loc = ONVERTEX; // return ONVERTEX; + break; + } + loc = ONEDGE; // return ONEDGE; + break; + } + if (oriapex == 0) { + enextself(*searchtet); // edge dest->oppo + loc = ONEDGE; // return ONEDGE; + break; + } + loc = ONFACE; // return ONFACE; + break; + } + if (oridest == 0) { + // Go to the face opposite to destination. + eprevesymself(*searchtet); + if (oriapex == 0) { + eprevself(*searchtet); // edge oppo->org + loc = ONEDGE; // return ONEDGE; + break; + } + loc = ONFACE; // return ONFACE; + break; + } + if (oriapex == 0) { + // Go to the face opposite to apex + esymself(*searchtet); + loc = ONFACE; // return ONFACE; + break; + } + loc = INTETRAHEDRON; + break; + } + } + } // if (locateflag) + + // Move to the next tet adjacent to the selected face. + decode(searchtet->tet[searchtet->ver & 3], *searchtet); // fsymself + + if (ishulltet(*searchtet)) { + loc = OUTSIDE; // return OUTSIDE; + break; + } + + } while (true); + + return loc; +} + +enum tetgenmesh::locateresult + tetgenmesh::locate(point searchpt, triface* searchtet, int chkencflag) +{ + point torg, tdest, tapex, toppo; + enum {ORGMOVE, DESTMOVE, APEXMOVE} nextmove; + REAL ori, oriorg, oridest, oriapex; + enum locateresult loc = OUTSIDE; + //int t1ver; + int s; + + torg = tdest = tapex = toppo = NULL; + + if (searchtet->tet == NULL) { + // A null tet. Choose the recenttet as the starting tet. + searchtet->tet = recenttet.tet; + } + + // Check if we are in the outside of the convex hull. + if (ishulltet(*searchtet)) { + // Get its adjacent tet (inside the hull). + searchtet->tet = decode_tet_only(searchtet->tet[3]); + } + + // Let searchtet be the face such that 'searchpt' lies above to it. + for (searchtet->ver = 0; searchtet->ver < 4; searchtet->ver++) { + torg = org(*searchtet); + tdest = dest(*searchtet); + tapex = apex(*searchtet); + ori = orient3d(torg, tdest, tapex, searchpt); + if (ori < 0.0) break; + } + if (searchtet->ver == 4) { + terminatetetgen(this, 2); + } + + // Walk through tetrahedra to locate the point. + while (true) { + toppo = oppo(*searchtet); + + // Check if the vertex is we seek. + if (toppo == searchpt) { + // Adjust the origin of searchtet to be searchpt. + esymself(*searchtet); + eprevself(*searchtet); + loc = ONVERTEX; // return ONVERTEX; + break; + } + + // We enter from one of serarchtet's faces, which face do we exit? + oriorg = orient3d(tdest, tapex, toppo, searchpt); + oridest = orient3d(tapex, torg, toppo, searchpt); + oriapex = orient3d(torg, tdest, toppo, searchpt); + + // Now decide which face to move. It is possible there are more than one + // faces are viable moves. If so, randomly choose one. + if (oriorg < 0) { + if (oridest < 0) { + if (oriapex < 0) { + // All three faces are possible. + s = randomnation(3); // 's' is in {0,1,2}. + if (s == 0) { + nextmove = ORGMOVE; + } else if (s == 1) { + nextmove = DESTMOVE; + } else { + nextmove = APEXMOVE; + } + } else { + // Two faces, opposite to origin and destination, are viable. + //s = randomnation(2); // 's' is in {0,1}. + if (randomnation(2)) { + nextmove = ORGMOVE; + } else { + nextmove = DESTMOVE; + } + } + } else { + if (oriapex < 0) { + // Two faces, opposite to origin and apex, are viable. + //s = randomnation(2); // 's' is in {0,1}. + if (randomnation(2)) { + nextmove = ORGMOVE; + } else { + nextmove = APEXMOVE; + } + } else { + // Only the face opposite to origin is viable. + nextmove = ORGMOVE; + } + } + } else { + if (oridest < 0) { + if (oriapex < 0) { + // Two faces, opposite to destination and apex, are viable. + //s = randomnation(2); // 's' is in {0,1}. + if (randomnation(2)) { + nextmove = DESTMOVE; + } else { + nextmove = APEXMOVE; + } + } else { + // Only the face opposite to destination is viable. + nextmove = DESTMOVE; + } + } else { + if (oriapex < 0) { + // Only the face opposite to apex is viable. + nextmove = APEXMOVE; + } else { + // The point we seek must be on the boundary of or inside this + // tetrahedron. Check for boundary cases. + if (oriorg == 0) { + // Go to the face opposite to origin. + enextesymself(*searchtet); + if (oridest == 0) { + eprevself(*searchtet); // edge oppo->apex + if (oriapex == 0) { + // oppo is duplicated with p. + loc = ONVERTEX; // return ONVERTEX; + break; + } + loc = ONEDGE; // return ONEDGE; + break; + } + if (oriapex == 0) { + enextself(*searchtet); // edge dest->oppo + loc = ONEDGE; // return ONEDGE; + break; + } + loc = ONFACE; // return ONFACE; + break; + } + if (oridest == 0) { + // Go to the face opposite to destination. + eprevesymself(*searchtet); + if (oriapex == 0) { + eprevself(*searchtet); // edge oppo->org + loc = ONEDGE; // return ONEDGE; + break; + } + loc = ONFACE; // return ONFACE; + break; + } + if (oriapex == 0) { + // Go to the face opposite to apex + esymself(*searchtet); + loc = ONFACE; // return ONFACE; + break; + } + loc = INTETRAHEDRON; // return INTETRAHEDRON; + break; + } + } + } + + // Move to the selected face. + if (nextmove == ORGMOVE) { + enextesymself(*searchtet); + } else if (nextmove == DESTMOVE) { + eprevesymself(*searchtet); + } else { + esymself(*searchtet); + } + if (chkencflag) { + // Check if we are walking across a subface. + if (issubface(*searchtet)) { + loc = ENCSUBFACE; + break; + } + } + // Move to the adjacent tetrahedron (maybe a hull tetrahedron). + decode(searchtet->tet[searchtet->ver & 3], *searchtet); // fsymself + if (ishulltet(*searchtet)) { + loc = OUTSIDE; // return OUTSIDE; + break; + } + + // Retreat the three vertices of the base face. + torg = org(*searchtet); + tdest = dest(*searchtet); + tapex = apex(*searchtet); + + } // while (true) + + return loc; +} + +//============================================================================// +// // +// insert_vertex_bw() Insert a vertex using the Bowyer-Watson algorithm. // +// // +// This function is only used for initial Delaunay triangulation construction.// +// It improves the speed of incremental algorithm. // +// // +//============================================================================// + +int tetgenmesh::insert_vertex_bw(point insertpt, triface *searchtet, + insertvertexflags *ivf) +{ + tetrahedron **ptptr, *tptr; + triface cavetet, spintet, neightet, neineitet, *parytet; + triface oldtet, newtet; //, newneitet; + point *pts; //, pa, pb, pc, *parypt; + enum locateresult loc = OUTSIDE; + REAL sign, ori; + //REAL attrib, volume; + bool enqflag; + int t1ver; + int i, j, k; //, s; + + if (b->verbose > 2) { + printf(" Insert point %d\n", pointmark(insertpt)); + } + + // Locate the point. + if (searchtet->tet != NULL) { + loc = (enum locateresult) ivf->iloc; + } + + if (loc == OUTSIDE) { + if (searchtet->tet == NULL) { + if (!b->weighted) { + randomsample(insertpt, searchtet); + } else { + // Weighted DT. There may exist dangling vertex. + *searchtet = recenttet; + } + } + loc = locate_dt(insertpt, searchtet); + } + + ivf->iloc = (int) loc; // The return value. + + if (b->weighted) { + if (loc != OUTSIDE) { + // Check if this vertex is regular. + pts = (point *) searchtet->tet; + sign = orient4d_s(pts[4], pts[5], pts[6], pts[7], insertpt, + pts[4][3], pts[5][3], pts[6][3], pts[7][3], + insertpt[3]); + if (sign > 0) { + // This new vertex lies above the lower hull. Do not insert it. + ivf->iloc = (int) NONREGULAR; + return 0; + } + } + } + + // Create the initial cavity C(p) which contains all tetrahedra that + // intersect p. It may include 1, 2, or n tetrahedra. + + if (loc == OUTSIDE) { + infect(*searchtet); + cave_oldtet_list->newindex((void **) &ptptr); + *ptptr = searchtet->tet; + } else if (loc == INTETRAHEDRON) { + infect(*searchtet); + cave_oldtet_list->newindex((void **) &ptptr); + *ptptr = searchtet->tet; + } else if (loc == ONFACE) { + infect(*searchtet); + cave_oldtet_list->newindex((void **) &ptptr); + *ptptr = searchtet->tet; + neightet.tet = decode_tet_only(searchtet->tet[searchtet->ver & 3]); + infect(neightet); + cave_oldtet_list->newindex((void **) &ptptr); + *ptptr = neightet.tet; + } else if (loc == ONEDGE) { + + // Add all adjacent boundary tets into list. + spintet = *searchtet; + while (1) { + infect(spintet); + cave_oldtet_list->newindex((void **) &ptptr); + *ptptr = spintet.tet; + fnextself(spintet); + if (spintet.tet == searchtet->tet) break; + } // while (1) + } else if (loc == ONVERTEX) { + // The point already exist. Do nothing and return. + return 0; + } + + // Create the cavity C(p). + + for (i = 0; i < cave_oldtet_list->objects; i++) { + ptptr = (tetrahedron **) fastlookup(cave_oldtet_list, i); + cavetet.tet = *ptptr; + for (cavetet.ver = 0; cavetet.ver < 4; cavetet.ver++) { + neightet.tet = decode_tet_only(cavetet.tet[cavetet.ver]); + if (!infected(neightet)) { + // neightet.tet is current outside the cavity. + enqflag = false; + if (!marktested(neightet)) { + if (!ishulltet(neightet)) { + pts = (point *) neightet.tet; + sign = insphere_s(pts[4], pts[5], pts[6], pts[7], insertpt); + enqflag = (sign < 0.0); + } else { + pts = (point *) neightet.tet; + ori = orient3d(pts[4], pts[5], pts[6], insertpt); + if (ori < 0) { + // A visible hull face. + enqflag = true; + } else if (ori == 0.) { + // A coplanar hull face. We need to test if this hull face is + // Delaunay or not. We test if the adjacent tet (not faked) + // of this hull face is Delaunay or not. + triface neineitet; + neineitet.tet = decode_tet_only(neightet.tet[3]); + pts = (point *) neineitet.tet; + sign = insphere_s(pts[4],pts[5],pts[6],pts[7], insertpt); + enqflag = (sign < 0.0); + } + } + marktest(neightet); + } + if (enqflag) { + infect(neightet); + cave_oldtet_list->newindex((void **) &ptptr); + *ptptr = neightet.tet; + } else { + // A boundary face. + cavebdrylist->newindex((void **) &parytet); + *parytet = cavetet; + } + } // if (!infected(neightet)) + } + } // i + + // Create new tetrahedra to fill the cavity. + int f_out = cavebdrylist->objects; + int v_out = (f_out + 4) / 2; + + + triface *pcavetet; + point V[3]; + int local_vcount = 0; // local index of vertex + int sidx[3]; + + static int row_v08_tbl[12] = {8,9,10,11,0,1,2,3,4,5,6,7}; + static int row_v11_tbl[12] = {8,9,10,11,0,1,2,3,4,5,6,7}; + static int col_v01_tbl[12] = {1,1,1,1,5,5,5,5,9,9,9,9}; + static int col_v02_tbl[12] = {2,2,2,2,6,6,6,6,10,10,10,10}; + static int col_v08_tbl[12] = {8,8,8,8,0,0,0,0,4,4,4,4}; + static int col_v11_tbl[12] = {11,11,11,11,3,3,3,3,7,7,7,7}; + + triface *tmp_bw_faces = NULL; + int shiftbits = 0; + + if (v_out < 64) { + shiftbits = 6; + tmp_bw_faces = _bw_faces; + } else if (v_out < 1024) { + // Dynamically allocate an array to store the adjacencies. + int arysize = 1; + int tmp = v_out; + shiftbits = 1; + while ((tmp >>= 1)) shiftbits++; + arysize <<= shiftbits; + tmp_bw_faces = new triface[arysize * arysize]; + } + + if (v_out < 1024) { + for (i = 0; i < f_out; i++) { + pcavetet = (triface *) fastlookup(cavebdrylist, i); + oldtet = *pcavetet; + + // Get the tet outside the cavity. + decode(oldtet.tet[oldtet.ver], neightet); + unmarktest(neightet); + + if (ishulltet(oldtet)) { + // neightet.tet may be also a hull tet (=> oldtet is a hull edge). + neightet.ver = epivot[neightet.ver]; + if ((apex(neightet) == dummypoint)) { + hullsize++; // Create a new hull tet. + } + } + + // Create a new tet in the cavity. + V[0] = dest(neightet); + V[1] = org(neightet); + V[2] = apex(neightet); + maketetrahedron2(&newtet, V[1], V[0], insertpt, V[2]); + //bond(newtet, neightet); + newtet.tet[2] = encode2(neightet.tet, neightet.ver); + neightet.tet[neightet.ver & 3] = encode2(newtet.tet, col_v02_tbl[neightet.ver]); + + // Fill the adjacency matrix, and count v_out. + for (j = 0; j < 3; j++) { + tptr = (tetrahedron *) point2tet(V[j]); + if (((point *) tptr)[6] != insertpt) { + // Found a unique vertex of the cavity. + setpointgeomtag(V[j], local_vcount++); + //local_vcount++; + setpoint2tet(V[j], (tetrahedron) (newtet.tet)); + } + sidx[j] = pointgeomtag(V[j]); + } // j + + neightet.tet = newtet.tet; + // Avoid using lookup tables. + neightet.ver = 11; + tmp_bw_faces[(sidx[1] << shiftbits) | sidx[0]] = neightet; + neightet.ver = 1; + tmp_bw_faces[(sidx[2] << shiftbits) | sidx[1]] = neightet; + neightet.ver = 8; + tmp_bw_faces[(sidx[0] << shiftbits) | sidx[2]] = neightet; + + *pcavetet = newtet; + } // i // f_out + + // Set a handle for speeding point location. + // Randomly pick a new tet. + i = rand() % f_out; + recenttet = * (triface *) fastlookup(cavebdrylist, i); + setpoint2tet(insertpt, (tetrahedron) (recenttet.tet)); + + for (i = 0; i < f_out; i++) { + neightet = * (triface *) fastlookup(cavebdrylist, i); + if (neightet.tet[3] == NULL) { + neightet.ver = 11; + j = pointgeomtag(org(neightet)); + k = pointgeomtag(dest(neightet)); + neineitet = tmp_bw_faces[(k << shiftbits) | j]; + // bondtbl[i][j] = (j & 3) + (((i & 12) + (j & 12)) % 12); + neightet.tet[3] = encode2(neineitet.tet, row_v11_tbl[neineitet.ver]); + neineitet.tet[neineitet.ver & 3] = encode2(neightet.tet, col_v11_tbl[neineitet.ver]); + } + if (neightet.tet[1] == NULL) { + neightet.ver = 1; + j = pointgeomtag(org(neightet)); + k = pointgeomtag(dest(neightet)); + neineitet = tmp_bw_faces[(k << shiftbits) | j]; + neightet.tet[1] = encode2(neineitet.tet, neineitet.ver); // row_v01_tbl + neineitet.tet[neineitet.ver & 3] = encode2(neightet.tet, col_v01_tbl[neineitet.ver]); + } + if (neightet.tet[0] == NULL) { + neightet.ver = 8; + j = pointgeomtag(org(neightet)); + k = pointgeomtag(dest(neightet)); + neineitet = tmp_bw_faces[(k << shiftbits) | j]; + // bondtbl[i][j] = (j & 3) + (((i & 12) + (j & 12)) % 12); + neightet.tet[0] = encode2(neineitet.tet, row_v08_tbl[neineitet.ver]); + neineitet.tet[neineitet.ver & 3] = encode2(neightet.tet, col_v08_tbl[neineitet.ver]); + } + } // i + + if (v_out >= 64) { + delete [] tmp_bw_faces; + } + } // v_out < 1024 + else { + // Fill a very large cavity with original neighboring searching method. + for (i = 0; i < f_out; i++) { + pcavetet = (triface *) fastlookup(cavebdrylist, i); + oldtet = *pcavetet; + + // Get the tet outside the cavity. + decode(oldtet.tet[oldtet.ver], neightet); + unmarktest(neightet); + + if (ishulltet(oldtet)) { + // neightet.tet may be also a hull tet (=> oldtet is a hull edge). + neightet.ver = epivot[neightet.ver]; + if ((apex(neightet) == dummypoint)) { + hullsize++; // Create a new hull tet. + } + } + + // Create a new tet in the cavity. + V[0] = dest(neightet); + V[1] = org(neightet); + V[2] = apex(neightet); + maketetrahedron2(&newtet, V[1], V[0], insertpt, V[2]); + //newtet.ver = 2; // esymself(newtet); + //assert(oppo(newtet) == insertpt); + + //bond(newtet, neightet); + newtet.tet[2] = encode2(neightet.tet, neightet.ver); + neightet.tet[neightet.ver & 3] = encode2(newtet.tet, col_v02_tbl[neightet.ver]); + + // Fill the adjacency matrix, and count v_out. + for (j = 0; j < 3; j++) { + tptr = (tetrahedron *) point2tet(V[j]); + if (((point *) tptr)[6] != insertpt) { + // Found a unique vertex of the cavity. + //setpointgeomtag(V[j], local_vcount); + local_vcount++; + setpoint2tet(V[j], (tetrahedron) (newtet.tet)); + } + //sidx[j] = pointgeomtag(V[j]); + } // j + } // i, f_out + + // Set a handle for speeding point location. + //recenttet = newtet; + //setpoint2tet(insertpt, (tetrahedron) (newtet.tet)); + i = rand() % f_out; + recenttet = * (triface *) fastlookup(cavebdrylist, i); + // This is still an oldtet. + fsymself(recenttet); + fsymself(recenttet); + setpoint2tet(insertpt, (tetrahedron) (recenttet.tet)); + + for (i = 0; i < f_out; i++) { + pcavetet = (triface *) fastlookup(cavebdrylist, i); + oldtet = *pcavetet; + + fsym(oldtet, neightet); + fsym(neightet, newtet); + // Comment: oldtet and newtet must be at the same directed edge. + // Connect the three other faces of this newtet. + for (j = 0; j < 3; j++) { + esym(newtet, neightet); // Go to the face. + if (neightet.tet[neightet.ver & 3] == NULL) { + // Find the adjacent face of this newtet. + spintet = oldtet; + while (1) { + fnextself(spintet); + if (!infected(spintet)) break; + } + fsym(spintet, neineitet); + esymself(neineitet); + bond(neightet, neineitet); + } + enextself(newtet); + enextself(oldtet); + } // j + } // i + } // fill cavity + + // C(p) is re-meshed successfully. + + // Delete the old tets in C(p). + for (i = 0; i < cave_oldtet_list->objects; i++) { + oldtet.tet = *(tetrahedron **) fastlookup(cave_oldtet_list, i); + if (ishulltet(oldtet)) { + hullsize--; + } + tetrahedrondealloc(oldtet.tet); + } + + cave_oldtet_list->restart(); + cavebdrylist->restart(); + + return 1; +} + +//============================================================================// +// // +// initialdelaunay() Create an initial Delaunay tetrahedralization. // +// // +// The tetrahedralization contains only one tetrahedron abcd, and four hull // +// tetrahedra. The points pa, pb, pc, and pd must be linearly independent. // +// // +//============================================================================// + +void tetgenmesh::initialdelaunay(point pa, point pb, point pc, point pd) +{ + triface firsttet, tetopa, tetopb, tetopc, tetopd; + triface worktet, worktet1; + + if (b->verbose > 2) { + printf(" Create init tet (%d, %d, %d, %d)\n", pointmark(pa), + pointmark(pb), pointmark(pc), pointmark(pd)); + } + + // Create the first tetrahedron. + maketetrahedron2(&firsttet, pa, pb, pc, pd); + //setvertices(firsttet, pa, pb, pc, pd); + + // Create four hull tetrahedra. + maketetrahedron2(&tetopa, pb, pc, pd, dummypoint); + //setvertices(tetopa, pb, pc, pd, dummypoint); + maketetrahedron2(&tetopb, pc, pa, pd, dummypoint); + //setvertices(tetopb, pc, pa, pd, dummypoint); + maketetrahedron2(&tetopc, pa, pb, pd, dummypoint); + //setvertices(tetopc, pa, pb, pd, dummypoint); + maketetrahedron2(&tetopd, pb, pa, pc, dummypoint); + //setvertices(tetopd, pb, pa, pc, dummypoint); + + hullsize += 4; + + // Connect hull tetrahedra to firsttet (at four faces of firsttet). + bond(firsttet, tetopd); + esym(firsttet, worktet); + bond(worktet, tetopc); // ab + enextesym(firsttet, worktet); + bond(worktet, tetopa); // bc + eprevesym(firsttet, worktet); + bond(worktet, tetopb); // ca + + // Connect hull tetrahedra together (at six edges of firsttet). + esym(tetopc, worktet); + esym(tetopd, worktet1); + bond(worktet, worktet1); // ab + esym(tetopa, worktet); + eprevesym(tetopd, worktet1); + bond(worktet, worktet1); // bc + esym(tetopb, worktet); + enextesym(tetopd, worktet1); + bond(worktet, worktet1); // ca + eprevesym(tetopc, worktet); + enextesym(tetopb, worktet1); + bond(worktet, worktet1); // da + eprevesym(tetopa, worktet); + enextesym(tetopc, worktet1); + bond(worktet, worktet1); // db + eprevesym(tetopb, worktet); + enextesym(tetopa, worktet1); + bond(worktet, worktet1); // dc + + // Set the vertex type. + if (pointtype(pa) == UNUSEDVERTEX) { + setpointtype(pa, VOLVERTEX); + } + if (pointtype(pb) == UNUSEDVERTEX) { + setpointtype(pb, VOLVERTEX); + } + if (pointtype(pc) == UNUSEDVERTEX) { + setpointtype(pc, VOLVERTEX); + } + if (pointtype(pd) == UNUSEDVERTEX) { + setpointtype(pd, VOLVERTEX); + } + + setpoint2tet(pa, encode(firsttet)); + setpoint2tet(pb, encode(firsttet)); + setpoint2tet(pc, encode(firsttet)); + setpoint2tet(pd, encode(firsttet)); + + setpoint2tet(dummypoint, encode(tetopa)); + + // Remember the first tetrahedron. + recenttet = firsttet; +} + + +//============================================================================// +// // +// incrementaldelaunay() Create a Delaunay tetrahedralization by // +// the incremental approach. // +// // +//============================================================================// + + +void tetgenmesh::incrementaldelaunay(clock_t& tv) +{ + triface searchtet; + point *permutarray, swapvertex; + REAL v1[3], v2[3], n[3]; + REAL bboxsize, bboxsize2, bboxsize3, ori; + int randindex; + int ngroup = 0; + int i, j; + + if (!b->quiet) { + printf("Delaunizing vertices...\n"); + } + // Form a random permuation (uniformly at random) of the set of vertices. + permutarray = new point[in->numberofpoints]; + points->traversalinit(); + + if (b->no_sort) { + if (b->verbose) { + printf(" Using the input order.\n"); + } + for (i = 0; i < in->numberofpoints; i++) { + permutarray[i] = (point) points->traverse(); + } + } else { + if (b->verbose) { + printf(" Permuting vertices.\n"); + } + srand(in->numberofpoints); + for (i = 0; i < in->numberofpoints; i++) { + randindex = rand() % (i + 1); // randomnation(i + 1); + permutarray[i] = permutarray[randindex]; + permutarray[randindex] = (point) points->traverse(); + } + if (b->brio_hilbert) { // -b option + if (b->verbose) { + printf(" Sorting vertices.\n"); + } + hilbert_init(in->mesh_dim); + brio_multiscale_sort(permutarray, in->numberofpoints, b->brio_threshold, + b->brio_ratio, &ngroup); + } + } + + tv = clock(); // Remember the time for sorting points. + + // Calculate the diagonal size of its bounding box. + bboxsize = sqrt(norm2(xmax - xmin, ymax - ymin, zmax - zmin)); + bboxsize2 = bboxsize * bboxsize; + bboxsize3 = bboxsize2 * bboxsize; + + // Make sure the second vertex is not identical with the first one. + i = 1; + while ((distance(permutarray[0],permutarray[i])/bboxsize)epsilon) { + i++; + if (i == in->numberofpoints - 1) { + printf("Exception: All vertices are (nearly) identical (Tol = %g).\n", + b->epsilon); + terminatetetgen(this, 10); + } + } + if (i > 1) { + // Swap to move the non-identical vertex from index i to index 1. + swapvertex = permutarray[i]; + permutarray[i] = permutarray[1]; + permutarray[1] = swapvertex; + } + + // Make sure the third vertex is not collinear with the first two. + i = 2; + for (j = 0; j < 3; j++) { + v1[j] = permutarray[1][j] - permutarray[0][j]; + v2[j] = permutarray[i][j] - permutarray[0][j]; + } + cross(v1, v2, n); + while ((sqrt(norm2(n[0], n[1], n[2])) / bboxsize2) < b->epsilon) { + i++; + if (i == in->numberofpoints - 1) { + printf("Exception: All vertices are (nearly) collinear (Tol = %g).\n", + b->epsilon); + terminatetetgen(this, 10); + } + for (j = 0; j < 3; j++) { + v2[j] = permutarray[i][j] - permutarray[0][j]; + } + cross(v1, v2, n); + } + if (i > 2) { + // Swap to move the non-identical vertex from index i to index 1. + swapvertex = permutarray[i]; + permutarray[i] = permutarray[2]; + permutarray[2] = swapvertex; + } + + // Make sure the fourth vertex is not coplanar with the first three. + i = 3; + ori = orient3dfast(permutarray[0], permutarray[1], permutarray[2], + permutarray[i]); + while ((fabs(ori) / bboxsize3) < b->epsilon) { + i++; + if (i == in->numberofpoints) { + printf("Exception: All vertices are coplanar (Tol = %g).\n", + b->epsilon); + terminatetetgen(this, 10); + } + ori = orient3dfast(permutarray[0], permutarray[1], permutarray[2], + permutarray[i]); + } + if (i > 3) { + // Swap to move the non-identical vertex from index i to index 1. + swapvertex = permutarray[i]; + permutarray[i] = permutarray[3]; + permutarray[3] = swapvertex; + } + + // Orient the first four vertices in permutarray so that they follow the + // right-hand rule. + if (ori > 0.0) { + // Swap the first two vertices. + swapvertex = permutarray[0]; + permutarray[0] = permutarray[1]; + permutarray[1] = swapvertex; + } + + // Create the initial Delaunay tetrahedralization. + initialdelaunay(permutarray[0], permutarray[1], permutarray[2], + permutarray[3]); + + if (b->verbose) { + printf(" Incrementally inserting vertices.\n"); + } + insertvertexflags ivf; + flipconstraints fc; + + ivf.bowywat = 1; // Use Bowyer-Watson algorithm + ivf.lawson = 0; + + + for (i = 4; i < in->numberofpoints; i++) { + if (pointtype(permutarray[i]) == UNUSEDVERTEX) { + setpointtype(permutarray[i], VOLVERTEX); + } + if (b->brio_hilbert || b->no_sort) { // -b or -b/1 + // Start the last updated tet. + searchtet.tet = recenttet.tet; + } else { // -b0 + // Randomly choose the starting tet for point location. + searchtet.tet = NULL; + } + ivf.iloc = (int) OUTSIDE; + // Insert the vertex. + if (!insert_vertex_bw(permutarray[i], &searchtet, &ivf)) { + if (ivf.iloc == (int) ONVERTEX) { + // The point already exists. Mark it and do nothing on it. + swapvertex = org(searchtet); + if (b->object != tetgenbehavior::STL) { + if (!b->quiet) { + printf("Warning: Point #%d is coincident with #%d. Ignored!\n", + pointmark(permutarray[i]), pointmark(swapvertex)); + } + } + setpoint2ppt(permutarray[i], swapvertex); + setpointtype(permutarray[i], DUPLICATEDVERTEX); + dupverts++; + } else if (ivf.iloc == (int) NEARVERTEX) { + // This should not happen by insert_point_bw(). + terminatetetgen(this, 2); // report a bug. + } else if (ivf.iloc == (int) NONREGULAR) { + // The point is non-regular. Skipped. + if (b->verbose) { + printf(" Point #%d is non-regular, skipped.\n", + pointmark(permutarray[i])); + } + setpointtype(permutarray[i], NREGULARVERTEX); + nonregularcount++; + } + } + } + + + + delete [] permutarray; +} + +// // +// // +//== delaunay_cxx ============================================================// + +//== surface_cxx =============================================================// +// // +// // + +//============================================================================// +// // +// flipshpush() Push a facet edge into flip stack. // +// // +//============================================================================// + +void tetgenmesh::flipshpush(face* flipedge) +{ + badface *newflipface; + + newflipface = (badface *) flippool->alloc(); + newflipface->ss = *flipedge; + newflipface->forg = sorg(*flipedge); + newflipface->fdest = sdest(*flipedge); + newflipface->nextitem = flipstack; + flipstack = newflipface; +} + +//============================================================================// +// // +// flip22() Perform a 2-to-2 flip in surface mesh. // +// // +// 'flipfaces' is an array of two subfaces. On input, they are [a,b,c] and // +// [b,a,d]. On output, they are [c,d,b] and [d,c,a]. As a result, edge [a,b] // +// is replaced by edge [c,d]. // +// // +//============================================================================// + +void tetgenmesh::flip22(face* flipfaces, int flipflag, int chkencflag) +{ + face bdedges[4], outfaces[4], infaces[4]; + face bdsegs[4]; + face checkface; + point pa, pb, pc, pd; + int i; + + pa = sorg(flipfaces[0]); + pb = sdest(flipfaces[0]); + pc = sapex(flipfaces[0]); + pd = sapex(flipfaces[1]); + + if (sorg(flipfaces[1]) != pb) { + sesymself(flipfaces[1]); + } + + flip22count++; + + // Collect the four boundary edges. + senext(flipfaces[0], bdedges[0]); + senext2(flipfaces[0], bdedges[1]); + senext(flipfaces[1], bdedges[2]); + senext2(flipfaces[1], bdedges[3]); + + // Collect outer boundary faces. + for (i = 0; i < 4; i++) { + spivot(bdedges[i], outfaces[i]); + infaces[i] = outfaces[i]; + sspivot(bdedges[i], bdsegs[i]); + if (outfaces[i].sh != NULL) { + if (isshsubseg(bdedges[i])) { + spivot(infaces[i], checkface); + while (checkface.sh != bdedges[i].sh) { + infaces[i] = checkface; + spivot(infaces[i], checkface); + } + } + } + } + + // The flags set in these two subfaces do not change. + // Shellmark does not change. + // area constraint does not change. + + // Transform [a,b,c] -> [c,d,b]. + setshvertices(flipfaces[0], pc, pd, pb); + // Transform [b,a,d] -> [d,c,a]. + setshvertices(flipfaces[1], pd, pc, pa); + + // Update the point-to-subface map. + if (pointtype(pa) == FREEFACETVERTEX) { + setpoint2sh(pa, sencode(flipfaces[1])); + } + if (pointtype(pb) == FREEFACETVERTEX) { + setpoint2sh(pb, sencode(flipfaces[0])); + } + if (pointtype(pc) == FREEFACETVERTEX) { + setpoint2sh(pc, sencode(flipfaces[0])); + } + if (pointtype(pd) == FREEFACETVERTEX) { + setpoint2sh(pd, sencode(flipfaces[0])); + } + + // Reconnect boundary edges to outer boundary faces. + for (i = 0; i < 4; i++) { + if (outfaces[(3 + i) % 4].sh != NULL) { + // Make sure that the subface has the ori as the segment. + if (bdsegs[(3 + i) % 4].sh != NULL) { + bdsegs[(3 + i) % 4].shver = 0; + if (sorg(bdedges[i]) != sorg(bdsegs[(3 + i) % 4])) { + sesymself(bdedges[i]); + } + } + sbond1(bdedges[i], outfaces[(3 + i) % 4]); + sbond1(infaces[(3 + i) % 4], bdedges[i]); + } else { + sdissolve(bdedges[i]); + } + if (bdsegs[(3 + i) % 4].sh != NULL) { + ssbond(bdedges[i], bdsegs[(3 + i) % 4]); + if (chkencflag & 1) { + // Queue this segment for encroaching check. + enqueuesubface(badsubsegs, &(bdsegs[(3 + i) % 4])); + } + } else { + ssdissolve(bdedges[i]); + } + } + + if (chkencflag & 2) { + // Queue the flipped subfaces for quality/encroaching checks. + for (i = 0; i < 2; i++) { + enqueuesubface(badsubfacs, &(flipfaces[i])); + } + } + + recentsh = flipfaces[0]; + + if (flipflag) { + // Put the boundary edges into flip stack. + for (i = 0; i < 4; i++) { + flipshpush(&(bdedges[i])); + } + } +} + +//============================================================================// +// // +// flip31() Remove a vertex by transforming 3-to-1 subfaces. // +// // +// 'flipfaces' is an array of subfaces. Its length is at least 4. On input, // +// the first three faces are: [p,a,b], [p,b,c], and [p,c,a]. This routine // +// replaces them by one face [a,b,c], it is returned in flipfaces[3]. // +// // +// NOTE: The three old subfaces are not deleted within this routine. They // +// still hold pointers to their adjacent subfaces. These informations are // +// needed by the routine 'sremovevertex()' for recovering a segment. // +// The caller of this routine must delete the old subfaces after their uses. // +// // +//============================================================================// + +void tetgenmesh::flip31(face* flipfaces, int flipflag) +{ + face bdedges[3], outfaces[3], infaces[3]; + face bdsegs[3]; + face checkface; + point pa, pb, pc; + int i; + + pa = sdest(flipfaces[0]); + pb = sdest(flipfaces[1]); + pc = sdest(flipfaces[2]); + + flip31count++; + + // Collect all infos at the three boundary edges. + for (i = 0; i < 3; i++) { + senext(flipfaces[i], bdedges[i]); + spivot(bdedges[i], outfaces[i]); + infaces[i] = outfaces[i]; + sspivot(bdedges[i], bdsegs[i]); + if (outfaces[i].sh != NULL) { + if (isshsubseg(bdedges[i])) { + spivot(infaces[i], checkface); + while (checkface.sh != bdedges[i].sh) { + infaces[i] = checkface; + spivot(infaces[i], checkface); + } + } + } + } // i + + // Create a new subface. + makeshellface(subfaces, &(flipfaces[3])); + setshvertices(flipfaces[3], pa, pb,pc); + setshellmark(flipfaces[3], shellmark(flipfaces[0])); + if (checkconstraints) { + //area = areabound(flipfaces[0]); + setareabound(flipfaces[3], areabound(flipfaces[0])); + } + if (useinsertradius) { + setfacetindex(flipfaces[3], getfacetindex(flipfaces[0])); + } + + // Update the point-to-subface map. + if (pointtype(pa) == FREEFACETVERTEX) { + setpoint2sh(pa, sencode(flipfaces[3])); + } + if (pointtype(pb) == FREEFACETVERTEX) { + setpoint2sh(pb, sencode(flipfaces[3])); + } + if (pointtype(pc) == FREEFACETVERTEX) { + setpoint2sh(pc, sencode(flipfaces[3])); + } + + // Update the three new boundary edges. + bdedges[0] = flipfaces[3]; // [a,b] + senext(flipfaces[3], bdedges[1]); // [b,c] + senext2(flipfaces[3], bdedges[2]); // [c,a] + + // Reconnect boundary edges to outer boundary faces. + for (i = 0; i < 3; i++) { + if (outfaces[i].sh != NULL) { + // Make sure that the subface has the ori as the segment. + if (bdsegs[i].sh != NULL) { + bdsegs[i].shver = 0; + if (sorg(bdedges[i]) != sorg(bdsegs[i])) { + sesymself(bdedges[i]); + } + } + sbond1(bdedges[i], outfaces[i]); + sbond1(infaces[i], bdedges[i]); + } + if (bdsegs[i].sh != NULL) { + ssbond(bdedges[i], bdsegs[i]); + } + } + + recentsh = flipfaces[3]; + + if (flipflag) { + // Put the boundary edges into flip stack. + for (i = 0; i < 3; i++) { + flipshpush(&(bdedges[i])); + } + } +} + +//============================================================================// +// // +// lawsonflip() Flip non-locally Delaunay edges. // +// // +//============================================================================// + +long tetgenmesh::lawsonflip() +{ + badface *popface; + face flipfaces[2]; + point pa, pb, pc, pd; + REAL sign; + long flipcount = 0; + + if (b->verbose > 2) { + printf(" Lawson flip %ld edges.\n", flippool->items); + } + + while (flipstack != (badface *) NULL) { + + // Pop an edge from the stack. + popface = flipstack; + flipfaces[0] = popface->ss; + pa = popface->forg; + pb = popface->fdest; + flipstack = popface->nextitem; // The next top item in stack. + flippool->dealloc((void *) popface); + + // Skip it if it is dead. + if (flipfaces[0].sh[3] == NULL) continue; + // Skip it if it is not the same edge as we saved. + if ((sorg(flipfaces[0]) != pa) || (sdest(flipfaces[0]) != pb)) continue; + // Skip it if it is a subsegment. + if (isshsubseg(flipfaces[0])) continue; + + // Get the adjacent face. + spivot(flipfaces[0], flipfaces[1]); + if (flipfaces[1].sh == NULL) continue; // Skip a hull edge. + pc = sapex(flipfaces[0]); + pd = sapex(flipfaces[1]); + + sign = incircle3d(pa, pb, pc, pd); + + if (sign < 0) { + // It is non-locally Delaunay. Flip it. + flip22(flipfaces, 1, 0); + flipcount++; + } + } + + if (b->verbose > 2) { + printf(" Performed %ld flips.\n", flipcount); + } + + return flipcount; +} + +//============================================================================// +// // +// sinsertvertex() Insert a vertex into a triangulation of a facet. // +// // +// This function uses three global arrays: 'caveshlist', 'caveshbdlist', and // +// 'caveshseglist'. On return, 'caveshlist' contains old subfaces in C(p), // +// 'caveshbdlist' contains new subfaces in C(p). If the new point lies on a // +// segment, 'cavesegshlist' returns the two new subsegments. // +// // +// 'iloc' suggests the location of the point. If it is OUTSIDE, this routine // +// will first locate the point. It starts searching from 'searchsh' or 'rec- // +// entsh' if 'searchsh' is NULL. // +// // +// If 'bowywat' is set (1), the Bowyer-Watson algorithm is used to insert // +// the vertex. Otherwise, only insert the vertex in the initial cavity. // +// // +// If 'iloc' is 'INSTAR', this means the cavity of this vertex was already // +// provided in the list 'caveshlist'. // +// // +// If 'splitseg' is not NULL, the new vertex lies on the segment and it will // +// be split. 'iloc' must be either 'ONEDGE' or 'INSTAR'. // +// // +// 'rflag' (rounding) is a parameter passed to slocate() function. If it is // +// set, after the location of the point is found, either ONEDGE or ONFACE, // +// round the result using an epsilon. // +// // +// NOTE: the old subfaces in C(p) are not deleted. They're needed in case we // +// want to remove the new point immediately. // +// // +//============================================================================// + +int tetgenmesh::sinsertvertex(point insertpt, face *searchsh, face *splitseg, + int iloc, int bowywat, int rflag) +{ + face cavesh, neighsh, *parysh; + face newsh, casout, casin; + face checkseg; + point pa, pb; + enum locateresult loc = OUTSIDE; + REAL sign, ori; + int i, j; + + if (b->verbose > 2) { + printf(" Insert facet point %d.\n", pointmark(insertpt)); + } + + if (bowywat == 3) { + loc = INSTAR; + } + + if ((splitseg != NULL) && (splitseg->sh != NULL)) { + // A segment is going to be split, no point location. + spivot(*splitseg, *searchsh); + if (loc != INSTAR) loc = ONEDGE; + } else { + if (loc != INSTAR) loc = (enum locateresult) iloc; + if (loc == OUTSIDE) { + // Do point location in surface mesh. + if (searchsh->sh == NULL) { + *searchsh = recentsh; + } + // Search the vertex. An above point must be provided ('aflag' = 1). + loc = slocate(insertpt, searchsh, 1, 1, rflag); + } + } + + + // Form the initial sC(p). + if (loc == ONFACE) { + // Add the face into list (in B-W cavity). + smarktest(*searchsh); + caveshlist->newindex((void **) &parysh); + *parysh = *searchsh; + } else if (loc == ONEDGE) { + if ((splitseg != NULL) && (splitseg->sh != NULL)) { + splitseg->shver = 0; + pa = sorg(*splitseg); + } else { + pa = sorg(*searchsh); + } + if (searchsh->sh != NULL) { + // Collect all subfaces share at this edge. + neighsh = *searchsh; + while (1) { + // Adjust the origin of its edge to be 'pa'. + if (sorg(neighsh) != pa) sesymself(neighsh); + // Add this face into list (in B-W cavity). + smarktest(neighsh); + caveshlist->newindex((void **) &parysh); + *parysh = neighsh; + // Add this face into face-at-splitedge list. + cavesegshlist->newindex((void **) &parysh); + *parysh = neighsh; + // Go to the next face at the edge. + spivotself(neighsh); + // Stop if all faces at the edge have been visited. + if (neighsh.sh == searchsh->sh) break; + if (neighsh.sh == NULL) break; + } + } // If (not a non-dangling segment). + } else if (loc == ONVERTEX) { + return (int) loc; + } else if (loc == OUTSIDE) { + // Comment: This should only happen during the surface meshing step. + // Enlarge the convex hull of the triangulation by including p. + // An above point of the facet is set in 'dummypoint' to replace + // orient2d tests by orient3d tests. + // Imagine that the current edge a->b (in 'searchsh') is horizontal in a + // plane, and a->b is directed from left to right, p lies above a->b. + // Find the right-most edge of the triangulation which is visible by p. + neighsh = *searchsh; + while (1) { + senext2self(neighsh); + spivot(neighsh, casout); + if (casout.sh == NULL) { + // A convex hull edge. Is it visible by p. + ori = orient3d(sorg(neighsh), sdest(neighsh), dummypoint, insertpt); + if (ori < 0) { + *searchsh = neighsh; // Visible, update 'searchsh'. + } else { + break; // 'searchsh' is the right-most visible edge. + } + } else { + if (sorg(casout) != sdest(neighsh)) sesymself(casout); + neighsh = casout; + } + } + // Create new triangles for all visible edges of p (from right to left). + casin.sh = NULL; // No adjacent face at right. + pa = sorg(*searchsh); + pb = sdest(*searchsh); + while (1) { + // Create a new subface on top of the (visible) edge. + makeshellface(subfaces, &newsh); + setshvertices(newsh, pb, pa, insertpt); + setshellmark(newsh, shellmark(*searchsh)); + if (checkconstraints) { + //area = areabound(*searchsh); + setareabound(newsh, areabound(*searchsh)); + } + if (useinsertradius) { + setfacetindex(newsh, getfacetindex(*searchsh)); + } + // Connect the new subface to the bottom subfaces. + sbond1(newsh, *searchsh); + sbond1(*searchsh, newsh); + // Connect the new subface to its right-adjacent subface. + if (casin.sh != NULL) { + senext(newsh, casout); + sbond1(casout, casin); + sbond1(casin, casout); + } + // The left-adjacent subface has not been created yet. + senext2(newsh, casin); + // Add the new face into list (inside the B-W cavity). + smarktest(newsh); + caveshlist->newindex((void **) &parysh); + *parysh = newsh; + // Move to the convex hull edge at the left of 'searchsh'. + neighsh = *searchsh; + while (1) { + senextself(neighsh); + spivot(neighsh, casout); + if (casout.sh == NULL) { + *searchsh = neighsh; + break; + } + if (sorg(casout) != sdest(neighsh)) sesymself(casout); + neighsh = casout; + } + // A convex hull edge. Is it visible by p. + pa = sorg(*searchsh); + pb = sdest(*searchsh); + ori = orient3d(pa, pb, dummypoint, insertpt); + // Finish the process if p is not visible by the hull edge. + if (ori >= 0) break; + } + } else if (loc == INSTAR) { + // Under this case, the sub-cavity sC(p) has already been formed in + // insertvertex(). + } + + // Form the Bowyer-Watson cavity sC(p). + for (i = 0; i < caveshlist->objects; i++) { + cavesh = * (face *) fastlookup(caveshlist, i); + for (j = 0; j < 3; j++) { + if (!isshsubseg(cavesh)) { + spivot(cavesh, neighsh); + if (neighsh.sh != NULL) { + // The adjacent face exists. + if (!smarktested(neighsh)) { + if (bowywat) { + if (loc == INSTAR) { // if (bowywat > 2) { + // It must be a boundary edge. + sign = 1; + } else { + // Check if this subface is connected to adjacent tet(s). + if (!isshtet(neighsh)) { + // Check if the subface is non-Delaunay wrt. the new pt. + sign = incircle3d(sorg(neighsh), sdest(neighsh), + sapex(neighsh), insertpt); + } else { + // It is connected to an adjacent tet. A boundary edge. + sign = 1; + } + } + if (sign < 0) { + // Add the adjacent face in list (in B-W cavity). + smarktest(neighsh); + caveshlist->newindex((void **) &parysh); + *parysh = neighsh; + } + } else { + sign = 1; // A boundary edge. + } + } else { + sign = -1; // Not a boundary edge. + } + } else { + // No adjacent face. It is a hull edge. + if (loc == OUTSIDE) { + // It is a boundary edge if it does not contain p. + if ((sorg(cavesh) == insertpt) || (sdest(cavesh) == insertpt)) { + sign = -1; // Not a boundary edge. + } else { + sign = 1; // A boundary edge. + } + } else { + sign = 1; // A boundary edge. + } + } + } else { + // Do not across a segment. It is a boundary edge. + sign = 1; + } + if (sign >= 0) { + // Add a boundary edge. + caveshbdlist->newindex((void **) &parysh); + *parysh = cavesh; + } + senextself(cavesh); + } // j + } // i + + + // Creating new subfaces. + for (i = 0; i < caveshbdlist->objects; i++) { + parysh = (face *) fastlookup(caveshbdlist, i); + sspivot(*parysh, checkseg); + if ((parysh->shver & 01) != 0) sesymself(*parysh); + pa = sorg(*parysh); + pb = sdest(*parysh); + // Create a new subface. + makeshellface(subfaces, &newsh); + setshvertices(newsh, pa, pb, insertpt); + setshellmark(newsh, shellmark(*parysh)); + if (checkconstraints) { + //area = areabound(*parysh); + setareabound(newsh, areabound(*parysh)); + } + if (useinsertradius) { + setfacetindex(newsh, getfacetindex(*parysh)); + } + // Update the point-to-subface map. + if (pointtype(pa) == FREEFACETVERTEX) { + setpoint2sh(pa, sencode(newsh)); + } + if (pointtype(pb) == FREEFACETVERTEX) { + setpoint2sh(pb, sencode(newsh)); + } + // Connect newsh to outer subfaces. + spivot(*parysh, casout); + if (casout.sh != NULL) { + casin = casout; + if (checkseg.sh != NULL) { + // Make sure that newsh has the right ori at this segment. + checkseg.shver = 0; + if (sorg(newsh) != sorg(checkseg)) { + sesymself(newsh); + sesymself(*parysh); // This side should also be inverse. + } + spivot(casin, neighsh); + while (neighsh.sh != parysh->sh) { + casin = neighsh; + spivot(casin, neighsh); + } + } + sbond1(newsh, casout); + sbond1(casin, newsh); + } + if (checkseg.sh != NULL) { + ssbond(newsh, checkseg); + } + // Connect oldsh <== newsh (for connecting adjacent new subfaces). + // *parysh and newsh point to the same edge and the same ori. + sbond1(*parysh, newsh); + } + + if (newsh.sh != NULL) { + // Set a handle for searching. + recentsh = newsh; + } + + // Update the point-to-subface map. + if (pointtype(insertpt) == FREEFACETVERTEX) { + setpoint2sh(insertpt, sencode(newsh)); + } + + // Connect adjacent new subfaces together. + for (i = 0; i < caveshbdlist->objects; i++) { + // Get an old subface at edge [a, b]. + parysh = (face *) fastlookup(caveshbdlist, i); + spivot(*parysh, newsh); // The new subface [a, b, p]. + senextself(newsh); // At edge [b, p]. + spivot(newsh, neighsh); + if (neighsh.sh == NULL) { + // Find the adjacent new subface at edge [b, p]. + pb = sdest(*parysh); + neighsh = *parysh; + while (1) { + senextself(neighsh); + spivotself(neighsh); + if (neighsh.sh == NULL) break; + if (!smarktested(neighsh)) break; + if (sdest(neighsh) != pb) sesymself(neighsh); + } + if (neighsh.sh != NULL) { + // Now 'neighsh' is a new subface at edge [b, #]. + if (sorg(neighsh) != pb) sesymself(neighsh); + senext2self(neighsh); // Go to the open edge [p, b]. + sbond(newsh, neighsh); + } + } + spivot(*parysh, newsh); // The new subface [a, b, p]. + senext2self(newsh); // At edge [p, a]. + spivot(newsh, neighsh); + if (neighsh.sh == NULL) { + // Find the adjacent new subface at edge [p, a]. + pa = sorg(*parysh); + neighsh = *parysh; + while (1) { + senext2self(neighsh); + spivotself(neighsh); + if (neighsh.sh == NULL) break; + if (!smarktested(neighsh)) break; + if (sorg(neighsh) != pa) sesymself(neighsh); + } + if (neighsh.sh != NULL) { + // Now 'neighsh' is a new subface at edge [#, a]. + if (sdest(neighsh) != pa) sesymself(neighsh); + senextself(neighsh); // Go to the open edge [a, p]. + sbond(newsh, neighsh); + } + } + } + + if ((loc == ONEDGE) || ((splitseg != NULL) && (splitseg->sh != NULL)) + || (cavesegshlist->objects > 0l)) { + // An edge is being split. We distinguish two cases: + // (1) the edge is not on the boundary of the cavity; + // (2) the edge is on the boundary of the cavity. + // In case (2), the edge is either a segment or a hull edge. There are + // degenerated new faces in the cavity. They must be removed. + face aseg, bseg, aoutseg, boutseg; + + for (i = 0; i < cavesegshlist->objects; i++) { + // Get the saved old subface. + parysh = (face *) fastlookup(cavesegshlist, i); + // Get a possible new degenerated subface. + spivot(*parysh, cavesh); + if (sapex(cavesh) == insertpt) { + // Found a degenerated new subface, i.e., case (2). + if (cavesegshlist->objects > 1) { + // There are more than one subface share at this edge. + j = (i + 1) % (int) cavesegshlist->objects; + parysh = (face *) fastlookup(cavesegshlist, j); + spivot(*parysh, neighsh); + // Adjust cavesh and neighsh both at edge a->b, and has p as apex. + if (sorg(neighsh) != sorg(cavesh)) { + sesymself(neighsh); + } + // Connect adjacent faces at two other edges of cavesh and neighsh. + // As a result, the two degenerated new faces are squeezed from the + // new triangulation of the cavity. Note that the squeezed faces + // still hold the adjacent informations which will be used in + // re-connecting subsegments (if they exist). + for (j = 0; j < 2; j++) { + senextself(cavesh); + senextself(neighsh); + spivot(cavesh, newsh); + spivot(neighsh, casout); + sbond1(newsh, casout); // newsh <- casout. + } + } else { + // There is only one subface containing this edge [a,b]. Squeeze the + // degenerated new face [a,b,c] by disconnecting it from its two + // adjacent subfaces at edges [b,c] and [c,a]. Note that the face + // [a,b,c] still hold the connection to them. + for (j = 0; j < 2; j++) { + senextself(cavesh); + spivot(cavesh, newsh); + sdissolve(newsh); + } + } + //recentsh = newsh; + // Update the point-to-subface map. + if (pointtype(insertpt) == FREEFACETVERTEX) { + setpoint2sh(insertpt, sencode(newsh)); + } + } + } + + if ((splitseg != NULL) && (splitseg->sh != NULL)) { + if (loc != INSTAR) { // if (bowywat < 3) { + smarktest(*splitseg); // Mark it as being processed. + } + + aseg = *splitseg; + pa = sorg(*splitseg); + pb = sdest(*splitseg); + + // Insert the new point p. + makeshellface(subsegs, &aseg); + makeshellface(subsegs, &bseg); + + setshvertices(aseg, pa, insertpt, NULL); + setshvertices(bseg, insertpt, pb, NULL); + setshellmark(aseg, shellmark(*splitseg)); + setshellmark(bseg, shellmark(*splitseg)); + if (checkconstraints) { + setareabound(aseg, areabound(*splitseg)); + setareabound(bseg, areabound(*splitseg)); + } + if (useinsertradius) { + setfacetindex(aseg, getfacetindex(*splitseg)); + setfacetindex(bseg, getfacetindex(*splitseg)); + } + + // Connect [#, a]<->[a, p]. + senext2(*splitseg, boutseg); // Temporarily use boutseg. + spivotself(boutseg); + if (boutseg.sh != NULL) { + senext2(aseg, aoutseg); + sbond(boutseg, aoutseg); + } + // Connect [p, b]<->[b, #]. + senext(*splitseg, aoutseg); + spivotself(aoutseg); + if (aoutseg.sh != NULL) { + senext(bseg, boutseg); + sbond(boutseg, aoutseg); + } + // Connect [a, p] <-> [p, b]. + senext(aseg, aoutseg); + senext2(bseg, boutseg); + sbond(aoutseg, boutseg); + + // Connect subsegs [a, p] and [p, b] to adjacent new subfaces. + // Although the degenerated new faces have been squeezed. They still + // hold the connections to the actual new faces. + for (i = 0; i < cavesegshlist->objects; i++) { + parysh = (face *) fastlookup(cavesegshlist, i); + spivot(*parysh, neighsh); + // neighsh is a degenerated new face. + if (sorg(neighsh) != pa) { + sesymself(neighsh); + } + senext2(neighsh, newsh); + spivotself(newsh); // The edge [p, a] in newsh + ssbond(newsh, aseg); + senext(neighsh, newsh); + spivotself(newsh); // The edge [b, p] in newsh + ssbond(newsh, bseg); + } + + + // Let the point remember the segment it lies on. + if (pointtype(insertpt) == FREESEGVERTEX) { + setpoint2sh(insertpt, sencode(aseg)); + } + // Update the point-to-seg map. + if (pointtype(pa) == FREESEGVERTEX) { + setpoint2sh(pa, sencode(aseg)); + } + if (pointtype(pb) == FREESEGVERTEX) { + setpoint2sh(pb, sencode(bseg)); + } + } // if ((splitseg != NULL) && (splitseg->sh != NULL)) + + // Delete all degenerated new faces. + for (i = 0; i < cavesegshlist->objects; i++) { + parysh = (face *) fastlookup(cavesegshlist, i); + spivotself(*parysh); + if (sapex(*parysh) == insertpt) { + shellfacedealloc(subfaces, parysh->sh); + } + } + cavesegshlist->restart(); + + if ((splitseg != NULL) && (splitseg->sh != NULL)) { + // Return the two new subsegments (for further process). + // Re-use 'cavesegshlist'. + cavesegshlist->newindex((void **) &parysh); + *parysh = aseg; + cavesegshlist->newindex((void **) &parysh); + *parysh = bseg; + } + } // if (loc == ONEDGE) + + + return (int) loc; +} + +//============================================================================// +// // +// sremovevertex() Remove a vertex from the surface mesh. // +// // +// 'delpt' (p) is the vertex to be removed. If 'parentseg' is not NULL, p is // +// a segment vertex, and the origin of 'parentseg' is p. Otherwise, p is a // +// facet vertex, and the origin of 'parentsh' is p. // +// // +// Within each facet, we first use a sequence of 2-to-2 flips to flip any // +// edge at p, finally use a 3-to-1 flip to remove p. // +// // +// All new created subfaces are returned in the global array 'caveshbdlist'. // +// The new segment (when p is on segment) is returned in 'parentseg'. // +// // +// If 'lawson' > 0, the Lawson flip algorithm is used to recover Delaunay- // +// ness after p is removed. // +// // +//============================================================================// + +int tetgenmesh::sremovevertex(point delpt, face* parentsh, face* parentseg, + int lawson) +{ + face flipfaces[4], spinsh, *parysh; + point pa, pb, pc, pd; + REAL ori1, ori2; + int it, i, j; + + if (parentseg != NULL) { + // 'delpt' (p) should be a Steiner point inserted in a segment [a,b], + // where 'parentseg' should be [p,b]. Find the segment [a,p]. + face startsh, neighsh, nextsh; + face abseg, prevseg, checkseg; + face adjseg1, adjseg2; + face fakesh; + senext2(*parentseg, prevseg); + spivotself(prevseg); + prevseg.shver = 0; + // Restore the original segment [a,b]. + pa = sorg(prevseg); + pb = sdest(*parentseg); + if (b->verbose > 2) { + printf(" Remove vertex %d from segment [%d, %d].\n", + pointmark(delpt), pointmark(pa), pointmark(pb)); + } + makeshellface(subsegs, &abseg); + setshvertices(abseg, pa, pb, NULL); + setshellmark(abseg, shellmark(*parentseg)); + if (checkconstraints) { + setareabound(abseg, areabound(*parentseg)); + } + if (useinsertradius) { + setfacetindex(abseg, getfacetindex(*parentseg)); + } + // Connect [#, a]<->[a, b]. + senext2(prevseg, adjseg1); + spivotself(adjseg1); + if (adjseg1.sh != NULL) { + adjseg1.shver = 0; + senextself(adjseg1); + senext2(abseg, adjseg2); + sbond(adjseg1, adjseg2); + } + // Connect [a, b]<->[b, #]. + senext(*parentseg, adjseg1); + spivotself(adjseg1); + if (adjseg1.sh != NULL) { + adjseg1.shver = 0; + senext2self(adjseg1); + senext(abseg, adjseg2); + sbond(adjseg1, adjseg2); + } + // Update the point-to-segment map. + setpoint2sh(pa, sencode(abseg)); + setpoint2sh(pb, sencode(abseg)); + + // Get the faces in face ring at segment [p, b]. + // Re-use array 'caveshlist'. + spivot(*parentseg, *parentsh); + if (parentsh->sh != NULL) { + spinsh = *parentsh; + while (1) { + // Save this face in list. + caveshlist->newindex((void **) &parysh); + *parysh = spinsh; + // Go to the next face in the ring. + spivotself(spinsh); + if (spinsh.sh == NULL) { + break; // It is possible there is only one facet. + } + if (spinsh.sh == parentsh->sh) break; + } + } + + // Create the face ring of the new segment [a,b]. Each face in the ring + // is [a,b,p] (degenerated!). It will be removed (automatically). + for (i = 0; i < caveshlist->objects; i++) { + parysh = (face *) fastlookup(caveshlist, i); + startsh = *parysh; + if (sorg(startsh) != delpt) { + sesymself(startsh); + } + // startsh is [p, b, #1], find the subface [a, p, #2]. + neighsh = startsh; + while (1) { + senext2self(neighsh); + sspivot(neighsh, checkseg); + if (checkseg.sh != NULL) { + // It must be the segment [a, p]. + break; + } + spivotself(neighsh); + if (sorg(neighsh) != delpt) sesymself(neighsh); + } + // Now neighsh is [a, p, #2]. + if (neighsh.sh != startsh.sh) { + // Detach the two subsegments [a,p] and [p,b] from subfaces. + ssdissolve(startsh); + ssdissolve(neighsh); + // Create a degenerated subface [a,b,p]. It is used to: (1) hold the + // new segment [a,b]; (2) connect to the two adjacent subfaces + // [p,b,#] and [a,p,#]. + makeshellface(subfaces, &fakesh); + setshvertices(fakesh, pa, pb, delpt); + setshellmark(fakesh, shellmark(startsh)); + // Connect fakesh to the segment [a,b]. + ssbond(fakesh, abseg); + // Connect fakesh to adjacent subfaces: [p,b,#1] and [a,p,#2]. + senext(fakesh, nextsh); + sbond(nextsh, startsh); + senext2(fakesh, nextsh); + sbond(nextsh, neighsh); + smarktest(fakesh); // Mark it as faked. + } else { + // Special case. There exists already a degenerated face [a,b,p]! + // There is no need to create a faked subface here. + senext2self(neighsh); // [a,b,p] + // Since we will re-connect the face ring using the faked subfaces. + // We put the adjacent face of [a,b,p] to the list. + spivot(neighsh, startsh); // The original adjacent subface. + if (sorg(startsh) != pa) sesymself(startsh); + sdissolve(startsh); + // Connect fakesh to the segment [a,b]. + ssbond(startsh, abseg); + fakesh = startsh; // Do not mark it! + // Delete the degenerated subface. + shellfacedealloc(subfaces, neighsh.sh); + } + // Save the fakesh in list (for re-creating the face ring). + cavesegshlist->newindex((void **) &parysh); + *parysh = fakesh; + } // i + caveshlist->restart(); + + // Re-create the face ring. + if (cavesegshlist->objects > 1) { + for (i = 0; i < cavesegshlist->objects; i++) { + parysh = (face *) fastlookup(cavesegshlist, i); + fakesh = *parysh; + // Get the next face in the ring. + j = (i + 1) % cavesegshlist->objects; + parysh = (face *) fastlookup(cavesegshlist, j); + nextsh = *parysh; + sbond1(fakesh, nextsh); + } + } + + // Delete the two subsegments containing p. + shellfacedealloc(subsegs, parentseg->sh); + shellfacedealloc(subsegs, prevseg.sh); + // Return the new segment. + *parentseg = abseg; + } else { + // p is inside the surface. + if (b->verbose > 2) { + printf(" Remove vertex %d from surface.\n", pointmark(delpt)); + } + // Let 'delpt' be its apex. + senextself(*parentsh); + // For unifying the code, we add parentsh to list. + cavesegshlist->newindex((void **) &parysh); + *parysh = *parentsh; + } + + // Remove the point (p). + + for (it = 0; it < cavesegshlist->objects; it++) { + parentsh = (face *) fastlookup(cavesegshlist, it); // [a,b,p] + senextself(*parentsh); // [b,p,a]. + spivotself(*parentsh); + if (sorg(*parentsh) != delpt) sesymself(*parentsh); + // now parentsh is [p,b,#]. + if (sorg(*parentsh) != delpt) { + // The vertex has already been removed in above special case. + continue; + } + + while (1) { + // Initialize the flip edge list. Re-use 'caveshlist'. + spinsh = *parentsh; // [p, b, #] + while (1) { + caveshlist->newindex((void **) &parysh); + *parysh = spinsh; + senext2self(spinsh); + spivotself(spinsh); + if (spinsh.sh == parentsh->sh) break; + if (sorg(spinsh) != delpt) sesymself(spinsh); + } // while (1) + + if (caveshlist->objects == 3) { + // Delete the point by a 3-to-1 flip. + for (i = 0; i < 3; i++) { + parysh = (face *) fastlookup(caveshlist, i); + flipfaces[i] = *parysh; + } + flip31(flipfaces, lawson); + for (i = 0; i < 3; i++) { + shellfacedealloc(subfaces, flipfaces[i].sh); + } + caveshlist->restart(); + // Save the new subface. + caveshbdlist->newindex((void **) &parysh); + *parysh = flipfaces[3]; + // The vertex is removed. + break; + } + + // Search an edge to flip. + for (i = 0; i < caveshlist->objects; i++) { + parysh = (face *) fastlookup(caveshlist, i); + flipfaces[0] = *parysh; + spivot(flipfaces[0], flipfaces[1]); + if (sorg(flipfaces[0]) != sdest(flipfaces[1])) + sesymself(flipfaces[1]); + // Skip this edge if it belongs to a faked subface. + if (!smarktested(flipfaces[0]) && !smarktested(flipfaces[1])) { + pa = sorg(flipfaces[0]); + pb = sdest(flipfaces[0]); + pc = sapex(flipfaces[0]); + pd = sapex(flipfaces[1]); + calculateabovepoint4(pa, pb, pc, pd); + // Check if a 2-to-2 flip is possible. + ori1 = orient3d(pc, pd, dummypoint, pa); + ori2 = orient3d(pc, pd, dummypoint, pb); + if (ori1 * ori2 < 0) { + // A 2-to-2 flip is found. + flip22(flipfaces, lawson, 0); + // The i-th edge is flipped. The i-th and (i-1)-th subfaces are + // changed. The 'flipfaces[1]' contains p as its apex. + senext2(flipfaces[1], *parentsh); + // Save the new subface. + caveshbdlist->newindex((void **) &parysh); + *parysh = flipfaces[0]; + break; + } + } // + } // i + + if (i == caveshlist->objects) { + // Do a flip22 and a flip31 to remove p. + parysh = (face *) fastlookup(caveshlist, 0); + flipfaces[0] = *parysh; + spivot(flipfaces[0], flipfaces[1]); + if (sorg(flipfaces[0]) != sdest(flipfaces[1])) { + sesymself(flipfaces[1]); + } + flip22(flipfaces, lawson, 0); + senext2(flipfaces[1], *parentsh); + // Save the new subface. + caveshbdlist->newindex((void **) &parysh); + *parysh = flipfaces[0]; + } + + // The edge list at p are changed. + caveshlist->restart(); + } // while (1) + + } // it + + cavesegshlist->restart(); + + if (b->verbose > 2) { + printf(" Created %ld new subfaces.\n", caveshbdlist->objects); + } + + + if (lawson) { + lawsonflip(); + } + + return 0; +} + +//============================================================================// +// // +// slocate() Locate a point in a surface triangulation. // +// // +// Staring the search from 'searchsh'(it should not be NULL). Perform a line // +// walk search for a subface containing the point (p). // +// // +// If 'aflag' is set, the 'dummypoint' is pre-calculated so that it lies // +// above the 'searchsh' in its current orientation. The test if c is CCW to // +// the line a->b can be done by the test if c is below the oriented plane // +// a->b->dummypoint. // +// // +// If 'cflag' is not TRUE, the triangulation may not be convex. Stop search // +// when a segment is met and return OUTSIDE. // +// // +// If 'rflag' (rounding) is set, after the location of the point is found, // +// either ONEDGE or ONFACE, round the result using an epsilon. // +// // +// The returned value indicates the following cases: // +// - ONVERTEX, p is the origin of 'searchsh'. // +// - ONEDGE, p lies on the edge of 'searchsh'. // +// - ONFACE, p lies in the interior of 'searchsh'. // +// - OUTSIDE, p lies outside of the triangulation, p is on the left-hand // +// side of the edge 'searchsh'(s), i.e., org(s), dest(s), p are CW. // +// // +//============================================================================// + +enum tetgenmesh::locateresult tetgenmesh::slocate(point searchpt, + face* searchsh, int aflag, int cflag, int rflag) +{ + face neighsh; + point pa, pb, pc; + enum locateresult loc; + enum {MOVE_BC, MOVE_CA} nextmove; + REAL ori, ori_bc, ori_ca; + int i; + + pa = sorg(*searchsh); + pb = sdest(*searchsh); + pc = sapex(*searchsh); + + if (!aflag) { + // No above point is given. Calculate an above point for this facet. + calculateabovepoint4(pa, pb, pc, searchpt); + } + + // 'dummypoint' is given. Make sure it is above [a,b,c] + ori = orient3d(pa, pb, pc, dummypoint); + if (ori > 0) { + sesymself(*searchsh); // Reverse the face orientation. + } else if (ori == 0.0) { + // This case should not happen theoretically. But... + return UNKNOWN; + } + + // Find an edge of the face s.t. p lies on its right-hand side (CCW). + for (i = 0; i < 3; i++) { + pa = sorg(*searchsh); + pb = sdest(*searchsh); + ori = orient3d(pa, pb, dummypoint, searchpt); + if (ori > 0) break; + senextself(*searchsh); + } + if (i == 3) { + return UNKNOWN; + } + + pc = sapex(*searchsh); + + if (pc == searchpt) { + senext2self(*searchsh); + return ONVERTEX; + } + + while (1) { + + ori_bc = orient3d(pb, pc, dummypoint, searchpt); + ori_ca = orient3d(pc, pa, dummypoint, searchpt); + + if (ori_bc < 0) { + if (ori_ca < 0) { // (--) + // Any of the edges is a viable move. + if (randomnation(2)) { + nextmove = MOVE_CA; + } else { + nextmove = MOVE_BC; + } + } else { // (-#) + // Edge [b, c] is viable. + nextmove = MOVE_BC; + } + } else { + if (ori_ca < 0) { // (#-) + // Edge [c, a] is viable. + nextmove = MOVE_CA; + } else { + if (ori_bc > 0) { + if (ori_ca > 0) { // (++) + loc = ONFACE; // Inside [a, b, c]. + break; + } else { // (+0) + senext2self(*searchsh); // On edge [c, a]. + loc = ONEDGE; + break; + } + } else { // ori_bc == 0 + if (ori_ca > 0) { // (0+) + senextself(*searchsh); // On edge [b, c]. + loc = ONEDGE; + break; + } else { // (00) + // p is coincident with vertex c. + senext2self(*searchsh); + return ONVERTEX; + } + } + } + } + + // Move to the next face. + if (nextmove == MOVE_BC) { + senextself(*searchsh); + } else { + senext2self(*searchsh); + } + if (!cflag) { + // NON-convex case. Check if we will cross a boundary. + if (isshsubseg(*searchsh)) { + return ENCSEGMENT; + } + } + spivot(*searchsh, neighsh); + if (neighsh.sh == NULL) { + return OUTSIDE; // A hull edge. + } + // Adjust the edge orientation. + if (sorg(neighsh) != sdest(*searchsh)) { + sesymself(neighsh); + } + + // Update the newly discovered face and its endpoints. + *searchsh = neighsh; + pa = sorg(*searchsh); + pb = sdest(*searchsh); + pc = sapex(*searchsh); + + if (pc == searchpt) { + senext2self(*searchsh); + return ONVERTEX; + } + + } // while (1) + + // assert(loc == ONFACE || loc == ONEDGE); + + + if (rflag) { + // Round the locate result before return. + REAL n[3], area_abc, area_abp, area_bcp, area_cap; + + pa = sorg(*searchsh); + pb = sdest(*searchsh); + pc = sapex(*searchsh); + + facenormal(pa, pb, pc, n, 1, NULL); + area_abc = sqrt(dot(n, n)); + + facenormal(pb, pc, searchpt, n, 1, NULL); + area_bcp = sqrt(dot(n, n)); + if ((area_bcp / area_abc) < b->epsilon) { + area_bcp = 0; // Rounding. + } + + facenormal(pc, pa, searchpt, n, 1, NULL); + area_cap = sqrt(dot(n, n)); + if ((area_cap / area_abc) < b->epsilon) { + area_cap = 0; // Rounding + } + + if ((loc == ONFACE) || (loc == OUTSIDE)) { + facenormal(pa, pb, searchpt, n, 1, NULL); + area_abp = sqrt(dot(n, n)); + if ((area_abp / area_abc) < b->epsilon) { + area_abp = 0; // Rounding + } + } else { // loc == ONEDGE + area_abp = 0; + } + + if (area_abp == 0) { + if (area_bcp == 0) { + senextself(*searchsh); + loc = ONVERTEX; // p is close to b. + } else { + if (area_cap == 0) { + loc = ONVERTEX; // p is close to a. + } else { + loc = ONEDGE; // p is on edge [a,b]. + } + } + } else if (area_bcp == 0) { + if (area_cap == 0) { + senext2self(*searchsh); + loc = ONVERTEX; // p is close to c. + } else { + senextself(*searchsh); + loc = ONEDGE; // p is on edge [b,c]. + } + } else if (area_cap == 0) { + senext2self(*searchsh); + loc = ONEDGE; // p is on edge [c,a]. + } else { + loc = ONFACE; // p is on face [a,b,c]. + } + } // if (rflag) + + return loc; +} + +//============================================================================// +// // +// sscoutsegment() Look for a segment in the surface triangulation. // +// // +// The segment is given by the origin of 'searchsh' and 'endpt'. // +// // +// If an edge in T is found matching this segment, the segment is "locked" // +// in T at the edge. Otherwise, flip the first edge in T that the segment // +// crosses. Continue the search from the flipped face. // +// // +// This routine uses 'orisent3d' to determine the search direction. It uses // +// 'dummypoint' as the 'lifted point' in 3d, and it assumes that it (dummy- // +// point) lies above the 'searchsh' (w.r.t the Right-hand rule). // +// // +//============================================================================// + +enum tetgenmesh::interresult tetgenmesh::sscoutsegment(face *searchsh, + point endpt, int insertsegflag, int reporterrorflag, int chkencflag) +{ + face flipshs[2], neighsh; + point startpt, pa, pb, pc, pd; + enum interresult dir; + enum {MOVE_AB, MOVE_CA} nextmove; + REAL ori_ab, ori_ca, len; + + pc = NULL; // Avoid warnings from MSVC + // The origin of 'searchsh' is fixed. + startpt = sorg(*searchsh); + nextmove = MOVE_AB; // Avoid compiler warning. + + if (b->verbose > 2) { + printf(" Scout segment (%d, %d).\n", pointmark(startpt), + pointmark(endpt)); + } + len = distance(startpt, endpt); + + // Search an edge in 'searchsh' on the path of this segment. + while (1) { + + pb = sdest(*searchsh); + if (pb == endpt) { + dir = SHAREEDGE; // Found! + break; + } + + pc = sapex(*searchsh); + if (pc == endpt) { + senext2self(*searchsh); + sesymself(*searchsh); + dir = SHAREEDGE; // Found! + break; + } + + + // Round the results. + if ((sqrt(triarea(startpt, pb, endpt)) / len) < b->epsilon) { + ori_ab = 0.0; + } else { + ori_ab = orient3d(startpt, pb, dummypoint, endpt); + } + if ((sqrt(triarea(pc, startpt, endpt)) / len) < b->epsilon) { + ori_ca = 0.0; + } else { + ori_ca = orient3d(pc, startpt, dummypoint, endpt); + } + + if (ori_ab < 0) { + if (ori_ca < 0) { // (--) + // Both sides are viable moves. + if (randomnation(2)) { + nextmove = MOVE_CA; + } else { + nextmove = MOVE_AB; + } + } else { // (-#) + nextmove = MOVE_AB; + } + } else { + if (ori_ca < 0) { // (#-) + nextmove = MOVE_CA; + } else { + if (ori_ab > 0) { + if (ori_ca > 0) { // (++) + // The segment intersects with edge [b, c]. + dir = ACROSSEDGE; + break; + } else { // (+0) + // The segment collinear with edge [c, a]. + senext2self(*searchsh); + sesymself(*searchsh); + dir = ACROSSVERT; + break; + } + } else { + if (ori_ca > 0) { // (0+) + // The segment is collinear with edge [a, b]. + dir = ACROSSVERT; + break; + } else { // (00) + // startpt == endpt. Not possible. + terminatetetgen(this, 2); + } + } + } + } + + // Move 'searchsh' to the next face, keep the origin unchanged. + if (nextmove == MOVE_AB) { + if (chkencflag) { + // Do not cross boundary. + if (isshsubseg(*searchsh)) { + return ACROSSEDGE; // ACROSS_SEG + } + } + spivot(*searchsh, neighsh); + if (neighsh.sh != NULL) { + if (sorg(neighsh) != pb) sesymself(neighsh); + senext(neighsh, *searchsh); + } else { + // This side (startpt->pb) is outside. It is caused by rounding error. + // Try the next side, i.e., (pc->startpt). + senext2(*searchsh, neighsh); + if (chkencflag) { + // Do not cross boundary. + if (isshsubseg(neighsh)) { + *searchsh = neighsh; + return ACROSSEDGE; // ACROSS_SEG + } + } + spivotself(neighsh); + if (sdest(neighsh) != pc) sesymself(neighsh); + *searchsh = neighsh; + } + } else { // MOVE_CA + senext2(*searchsh, neighsh); + if (chkencflag) { + // Do not cross boundary. + if (isshsubseg(neighsh)) { + *searchsh = neighsh; + return ACROSSEDGE; // ACROSS_SEG + } + } + spivotself(neighsh); + if (neighsh.sh != NULL) { + if (sdest(neighsh) != pc) sesymself(neighsh); + *searchsh = neighsh; + } else { + // The same reason as above. + // Try the next side, i.e., (startpt->pb). + if (chkencflag) { + // Do not cross boundary. + if (isshsubseg(*searchsh)) { + return ACROSSEDGE; // ACROSS_SEG + } + } + spivot(*searchsh, neighsh); + if (sorg(neighsh) != pb) sesymself(neighsh); + senext(neighsh, *searchsh); + } + } + } // while + + if (dir == SHAREEDGE) { + if (insertsegflag) { + // Insert the segment into the triangulation. + face newseg; + makeshellface(subsegs, &newseg); + setshvertices(newseg, startpt, endpt, NULL); + // Set the default segment marker. + setshellmark(newseg, -1); + ssbond(*searchsh, newseg); + spivot(*searchsh, neighsh); + if (neighsh.sh != NULL) { + ssbond(neighsh, newseg); + } + } + return dir; + } + + if (dir == ACROSSVERT) { + // A point is found collinear with this segment. + if (reporterrorflag) { + point pp = sdest(*searchsh); + printf("PLC Error: A vertex lies in a segment in facet #%d.\n", + shellmark(*searchsh)); + printf(" Vertex: [%d] (%g,%g,%g).\n",pointmark(pp),pp[0],pp[1],pp[2]); + printf(" Segment: [%d, %d]\n", pointmark(startpt), pointmark(endpt)); + } + return dir; + } + + if (dir == ACROSSEDGE) { + // Edge [b, c] intersects with the segment. + senext(*searchsh, flipshs[0]); + if (isshsubseg(flipshs[0])) { + if (reporterrorflag) { + REAL P[3], Q[3], tp = 0, tq = 0; + linelineint(startpt, endpt, pb, pc, P, Q, &tp, &tq); + printf("PLC Error: Two segments intersect at point (%g,%g,%g),", + P[0], P[1], P[2]); + printf(" in facet #%d.\n", shellmark(*searchsh)); + printf(" Segment 1: [%d, %d]\n", pointmark(pb), pointmark(pc)); + printf(" Segment 2: [%d, %d]\n", pointmark(startpt),pointmark(endpt)); + } + return dir; // ACROSS_SEG + } + // Flip edge [b, c], queue unflipped edges (for Delaunay checks). + spivot(flipshs[0], flipshs[1]); + if (sorg(flipshs[1]) != sdest(flipshs[0])) sesymself(flipshs[1]); + flip22(flipshs, 1, 0); + // The flip may create an inverted triangle, check it. + pa = sapex(flipshs[1]); + pb = sapex(flipshs[0]); + pc = sorg(flipshs[0]); + pd = sdest(flipshs[0]); + // Check if pa and pb are on the different sides of [pc, pd]. + // Re-use ori_ab, ori_ca for the tests. + ori_ab = orient3d(pc, pd, dummypoint, pb); + ori_ca = orient3d(pd, pc, dummypoint, pa); + if (ori_ab <= 0) { + flipshpush(&(flipshs[0])); + } else if (ori_ca <= 0) { + flipshpush(&(flipshs[1])); + } + // Set 'searchsh' s.t. its origin is 'startpt'. + *searchsh = flipshs[0]; + } + + return sscoutsegment(searchsh, endpt, insertsegflag, reporterrorflag, + chkencflag); +} + +//============================================================================// +// // +// scarveholes() Remove triangles not in the facet. // +// // +// This routine re-uses the two global arrays: caveshlist and caveshbdlist. // +// // +//============================================================================// + +void tetgenmesh::scarveholes(int holes, REAL* holelist) +{ + face *parysh, searchsh, neighsh; + enum locateresult loc; + int i, j; + + // Get all triangles. Infect unprotected convex hull triangles. + smarktest(recentsh); + caveshlist->newindex((void **) &parysh); + *parysh = recentsh; + for (i = 0; i < caveshlist->objects; i++) { + parysh = (face *) fastlookup(caveshlist, i); + searchsh = *parysh; + searchsh.shver = 0; + for (j = 0; j < 3; j++) { + spivot(searchsh, neighsh); + // Is this side on the convex hull? + if (neighsh.sh != NULL) { + if (!smarktested(neighsh)) { + smarktest(neighsh); + caveshlist->newindex((void **) &parysh); + *parysh = neighsh; + } + } else { + // A hull side. Check if it is protected by a segment. + if (!isshsubseg(searchsh)) { + // Not protected. Save this face. + if (!sinfected(searchsh)) { + sinfect(searchsh); + caveshbdlist->newindex((void **) &parysh); + *parysh = searchsh; + } + } + } + senextself(searchsh); + } + } + + // Infect the triangles in the holes. + for (i = 0; i < 3 * holes; i += 3) { + searchsh = recentsh; + loc = slocate(&(holelist[i]), &searchsh, 1, 1, 0); + if (loc != OUTSIDE) { + sinfect(searchsh); + caveshbdlist->newindex((void **) &parysh); + *parysh = searchsh; + } + } + + // Find and infect all exterior triangles. + for (i = 0; i < caveshbdlist->objects; i++) { + parysh = (face *) fastlookup(caveshbdlist, i); + searchsh = *parysh; + searchsh.shver = 0; + for (j = 0; j < 3; j++) { + spivot(searchsh, neighsh); + if (neighsh.sh != NULL) { + if (!isshsubseg(searchsh)) { + if (!sinfected(neighsh)) { + sinfect(neighsh); + caveshbdlist->newindex((void **) &parysh); + *parysh = neighsh; + } + } else { + sdissolve(neighsh); // Disconnect a protected face. + } + } + senextself(searchsh); + } + } + + // Delete exterior triangles, unmark interior triangles. + for (i = 0; i < caveshlist->objects; i++) { + parysh = (face *) fastlookup(caveshlist, i); + if (sinfected(*parysh)) { + shellfacedealloc(subfaces, parysh->sh); + } else { + sunmarktest(*parysh); + } + } + + caveshlist->restart(); + caveshbdlist->restart(); +} + +//============================================================================// +// // +// triangulate() Create a CDT for the facet. // +// // +// All vertices of the triangulation have type FACETVERTEX. The actual type // +// of boundary vertices are set by the routine unifysements(). // +// // +// All segments created here will have a default marker '-1'. Some of these // +// segments will get their actual marker defined in 'edgemarkerlist'. // +// // +//============================================================================// + +int tetgenmesh::triangulate(int shmark, arraypool* ptlist, arraypool* conlist, + int holes, REAL* holelist) +{ + face searchsh, newsh, *parysh; + face newseg, *paryseg; + point pa, pb, pc, *ppt, *cons; + int iloc; + int i, j; + + if (b->verbose > 2) { + printf(" f%d: %ld vertices, %ld segments", shmark, ptlist->objects, + conlist->objects); + if (holes > 0) { + printf(", %d holes", holes); + } + printf(".\n"); + } + + if (ptlist->objects < 2l) { + // Not a segment or a facet. + return 1; + } else if (ptlist->objects == 2l) { + pa = * (point *) fastlookup(ptlist, 0); + pb = * (point *) fastlookup(ptlist, 1); + if (distance(pa, pb) > 0) { + // It is a single segment. + makeshellface(subsegs, &newseg); + setshvertices(newseg, pa, pb, NULL); + setshellmark(newseg, -1); + } + if (pointtype(pa) == VOLVERTEX) { + setpointtype(pa, FACETVERTEX); + } + if (pointtype(pb) == VOLVERTEX) { + setpointtype(pb, FACETVERTEX); + } + return 1; + } else if (ptlist->objects == 3) { + pa = * (point *) fastlookup(ptlist, 0); + pb = * (point *) fastlookup(ptlist, 1); + pc = * (point *) fastlookup(ptlist, 2); + } else { + // Calculate an above point of this facet. + if (!calculateabovepoint(ptlist, &pa, &pb, &pc)) { + if (!b->quiet) { + printf("Warning: Unable to triangulate facet #%d. Skipped!\n",shmark); + } + return 0; // The point set is degenerate. + } + } + + // Create an initial triangulation. + makeshellface(subfaces, &newsh); + setshvertices(newsh, pa, pb, pc); + setshellmark(newsh, shmark); + recentsh = newsh; + + if (pointtype(pa) == VOLVERTEX) { + setpointtype(pa, FACETVERTEX); + } + if (pointtype(pb) == VOLVERTEX) { + setpointtype(pb, FACETVERTEX); + } + if (pointtype(pc) == VOLVERTEX) { + setpointtype(pc, FACETVERTEX); + } + + // Are there area constraints? + if (b->quality && (in->facetconstraintlist != NULL)) { + for (i = 0; i < in->numberoffacetconstraints; i++) { + if (shmark == ((int) in->facetconstraintlist[i * 2])) { + REAL area = in->facetconstraintlist[i * 2 + 1]; + setareabound(newsh, area); + break; + } + } + } + + if (ptlist->objects == 3) { + // The triangulation only has one element. + for (i = 0; i < 3; i++) { + makeshellface(subsegs, &newseg); + setshvertices(newseg, sorg(newsh), sdest(newsh), NULL); + setshellmark(newseg, -1); + ssbond(newsh, newseg); + senextself(newsh); + } + return 1; + } + + // Triangulate the facet. It may not success (due to rounding error, or + // incorrect input data), use 'caveencshlist' and 'caveencseglist' are + // re-used to store all the newly created subfaces and segments. So we + // can clean them if the triangulation is not successful. + caveencshlist->newindex((void **) &parysh); + *parysh = newsh; + + // Incrementally build the triangulation. + pinfect(pa); + pinfect(pb); + pinfect(pc); + for (i = 0; i < ptlist->objects; i++) { + ppt = (point *) fastlookup(ptlist, i); + if (!pinfected(*ppt)) { + searchsh = recentsh; // Start from 'recentsh'. + iloc = (int) OUTSIDE; + // Insert the vertex. Use Bowyer-Watson algo. Round the location. + iloc = sinsertvertex(*ppt, &searchsh, NULL, iloc, 1, 1); + if (iloc != ((int) ONVERTEX)) { + // Point inserted successfully. + if (pointtype(*ppt) == VOLVERTEX) { + setpointtype(*ppt, FACETVERTEX); + } + // Save the set of new subfaces. + for (j = 0; j < caveshbdlist->objects; j++) { + // Get an old subface at edge [a, b]. + parysh = (face *) fastlookup(caveshbdlist, j); + spivot(*parysh, searchsh); // The new subface [a, b, p]. + // Do not save a deleted new face (degenerated). + if (searchsh.sh[3] != NULL) { + caveencshlist->newindex((void **) &parysh); + *parysh = searchsh; + } + } + // Delete all removed subfaces. + for (j = 0; j < caveshlist->objects; j++) { + parysh = (face *) fastlookup(caveshlist, j); + shellfacedealloc(subfaces, parysh->sh); + } + // Clear the global lists. + caveshbdlist->restart(); + caveshlist->restart(); + cavesegshlist->restart(); + } else { + // The facet triangulation is failed. + break; + } + } + } // i + puninfect(pa); + puninfect(pb); + puninfect(pc); + + if (i < ptlist->objects) { + //The facet triangulation is failed. Clean the new subfaces. + // There is no new segment be created yet. + if (!b->quiet) { + printf("Warning: Fail to triangulate facet #%d. Skipped!\n", shmark); + } + for (i = 0; i < caveencshlist->objects; i++) { + parysh = (face *) fastlookup(caveencshlist, i); + if (parysh->sh[3] != NULL) { + shellfacedealloc(subfaces, parysh->sh); + } + } + caveencshlist->restart(); + return 0; + } + + // Insert the segments. + for (i = 0; i < conlist->objects; i++) { + cons = (point *) fastlookup(conlist, i); + searchsh = recentsh; + iloc = (int) slocate(cons[0], &searchsh, 1, 1, 0); + if (iloc != (int) ONVERTEX) { + // Not found due to roundoff errors. Do a brute-force search. + bool bflag = false; + subfaces->traversalinit(); + searchsh.sh = shellfacetraverse(subfaces); + while (searchsh.sh != NULL) { + // Only search the subface in the same facet. + if (shellmark(searchsh) == shmark) { + if ((point) searchsh.sh[3] == cons[0]) { + searchsh.shver = 0; bflag = true; //break; + } else if ((point) searchsh.sh[4] == cons[0]) { + searchsh.shver = 2; bflag = true; //break; + } else if ((point) searchsh.sh[5] == cons[0]) { + searchsh.shver = 4; bflag = true; //break; + } + } + if (bflag) { + // [2019-12-03] The subface is not guaranteed to be coplanar, + // only use "shmark" is not enough. + point pa = sorg(searchsh); + point pb = sdest(searchsh); + point pc = sapex(searchsh); + REAL chkori = orient3d(pa, pb, pc, cons[1]); + if (chkori != 0.0) { + REAL len = distance(pa, pb); + len += distance(pb, pc); + len += distance(pc, pa); + len /= 3.0; + REAL len3 = len * len * len; + REAL eps = fabs(chkori) / len3; + if (eps < 1e-5) { + break; // They are almost coplanar. + } + } else { + break; + } + bflag = false; // not this subface. + } + searchsh.sh = shellfacetraverse(subfaces); + } + //if (searchsh.sh == NULL) { + // // Failed to find a subface containing vertex cons[0]. + //} + } + if (searchsh.sh != NULL) { + // Recover the segment. Some edges may be flipped. + if (sscoutsegment(&searchsh, cons[1], 1, 1, 0) != SHAREEDGE) { + break; // Fail to recover a segment. + } + // Save this newseg. + sspivot(searchsh, newseg); + caveencseglist->newindex((void **) &paryseg); + *paryseg = newseg; + if (flipstack != NULL) { + // Recover locally Delaunay edges. + lawsonflip(); + } + } else { + break; // Failed to find a segment. + } + } // i + + if (i < conlist->objects) { + if (!b->quiet) { + printf("Warning: Fail to recover a segment in facet #%d. Skipped!\n", + shmark); + } + for (i = 0; i < caveencshlist->objects; i++) { + parysh = (face *) fastlookup(caveencshlist, i); + if (parysh->sh[3] != NULL) { + shellfacedealloc(subfaces, parysh->sh); + } + } + for (i = 0; i < caveencseglist->objects; i++) { + paryseg = (face *) fastlookup(caveencseglist, i); + if (paryseg->sh[3] != NULL) { + shellfacedealloc(subsegs, paryseg->sh); + } + } + caveencshlist->restart(); + caveencseglist->restart(); + return 0; + } + + // Remove exterior and hole triangles. + scarveholes(holes, holelist); + + caveencshlist->restart(); + caveencseglist->restart(); + return 1; +} + +//============================================================================// +// // +// unifysegments() Remove redundant segments and create face links. // +// // +// After this routine, although segments are unique, but some of them may be // +// removed later by mergefacet(). All vertices still have type FACETVERTEX. // +// // +//============================================================================// + +void tetgenmesh::unifysegments() +{ + badface *facelink = NULL, *newlinkitem, *f1, *f2; + face *facperverlist, sface; + face subsegloop, testseg; + point torg, tdest; + REAL ori1, ori2; //, ori3; + REAL n1[3], n2[3]; + REAL cosang, ang, ang_tol; + int *idx2faclist; + int idx, k, m; + + if (b->verbose > 1) { + printf(" Unifying segments.\n"); + } + // The limit dihedral angle that two facets are not overlapping. + //ang_tol = b->facet_overlap_ang_tol / 180.0 * PI; + //if (ang_tol < 0.0) ang_tol = 0.0; + + // Create a mapping from vertices to subfaces. + makepoint2submap(subfaces, idx2faclist, facperverlist); + + + subsegloop.shver = 0; + subsegs->traversalinit(); + subsegloop.sh = shellfacetraverse(subsegs); + while (subsegloop.sh != (shellface *) NULL) { + torg = sorg(subsegloop); + tdest = sdest(subsegloop); + + idx = pointmark(torg) - in->firstnumber; + // Loop through the set of subfaces containing 'torg'. Get all the + // subfaces containing the edge (torg, tdest). Save and order them + // in 'sfacelist', the ordering is defined by the right-hand rule + // with thumb points from torg to tdest. + for (k = idx2faclist[idx]; k < idx2faclist[idx + 1]; k++) { + sface = facperverlist[k]; + // The face may be deleted if it is a duplicated face. + if (sface.sh[3] == NULL) continue; + // Search the edge torg->tdest. + if (sdest(sface) != tdest) { + senext2self(sface); + sesymself(sface); + } + if (sdest(sface) != tdest) continue; + + // Save the face f in facelink. + if (flippool->items >= 2) { + f1 = facelink; + for (m = 0; m < flippool->items - 1; m++) { + f2 = f1->nextitem; + ori1 = facedihedral(torg, tdest, sapex(f1->ss), sapex(f2->ss)); + ori2 = facedihedral(torg, tdest, sapex(f1->ss), sapex(sface)); + if (ori1 >= ori2) { + break; // insert this face between f1 and f2. + } + // Go to the next item; + f1 = f2; + } // for (m = 0; ...) + //if (sface.sh[3] != NULL) { + // Insert sface between f1 and f2. + newlinkitem = (badface *) flippool->alloc(); + newlinkitem->ss = sface; + newlinkitem->nextitem = f1->nextitem; + f1->nextitem = newlinkitem; + //} + } else if (flippool->items == 1) { + f1 = facelink; + // Add this face to link if it is not deleted. + //if (sface.sh[3] != NULL) { + // Add this face into link. + newlinkitem = (badface *) flippool->alloc(); + newlinkitem->ss = sface; + newlinkitem->nextitem = NULL; + f1->nextitem = newlinkitem; + //} + } else { + // The first face. + newlinkitem = (badface *) flippool->alloc(); + newlinkitem->ss = sface; + newlinkitem->nextitem = NULL; + facelink = newlinkitem; + } + } // for (k = idx2faclist[idx]; ...) + + + // Set the connection between this segment and faces containing it, + // at the same time, remove redundant segments. + f1 = facelink; + for (k = 0; k < flippool->items; k++) { + sspivot(f1->ss, testseg); + // If 'testseg' is not 'subsegloop' and is not dead, it is redundant. + if ((testseg.sh != subsegloop.sh) && (testseg.sh[3] != NULL)) { + shellfacedealloc(subsegs, testseg.sh); + } + // Bonds the subface and the segment together. + ssbond(f1->ss, subsegloop); + f1 = f1->nextitem; + } + + // Create the face ring at the segment. + if (flippool->items > 1) { + f1 = facelink; + for (k = 1; k <= flippool->items; k++) { + k < flippool->items ? f2 = f1->nextitem : f2 = facelink; + // Calculate the dihedral angle between the two facet. + facenormal(torg, tdest, sapex(f1->ss), n1, 1, NULL); + facenormal(torg, tdest, sapex(f2->ss), n2, 1, NULL); + cosang = dot(n1, n2) / (sqrt(dot(n1, n1)) * sqrt(dot(n2, n2))); + // Rounding. + if (cosang > 1.0) cosang = 1.0; + else if (cosang < -1.0) cosang = -1.0; + ang = acos(cosang); + //if (ang < ang_tol) { + // // Two facets are treated as overlapping each other. + // report_overlapping_facets(&(f1->ss), &(f2->ss), ang); + //} else { + // Record the smallest input dihedral angle. + if (ang < minfacetdihed) { + minfacetdihed = ang; + } + sbond1(f1->ss, f2->ss); + //} + f1 = f2; + } + } + + flippool->restart(); + + // Are there length constraints? + if (b->quality && (in->segmentconstraintlist != (REAL *) NULL)) { + int e1, e2; + REAL len; + for (k = 0; k < in->numberofsegmentconstraints; k++) { + e1 = (int) in->segmentconstraintlist[k * 3]; + e2 = (int) in->segmentconstraintlist[k * 3 + 1]; + if (((pointmark(torg) == e1) && (pointmark(tdest) == e2)) || + ((pointmark(torg) == e2) && (pointmark(tdest) == e1))) { + len = in->segmentconstraintlist[k * 3 + 2]; + setareabound(subsegloop, len); + break; + } + } + } + + subsegloop.sh = shellfacetraverse(subsegs); + } + + delete [] idx2faclist; + delete [] facperverlist; +} + +//============================================================================// +// // +// identifyinputedges() Identify input edges. // +// // +// A set of input edges is provided in the 'in->edgelist'. We find these // +// edges in the surface mesh and make them segments of the mesh. // +// // +// It is possible that an input edge is not in any facet, i.e.,it is a float- // +// segment inside the volume. // +// // +//============================================================================// + +void tetgenmesh::identifyinputedges(point *idx2verlist) +{ + face* shperverlist; + int* idx2shlist; + face searchsh, neighsh; + face segloop, checkseg, newseg; + point checkpt, pa = NULL, pb = NULL; + int *endpts; + int edgemarker; + int idx, i, j; + + int e1, e2; + REAL len; + + if (!b->quiet) { + printf("Inserting edges ...\n"); + } + + // Construct a map from points to subfaces. + makepoint2submap(subfaces, idx2shlist, shperverlist); + + // Process the set of input edges. + for (i = 0; i < in->numberofedges; i++) { + endpts = &(in->edgelist[(i << 1)]); + if (endpts[0] == endpts[1]) { + if (!b->quiet) { + printf("Warning: Edge #%d is degenerated. Skipped.\n", i); + } + continue; // Skip a degenerated edge. + } else if (dupverts > 0l) { + // Replace duplicated vertices. + for (j = 0; j < 2; j++) { + checkpt = idx2verlist[endpts[j]]; + if (pointtype(checkpt) == DUPLICATEDVERTEX) { + point meshpt = point2ppt(checkpt); + endpts[j] = pointmark(meshpt); + } + } + } + // Recall that all existing segments have a default marker '-1'. + // We assign all identified segments a default marker '-2'. + edgemarker = in->edgemarkerlist ? in->edgemarkerlist[i] : -2; + + // Find a face contains the edge. + newseg.sh = NULL; + searchsh.sh = NULL; + idx = endpts[0] - in->firstnumber; + for (j = idx2shlist[idx]; j < idx2shlist[idx + 1]; j++) { + checkpt = sdest(shperverlist[j]); + if (pointmark(checkpt) == endpts[1]) { + searchsh = shperverlist[j]; + break; // Found. + } else { + checkpt = sapex(shperverlist[j]); + if (pointmark(checkpt) == endpts[1]) { + senext2(shperverlist[j], searchsh); + sesymself(searchsh); + break; + } + } + } // j + + if (searchsh.sh != NULL) { + // Check if this edge is already a segment of the mesh. + sspivot(searchsh, checkseg); + if (checkseg.sh != NULL) { + // This segment already exist. + newseg = checkseg; + } else { + // Create a new segment at this edge. + pa = sorg(searchsh); + pb = sdest(searchsh); + makeshellface(subsegs, &newseg); + setshvertices(newseg, pa, pb, NULL); + ssbond(searchsh, newseg); + spivot(searchsh, neighsh); + if (neighsh.sh != NULL) { + ssbond(neighsh, newseg); + } + } + } else { + // It is a dangling segment (not belong to any facets). + // Get the two endpoints of this segment. + pa = idx2verlist[endpts[0]]; + pb = idx2verlist[endpts[1]]; + if (pa == pb) { + if (!b->quiet) { + printf("Warning: Edge #%d is degenerated. Skipped.\n", i); + } + continue; + } + // Check if segment [a,b] already exists. + // TODO: Change the brute-force search. Slow! + point *ppt; + subsegs->traversalinit(); + segloop.sh = shellfacetraverse(subsegs); + while (segloop.sh != NULL) { + ppt = (point *) &(segloop.sh[3]); + if (((ppt[0] == pa) && (ppt[1] == pb)) || + ((ppt[0] == pb) && (ppt[1] == pa))) { + // Found! + newseg = segloop; + break; + } + segloop.sh = shellfacetraverse(subsegs); + } + if (newseg.sh == NULL) { + makeshellface(subsegs, &newseg); + setshvertices(newseg, pa, pb, NULL); + } + } + + setshellmark(newseg, edgemarker); + + if (b->quality && (in->segmentconstraintlist != (REAL *) NULL)) { + for (i = 0; i < in->numberofsegmentconstraints; i++) { + e1 = (int) in->segmentconstraintlist[i * 3]; + e2 = (int) in->segmentconstraintlist[i * 3 + 1]; + if (((pointmark(pa) == e1) && (pointmark(pb) == e2)) || + ((pointmark(pa) == e2) && (pointmark(pb) == e1))) { + len = in->segmentconstraintlist[i * 3 + 2]; + setareabound(newseg, len); + break; + } + } + } + } // i + + delete [] shperverlist; + delete [] idx2shlist; +} + +//============================================================================// +// // +// mergefacets() Merge adjacent facets. // +// // +//============================================================================// + +void tetgenmesh::mergefacets() +{ + face parentsh, neighsh, neineish; + face segloop; + point pa, pb, pc, pd; + REAL n1[3], n2[3]; + REAL cosang, cosang_tol; + + // Allocate an array to save calcaulated dihedral angles at segments. + arraypool *dihedangarray = new arraypool(sizeof(double), 10); + REAL *paryang = NULL; + + // First, remove coplanar segments. + // The dihedral angle bound for two different facets. + cosang_tol = cos(b->facet_separate_ang_tol / 180.0 * PI); + + subsegs->traversalinit(); + segloop.sh = shellfacetraverse(subsegs); + while (segloop.sh != (shellface *) NULL) { + // Only remove a segment if it has a marker '-1'. + if (shellmark(segloop) != -1) { + segloop.sh = shellfacetraverse(subsegs); + continue; + } + spivot(segloop, parentsh); + if (parentsh.sh != NULL) { + spivot(parentsh, neighsh); + if (neighsh.sh != NULL) { + spivot(neighsh, neineish); + if (neineish.sh == parentsh.sh) { + // Exactly two subfaces at this segment. + // Only merge them if they have the same boundary marker. + if (shellmark(parentsh) == shellmark(neighsh)) { + pa = sorg(segloop); + pb = sdest(segloop); + pc = sapex(parentsh); + pd = sapex(neighsh); + // Calculate the dihedral angle at the segment [a,b]. + facenormal(pa, pb, pc, n1, 1, NULL); + facenormal(pa, pb, pd, n2, 1, NULL); + cosang = dot(n1, n2) / (sqrt(dot(n1, n1)) * sqrt(dot(n2, n2))); + if (cosang < cosang_tol) { + ssdissolve(parentsh); + ssdissolve(neighsh); + shellfacedealloc(subsegs, segloop.sh); + // Add the edge to flip stack. + flipshpush(&parentsh); + } else { + // Save 'cosang' to avoid re-calculate it. + // Re-use the pointer at the first segment. + dihedangarray->newindex((void **) &paryang); + *paryang = cosang; + segloop.sh[6] = (shellface) paryang; + } + } + } // if (neineish.sh == parentsh.sh) + } + } + segloop.sh = shellfacetraverse(subsegs); + } + + // Second, remove ridge segments at small angles. + // The dihedral angle bound for two different facets. + cosang_tol = cos(b->facet_small_ang_tol / 180.0 * PI); + REAL cosang_sep_tol = cos((b->facet_separate_ang_tol - 5.0) / 180.0 * PI); + face shloop; + face seg1, seg2; + REAL cosang1, cosang2; + int i, j; + + subfaces->traversalinit(); + shloop.sh = shellfacetraverse(subfaces); + while (shloop.sh != (shellface *) NULL) { + for (i = 0; i < 3; i++) { + if (isshsubseg(shloop)) { + senext(shloop, neighsh); + if (isshsubseg(neighsh)) { + // Found two segments sharing at one vertex. + // Check if they form a small angle. + pa = sorg(shloop); + pb = sdest(shloop); + pc = sapex(shloop); + for (j = 0; j < 3; j++) n1[j] = pa[j] - pb[j]; + for (j = 0; j < 3; j++) n2[j] = pc[j] - pb[j]; + cosang = dot(n1, n2) / (sqrt(dot(n1, n1)) * sqrt(dot(n2, n2))); + if (cosang > cosang_tol) { + // Found a small angle. + segloop.sh = NULL; + sspivot(shloop, seg1); + sspivot(neighsh, seg2); + if (seg1.sh[6] != NULL) { + paryang = (REAL *) (seg1.sh[6]); + cosang1 = *paryang; + } else { + cosang1 = 1.0; // 0 degree; + } + if (seg2.sh[6] != NULL) { + paryang = (REAL *) (seg2.sh[6]); + cosang2 = *paryang; + } else { + cosang2 = 1.0; // 0 degree; + } + if (cosang1 < cosang_sep_tol) { + if (cosang2 < cosang_sep_tol) { + if (cosang1 < cosang2) { + segloop = seg1; + } else { + segloop = seg2; + } + } else { + segloop = seg1; + } + } else { + if (cosang2 < cosang_sep_tol) { + segloop = seg2; + } + } + if (segloop.sh != NULL) { + // Remove this segment. + segloop.shver = 0; + spivot(segloop, parentsh); + spivot(parentsh, neighsh); + ssdissolve(parentsh); + ssdissolve(neighsh); + shellfacedealloc(subsegs, segloop.sh); + // Add the edge to flip stack. + flipshpush(&parentsh); + break; + } + } + } // if (isshsubseg) + } // if (isshsubseg) + senextself(shloop); + } + shloop.sh = shellfacetraverse(subfaces); + } + + delete dihedangarray; + + + if (flipstack != NULL) { + lawsonflip(); // Recover Delaunayness. + } +} + +//============================================================================// +// // +// meshsurface() Create a surface mesh of the input PLC. // +// // +//============================================================================// + +void tetgenmesh::meshsurface() +{ + arraypool *ptlist, *conlist; + point *idx2verlist; + point tstart, tend, *pnewpt, *cons; + tetgenio::facet *f; + tetgenio::polygon *p; + int end1, end2; + int shmark, i, j; + + if (!b->quiet) { + printf("Creating surface mesh ...\n"); + } + + // Create a map from indices to points. + makeindex2pointmap(idx2verlist); + + // Initialize arrays (block size: 2^8 = 256). + ptlist = new arraypool(sizeof(point *), 8); + conlist = new arraypool(2 * sizeof(point *), 8); + + // Loop the facet list, triangulate each facet. + for (shmark = 1; shmark <= in->numberoffacets; shmark++) { + + // Get a facet F. + f = &in->facetlist[shmark - 1]; + + // Process the duplicated points first, they are marked with type + // DUPLICATEDVERTEX. If p and q are duplicated, and p'index > q's, + // then p is substituted by q. + if (dupverts > 0l) { + // Loop all polygons of this facet. + for (i = 0; i < f->numberofpolygons; i++) { + p = &(f->polygonlist[i]); + // Loop other vertices of this polygon. + for (j = 0; j < p->numberofvertices; j++) { + end1 = p->vertexlist[j]; + tstart = idx2verlist[end1]; + if (pointtype(tstart) == DUPLICATEDVERTEX) { + // Reset the index of vertex-j. + tend = point2ppt(tstart); + end2 = pointmark(tend); + p->vertexlist[j] = end2; + } + } + } + } + + // Loop polygons of F, get the set of vertices and segments. + for (i = 0; i < f->numberofpolygons; i++) { + // Get a polygon. + p = &(f->polygonlist[i]); + // Get the first vertex. + end1 = p->vertexlist[0]; + if ((end1 < in->firstnumber) || + (end1 >= in->firstnumber + in->numberofpoints)) { + if (!b->quiet) { + printf("Warning: Invalid the 1st vertex %d of polygon", end1); + printf(" %d in facet %d.\n", i + 1, shmark); + } + continue; // Skip this polygon. + } + tstart = idx2verlist[end1]; + // Add tstart to V if it haven't been added yet. + if (!pinfected(tstart)) { + pinfect(tstart); + ptlist->newindex((void **) &pnewpt); + *pnewpt = tstart; + } + // Loop other vertices of this polygon. + for (j = 1; j <= p->numberofvertices; j++) { + // get a vertex. + if (j < p->numberofvertices) { + end2 = p->vertexlist[j]; + } else { + end2 = p->vertexlist[0]; // Form a loop from last to first. + } + if ((end2 < in->firstnumber) || + (end2 >= in->firstnumber + in->numberofpoints)) { + if (!b->quiet) { + printf("Warning: Invalid vertex %d in polygon %d", end2, i + 1); + printf(" in facet %d.\n", shmark); + } + } else { + if (end1 != end2) { + // 'end1' and 'end2' form a segment. + tend = idx2verlist[end2]; + // Add tstart to V if it haven't been added yet. + if (!pinfected(tend)) { + pinfect(tend); + ptlist->newindex((void **) &pnewpt); + *pnewpt = tend; + } + // Save the segment in S (conlist). + conlist->newindex((void **) &cons); + cons[0] = tstart; + cons[1] = tend; + // Set the start for next continuous segment. + end1 = end2; + tstart = tend; + } else { + // Two identical vertices mean an isolated vertex of F. + if (p->numberofvertices > 2) { + // This may be an error in the input, anyway, we can continue + // by simply skipping this segment. + if (!b->quiet) { + printf("Warning: Polygon %d has two identical verts", i + 1); + printf(" in facet %d.\n", shmark); + } + } + // Ignore this vertex. + } + } + // Is the polygon degenerate (a segment or a vertex)? + if (p->numberofvertices == 2) break; + } + } + // Unmark vertices. + for (i = 0; i < ptlist->objects; i++) { + pnewpt = (point *) fastlookup(ptlist, i); + puninfect(*pnewpt); + } + + // Triangulate F into a CDT. + // If in->facetmarklist is NULL, use the default marker -1. + triangulate(in->facetmarkerlist ? in->facetmarkerlist[shmark - 1] : -1, + ptlist, conlist, f->numberofholes, f->holelist); + + // Clear working lists. + ptlist->restart(); + conlist->restart(); + } + + + // Remove redundant segments and build the face links. + unifysegments(); + if (in->numberofedges > 0) { + // There are input segments. Insert them. + identifyinputedges(idx2verlist); + } + if (!b->diagnose && !b->nomergefacet && !b->nobisect) { // No -d -M -Y + // Merge coplanar facets. + mergefacets(); + } + + // Mark all segment vertices to be RIDGEVERTEX. + face segloop; + point *ppt; + subsegs->traversalinit(); + segloop.sh = shellfacetraverse(subsegs); + while (segloop.sh != NULL) { + ppt = (point *) &(segloop.sh[3]); + for (i = 0; i < 2; i++) { + setpointtype(ppt[i], RIDGEVERTEX); + } + segloop.sh = shellfacetraverse(subsegs); + } + + if (b->object == tetgenbehavior::STL) { + // Remove redundant vertices (for .stl input mesh). + jettisonnodes(); + // Update the number of input vertices. + in->numberofpoints = points->items; + } + + if (b->verbose) { + printf(" %ld (%ld) subfaces (segments).\n", subfaces->items, + subsegs->items); + } + + // The total number of iunput segments. + insegments = subsegs->items; + + delete [] idx2verlist; + delete ptlist; + delete conlist; +} + +// // +// // +//== surface_cxx =============================================================// + +//== constrained_cxx =========================================================// +// // +// // + +//============================================================================// +// // +// finddirection() Find the tet on the path from one point to another. // +// // +// The path starts from 'searchtet''s origin and ends at 'endpt'. On finish, // +// 'searchtet' contains a tet on the path, its origin does not change. // +// // +// The return value indicates one of the following cases (let 'searchtet' be // +// abcd, a is the origin of the path): // +// - ACROSSVERT, edge ab is collinear with the path; // +// - ACROSSEDGE, edge bc intersects with the path; // +// - ACROSSFACE, face bcd intersects with the path. // +// // +// WARNING: This routine is designed for convex triangulations, and will not // +// generally work after the holes and concavities have been carved. // +// // +//============================================================================// + +enum tetgenmesh::interresult + tetgenmesh::finddirection(triface* searchtet, point endpt) +{ + triface neightet; + point pa, pb, pc, pd; + enum {HMOVE, RMOVE, LMOVE} nextmove; + REAL hori, rori, lori; + int t1ver; + int s; + + // The origin is fixed. + pa = org(*searchtet); + if ((point) searchtet->tet[7] == dummypoint) { + // A hull tet. Choose the neighbor of its base face. + decode(searchtet->tet[3], *searchtet); + // Reset the origin to be pa. + if ((point) searchtet->tet[4] == pa) { + searchtet->ver = 11; + } else if ((point) searchtet->tet[5] == pa) { + searchtet->ver = 3; + } else if ((point) searchtet->tet[6] == pa) { + searchtet->ver = 7; + } else { + searchtet->ver = 0; + } + } + + pb = dest(*searchtet); + // Check whether the destination or apex is 'endpt'. + if (pb == endpt) { + // pa->pb is the search edge. + return ACROSSVERT; + } + + pc = apex(*searchtet); + if (pc == endpt) { + // pa->pc is the search edge. + eprevesymself(*searchtet); + return ACROSSVERT; + } + + // Walk through tets around pa until the right one is found. + while (1) { + + pd = oppo(*searchtet); + // Check whether the opposite vertex is 'endpt'. + if (pd == endpt) { + // pa->pd is the search edge. + esymself(*searchtet); + enextself(*searchtet); + return ACROSSVERT; + } + // Check if we have entered outside of the domain. + if (pd == dummypoint) { + // This is possible when the mesh is non-convex. + if (nonconvex) { + return ACROSSFACE; // return ACROSSSUB; // Hit a bounday. + } else { + terminatetetgen(this, 2); + } + } + + // Now assume that the base face abc coincides with the horizon plane, + // and d lies above the horizon. The search point 'endpt' may lie + // above or below the horizon. We test the orientations of 'endpt' + // with respect to three planes: abc (horizon), bad (right plane), + // and acd (left plane). + hori = orient3d(pa, pb, pc, endpt); + rori = orient3d(pb, pa, pd, endpt); + lori = orient3d(pa, pc, pd, endpt); + + // Now decide the tet to move. It is possible there are more than one + // tets are viable moves. Is so, randomly choose one. + if (hori > 0) { + if (rori > 0) { + if (lori > 0) { + // Any of the three neighbors is a viable move. + s = randomnation(3); + if (s == 0) { + nextmove = HMOVE; + } else if (s == 1) { + nextmove = RMOVE; + } else { + nextmove = LMOVE; + } + } else { + // Two tets, below horizon and below right, are viable. + if (randomnation(2)) { + nextmove = HMOVE; + } else { + nextmove = RMOVE; + } + } + } else { + if (lori > 0) { + // Two tets, below horizon and below left, are viable. + if (randomnation(2)) { + nextmove = HMOVE; + } else { + nextmove = LMOVE; + } + } else { + // The tet below horizon is chosen. + nextmove = HMOVE; + } + } + } else { + if (rori > 0) { + if (lori > 0) { + // Two tets, below right and below left, are viable. + if (randomnation(2)) { + nextmove = RMOVE; + } else { + nextmove = LMOVE; + } + } else { + // The tet below right is chosen. + nextmove = RMOVE; + } + } else { + if (lori > 0) { + // The tet below left is chosen. + nextmove = LMOVE; + } else { + // 'endpt' lies either on the plane(s) or across face bcd. + if (hori == 0) { + if (rori == 0) { + // pa->'endpt' is COLLINEAR with pa->pb. + return ACROSSVERT; + } + if (lori == 0) { + // pa->'endpt' is COLLINEAR with pa->pc. + eprevesymself(*searchtet); // [a,c,d] + return ACROSSVERT; + } + // pa->'endpt' crosses the edge pb->pc. + return ACROSSEDGE; + } + if (rori == 0) { + if (lori == 0) { + // pa->'endpt' is COLLINEAR with pa->pd. + esymself(*searchtet); // face bad. + enextself(*searchtet); // face [a,d,b] + return ACROSSVERT; + } + // pa->'endpt' crosses the edge pb->pd. + esymself(*searchtet); // face bad. + enextself(*searchtet); // face adb + return ACROSSEDGE; + } + if (lori == 0) { + // pa->'endpt' crosses the edge pc->pd. + eprevesymself(*searchtet); // [a,c,d] + return ACROSSEDGE; + } + // pa->'endpt' crosses the face bcd. + return ACROSSFACE; + } + } + } + + // Move to the next tet, fix pa as its origin. + if (nextmove == RMOVE) { + fnextself(*searchtet); + } else if (nextmove == LMOVE) { + eprevself(*searchtet); + fnextself(*searchtet); + enextself(*searchtet); + } else { // HMOVE + fsymself(*searchtet); + enextself(*searchtet); + } + if (org(*searchtet) != pa) { + terminatetetgen(this, 2); + } + pb = dest(*searchtet); + pc = apex(*searchtet); + + } // while (1) + +} + +//============================================================================// +// // +// scoutsegment() Search an edge in the tetrahedralization. // +// // +// If the edge is found, it returns SHAREEDGE, and 'searchtet' returns the // +// edge from startpt to endpt. // +// // +// If the edge is missing, it returns either ACROSSEDGE or ACROSSFACE, which // +// indicates that the edge intersects an edge or a face. If 'refpt' is NULL, // +// 'searchtet' returns the edge or face. If 'refpt' is not NULL, it returns // +// a vertex which encroaches upon this edge, and 'searchtet' returns a tet // +// which containing 'refpt'. // +// // +// The parameter 'sedge' is used to report self-intersection. It is the // +// whose endpoints are 'startpt' and 'endpt'. It must not be a NULL. // +// // +//============================================================================// + +enum tetgenmesh::interresult tetgenmesh::scoutsegment(point startpt,point endpt, + face *sedge, triface* searchtet, point* refpt, arraypool* intfacelist) +{ + point pd; + enum interresult dir; + int t1ver; + + if (b->verbose > 2) { + printf(" Scout seg (%d, %d).\n",pointmark(startpt),pointmark(endpt)); + } + + point2tetorg(startpt, *searchtet); + dir = finddirection(searchtet, endpt); + + if (dir == ACROSSVERT) { + pd = dest(*searchtet); + if (pd == endpt) { + if (issubseg(*searchtet)) { + //report_selfint_edge(startpt, endpt, sedge, searchtet, dir); + terminatetetgen(this, 3); + } + return SHAREEDGE; + } else { + // A point is on the path. + //report_selfint_edge(startpt, endpt, sedge, searchtet, dir); + terminatetetgen(this, 3); + return ACROSSVERT; + } + } + + // dir is either ACROSSEDGE or ACROSSFACE. + enextesymself(*searchtet); // Go to the opposite face. + fsymself(*searchtet); // Enter the adjacent tet. + + if (dir == ACROSSEDGE) { + // Check whether two segments are intersecting. + if (issubseg(*searchtet)) { + //report_selfint_edge(startpt, endpt, sedge, searchtet, dir); + terminatetetgen(this, 3); + } + } else if (dir == ACROSSFACE) { + if (checksubfaceflag) { + // Check whether a segment and a subface are intersecting. + if (issubface(*searchtet)) { + //report_selfint_edge(startpt, endpt, sedge, searchtet, dir); + terminatetetgen(this, 3); + } + } + } else { + terminatetetgen(this, 2); + } + + if (refpt == NULL) { + // Do not need a reference point. Return. + return dir; + } + + triface neightet, reftet; + point pa, pb, pc; + REAL angmax, ang; + int types[2], poss[4]; + int pos = 0, i, j; + + pa = org(*searchtet); + angmax = interiorangle(pa, startpt, endpt, NULL); + *refpt = pa; + pb = dest(*searchtet); + ang = interiorangle(pb, startpt, endpt, NULL); + if (ang > angmax) { + angmax = ang; + *refpt = pb; + } + pc = apex(*searchtet); + ang = interiorangle(pc, startpt, endpt, NULL); + if (ang > angmax) { + angmax = ang; + *refpt = pc; + } + reftet = *searchtet; // Save the tet containing the refpt. + + // Search intersecting faces along the segment. + while (1) { + + + pd = oppo(*searchtet); + + + // Stop if we meet 'endpt'. + if (pd == endpt) break; + + ang = interiorangle(pd, startpt, endpt, NULL); + if (ang > angmax) { + angmax = ang; + *refpt = pd; + reftet = *searchtet; + } + + // Find a face intersecting the segment. + if (dir == ACROSSFACE) { + // One of the three oppo faces in 'searchtet' intersects the segment. + neightet = *searchtet; + j = (neightet.ver & 3); // j is the current face number. + for (i = j + 1; i < j + 4; i++) { + neightet.ver = (i % 4); + pa = org(neightet); + pb = dest(neightet); + pc = apex(neightet); + pd = oppo(neightet); // The above point. + if (tri_edge_test(pa, pb, pc, startpt, endpt, pd, 1, types, poss)) { + dir = (enum interresult) types[0]; + pos = poss[0]; + break; + } else { + dir = DISJOINT; + pos = 0; + } + } + } else if (dir == ACROSSEDGE) { + // Check the two opposite faces (of the edge) in 'searchtet'. + for (i = 0; i < 2; i++) { + if (i == 0) { + enextesym(*searchtet, neightet); + } else { + eprevesym(*searchtet, neightet); + } + pa = org(neightet); + pb = dest(neightet); + pc = apex(neightet); + pd = oppo(neightet); // The above point. + if (tri_edge_test(pa, pb, pc, startpt, endpt, pd, 1, types, poss)) { + dir = (enum interresult) types[0]; + pos = poss[0]; + break; + } else { + dir = DISJOINT; + pos = 0; + } + } + if (dir == DISJOINT) { + // No intersection. Rotate to the next tet at the edge. + dir = ACROSSEDGE; + fnextself(*searchtet); + continue; + } + } + + if (dir == ACROSSVERT) { + // This segment passing a vertex. Choose it and return. + for (i = 0; i < pos; i++) { + enextself(neightet); + } + eprev(neightet, *searchtet); + // dest(*searchtet) lies on the segment. + //report_selfint_edge(startpt, endpt, sedge, searchtet, dir); + terminatetetgen(this, 3); + return ACROSSVERT; + } else if (dir == ACROSSEDGE) { + // Get the edge intersects with the segment. + for (i = 0; i < pos; i++) { + enextself(neightet); + } + } + // Go to the next tet. + fsym(neightet, *searchtet); + + if (dir == ACROSSEDGE) { + // Check whether two segments are intersecting. + if (issubseg(*searchtet)) { + //report_selfint_edge(startpt, endpt, sedge, searchtet, dir); + terminatetetgen(this, 3); + } + } else if (dir == ACROSSFACE) { + if (checksubfaceflag) { + // Check whether a segment and a subface are intersecting. + if (issubface(*searchtet)) { + //report_selfint_edge(startpt, endpt, sedge, searchtet, dir); + terminatetetgen(this, 3); + } + } + } else { + terminatetetgen(this, 2); + } + + } // while (1) + + // A valid reference point should inside the diametrial circumsphere of + // the missing segment, i.e., it encroaches upon it. + if (2.0 * angmax < PI) { + *refpt = NULL; + } + + + *searchtet = reftet; + return dir; +} + +//============================================================================// +// // +// getsteinerpointonsegment() Get a Steiner point on a segment. // +// // +// Return '1' if 'refpt' lies on an adjacent segment of this segment. Other- // +// wise, return '0'. // +// // +//============================================================================// + +int tetgenmesh::getsteinerptonsegment(face* seg, point refpt, point steinpt) +{ + point ei = sorg(*seg); + point ej = sdest(*seg); + int adjflag = 0, i; + + if (refpt != NULL) { + REAL L, L1, t; + + if (pointtype(refpt) == FREESEGVERTEX) { + face parentseg; + sdecode(point2sh(refpt), parentseg); + int sidx1 = getfacetindex(parentseg); + point far_pi = segmentendpointslist[sidx1 * 2]; + point far_pj = segmentendpointslist[sidx1 * 2 + 1]; + int sidx2 = getfacetindex(*seg); + point far_ei = segmentendpointslist[sidx2 * 2]; + point far_ej = segmentendpointslist[sidx2 * 2 + 1]; + if ((far_pi == far_ei) || (far_pj == far_ei)) { + // Create a Steiner point at the intersection of the segment + // [far_ei, far_ej] and the sphere centered at far_ei with + // radius |far_ei - refpt|. + L = distance(far_ei, far_ej); + L1 = distance(far_ei, refpt); + t = L1 / L; + for (i = 0; i < 3; i++) { + steinpt[i] = far_ei[i] + t * (far_ej[i] - far_ei[i]); + } + adjflag = 1; + } else if ((far_pi == far_ej) || (far_pj == far_ej)) { + L = distance(far_ei, far_ej); + L1 = distance(far_ej, refpt); + t = L1 / L; + for (i = 0; i < 3; i++) { + steinpt[i] = far_ej[i] + t * (far_ei[i] - far_ej[i]); + } + adjflag = 1; + } else { + // Cut the segment by the projection point of refpt. + projpt2edge(refpt, ei, ej, steinpt); + } + } else { + // Cut the segment by the projection point of refpt. + projpt2edge(refpt, ei, ej, steinpt); + } + + // Make sure that steinpt is not too close to ei and ej. + L = distance(ei, ej); + L1 = distance(steinpt, ei); + t = L1 / L; + if ((t < 0.2) || (t > 0.8)) { + // Split the point at the middle. + for (i = 0; i < 3; i++) { + steinpt[i] = ei[i] + 0.5 * (ej[i] - ei[i]); + } + } + } else { + // Split the point at the middle. + for (i = 0; i < 3; i++) { + steinpt[i] = ei[i] + 0.5 * (ej[i] - ei[i]); + } + } + + + return adjflag; +} + + + +//============================================================================// +// // +// delaunizesegments() Recover segments in a DT. // +// // +// All segments need to be recovered are in 'subsegstack' (Q). They will be // +// be recovered one by one (in a random order). // +// // +// Given a segment s in the Q, this routine first queries s in the DT, if s // +// matches an edge in DT, it is 'locked' at the edge. Otherwise, s is split // +// by inserting a new point p in both the DT and itself. The two new subseg- // +// ments of s are queued in Q. The process continues until Q is empty. // +// // +//============================================================================// + +void tetgenmesh::delaunizesegments() +{ + triface searchtet, spintet; + face searchsh; + face sseg, *psseg; + point refpt, newpt; + enum interresult dir; + insertvertexflags ivf; + int t1ver; + + + ivf.bowywat = 1; // Use Bowyer-Watson insertion. + ivf.sloc = (int) ONEDGE; // on 'sseg'. + ivf.sbowywat = 1; // Use Bowyer-Watson insertion. + ivf.assignmeshsize = b->metric; + ivf.smlenflag = useinsertradius; // Return the closet mesh vertex. + + // Loop until 'subsegstack' is empty. + while (subsegstack->objects > 0l) { + // seglist is used as a stack. + subsegstack->objects--; + psseg = (face *) fastlookup(subsegstack, subsegstack->objects); + sseg = *psseg; + + // Check if this segment has been recovered. + sstpivot1(sseg, searchtet); + if (searchtet.tet != NULL) { + continue; // Not a missing segment. + } + + // Search the segment. + dir = scoutsegment(sorg(sseg), sdest(sseg), &sseg,&searchtet,&refpt,NULL); + + if (dir == SHAREEDGE) { + // Found this segment, insert it. + // Let the segment remember an adjacent tet. + sstbond1(sseg, searchtet); + // Bond the segment to all tets containing it. + spintet = searchtet; + do { + tssbond1(spintet, sseg); + fnextself(spintet); + } while (spintet.tet != searchtet.tet); + } else { + if ((dir == ACROSSFACE) || (dir == ACROSSEDGE)) { + // The segment is missing. Split it. + // Create a new point. + makepoint(&newpt, FREESEGVERTEX); + //setpointtype(newpt, FREESEGVERTEX); + getsteinerptonsegment(&sseg, refpt, newpt); + + // Start searching from 'searchtet'. + ivf.iloc = (int) OUTSIDE; + // Insert the new point into the tetrahedralization T. + // Missing segments and subfaces are queued for recovery. + // Note that T is convex (nonconvex = 0). + if (insertpoint(newpt, &searchtet, &searchsh, &sseg, &ivf)) { + // The new point has been inserted. + st_segref_count++; + if (steinerleft > 0) steinerleft--; + if (useinsertradius) { + //save_segmentpoint_insradius(newpt, ivf.parentpt, ivf.smlen); + } + } else { + if (ivf.iloc == (int) NEARVERTEX) { + // The new point (in the segment) is very close to an existing + // vertex -- a small feature is detected. + point nearpt = org(searchtet); + if (pointtype(nearpt) == FREESEGVERTEX) { + face parentseg; + sdecode(point2sh(nearpt), parentseg); + point p1 = farsorg(sseg); + point p2 = farsdest(sseg); + point p3 = farsorg(parentseg); + point p4 = farsdest(parentseg); + printf("Two segments are very close to each other.\n"); + printf(" Segment 1: [%d, %d] #%d\n", pointmark(p1), + pointmark(p2), shellmark(sseg)); + printf(" Segment 2: [%d, %d] #%d\n", pointmark(p3), + pointmark(p4), shellmark(parentseg)); + terminatetetgen(this, 4); + } else { + terminatetetgen(this, 2); + } + } else if (ivf.iloc == (int) ONVERTEX) { + // The new point (in the segment) is coincident with an existing + // vertex -- a self-intersection is detected. + eprevself(searchtet); + //report_selfint_edge(sorg(sseg), sdest(sseg), &sseg, &searchtet, + // ACROSSVERT); + terminatetetgen(this, 3); + } else { + // An unknown case. Report a bug. + terminatetetgen(this, 2); + } + } + } else { + // An unknown case. Report a bug. + terminatetetgen(this, 2); + } + } + } // while +} + +//============================================================================// +// // +// scoutsubface() Search subface in the tetrahedralization. // +// // +// 'searchsh' is searched in T. If it exists, it is 'locked' at the face in // +// T. 'searchtet' refers to the face. Otherwise, it is missing. // +// // +// The parameter 'shflag' indicates whether 'searchsh' is a boundary face or // +// not. It is possible that 'searchsh' is a temporarily subface that is used // +// as a cavity boundary face. // +// // +//============================================================================// + +int tetgenmesh::scoutsubface(face* searchsh, triface* searchtet, int shflag) +{ + point pa = sorg(*searchsh); + point pb = sdest(*searchsh); + + // Get a tet whose origin is a. + point2tetorg(pa, *searchtet); + // Search the edge [a,b]. + enum interresult dir = finddirection(searchtet, pb); + if (dir == ACROSSVERT) { + // Check validity of a PLC. + if (dest(*searchtet) != pb) { + if (shflag) { + // A vertex lies on the search edge. + //report_selfint_edge(pa, pb, searchsh, searchtet, dir); + terminatetetgen(this, 3); + } else { + terminatetetgen(this, 2); + } + } + int t1ver; + // The edge exists. Check if the face exists. + point pc = sapex(*searchsh); + // Searchtet holds edge [a,b]. Search a face with apex c. + triface spintet = *searchtet; + while (1) { + if (apex(spintet) == pc) { + // Found a face matching to 'searchsh'! + if (!issubface(spintet)) { + // Insert 'searchsh'. + tsbond(spintet, *searchsh); + fsymself(spintet); + sesymself(*searchsh); + tsbond(spintet, *searchsh); + *searchtet = spintet; + return 1; + } else { + terminatetetgen(this, 2); + } + } + fnextself(spintet); + if (spintet.tet == searchtet->tet) break; + } + } + + return 0; +} + +//============================================================================// +// // +// formregion() Form the missing region of a missing subface. // +// // +// 'missh' is a missing subface. From it we form a missing region R which is // +// a connected region formed by a set of missing subfaces of a facet. // +// Comment: There should be no segment inside R. // +// // +// 'missingshs' returns the list of subfaces in R. All subfaces in this list // +// are oriented as the 'missh'. 'missingshbds' returns the list of boundary // +// edges (tetrahedral handles) of R. 'missingshverts' returns all vertices // +// of R. They are all pmarktested. // +// // +// Except the first one (which is 'missh') in 'missingshs', each subface in // +// this list represents an internal edge of R, i.e., it is missing in the // +// tetrahedralization. Since R may contain interior vertices, not all miss- // +// ing edges can be found by this way. // +//============================================================================// + +void tetgenmesh::formregion(face* missh, arraypool* missingshs, + arraypool* missingshbds, arraypool* missingshverts) +{ + triface searchtet, spintet; + face neighsh, *parysh; + face neighseg, fakeseg; + point pa, pb, *parypt; + enum interresult dir; + int t1ver; + int i, j; + + smarktest(*missh); + missingshs->newindex((void **) &parysh); + *parysh = *missh; + + // Incrementally find other missing subfaces. + for (i = 0; i < missingshs->objects; i++) { + missh = (face *) fastlookup(missingshs, i); + for (j = 0; j < 3; j++) { + pa = sorg(*missh); + pb = sdest(*missh); + point2tetorg(pa, searchtet); + dir = finddirection(&searchtet, pb); + if (dir != ACROSSVERT) { + // This edge is missing. Its neighbor is a missing subface. + spivot(*missh, neighsh); + if (!smarktested(neighsh)) { + // Adjust the face orientation. + if (sorg(neighsh) != pb) sesymself(neighsh); + smarktest(neighsh); + missingshs->newindex((void **) &parysh); + *parysh = neighsh; + } + } else { + if (dest(searchtet) != pb) { + // Report a PLC problem. + //report_selfint_edge(pa, pb, missh, &searchtet, dir); + terminatetetgen(this, 3); + } + } + // Collect the vertices of R. + if (!pmarktested(pa)) { + pmarktest(pa); + missingshverts->newindex((void **) &parypt); + *parypt = pa; + } + senextself(*missh); + } // j + } // i + + // Get the boundary edges of R. + for (i = 0; i < missingshs->objects; i++) { + missh = (face *) fastlookup(missingshs, i); + for (j = 0; j < 3; j++) { + spivot(*missh, neighsh); + if ((neighsh.sh == NULL) || !smarktested(neighsh)) { + // A boundary edge of R. + // Let the segment point to the adjacent tet. + point2tetorg(sorg(*missh), searchtet); + finddirection(&searchtet, sdest(*missh)); + missingshbds->newindex((void **) &parysh); + *parysh = *missh; + // Check if this edge is a segment. + sspivot(*missh, neighseg); + if (neighseg.sh == NULL) { + // Temporarily create a segment at this edge. + makeshellface(subsegs, &fakeseg); + setsorg(fakeseg, sorg(*missh)); + setsdest(fakeseg, sdest(*missh)); + sinfect(fakeseg); // Mark it as faked. + // Connect it to all tets at this edge. + spintet = searchtet; + while (1) { + tssbond1(spintet, fakeseg); + fnextself(spintet); + if (spintet.tet == searchtet.tet) break; + } + neighseg = fakeseg; + } + // Let the segment and the boundary edge point to each other. + ssbond(*missh, neighseg); + sstbond1(neighseg, searchtet); + } + senextself(*missh); + } // j + } // i + + + // Unmarktest collected missing subfaces. + for (i = 0; i < missingshs->objects; i++) { + parysh = (face *) fastlookup(missingshs, i); + sunmarktest(*parysh); + } +} + +//============================================================================// +// // +// scoutcrossedge() Search an edge that crosses the missing region. // +// // +// Return 1 if a crossing edge is found. It is returned by 'crosstet'. More- // +// over, the edge is oriented such that its origin lies below R. Return 0 // +// if no such edge is found. // +// // +// Assumption: All vertices of the missing region are marktested. // +// // +//============================================================================// + +int tetgenmesh::scoutcrossedge(triface& crosstet, arraypool* missingshbds, + arraypool* missingshs) +{ + triface searchtet, spintet, neightet; + face oldsh, searchsh, *parysh; + face neighseg; + point pa, pb, pc, pd, pe; + REAL ori; + int types[2], poss[4]; + int searchflag, interflag; + int t1ver; + int i, j; + + searchflag = 0; + + // Search the first new subface to fill the region. + for (i = 0; i < missingshbds->objects && !searchflag; i++) { + parysh = (face *) fastlookup(missingshbds, i); + sspivot(*parysh, neighseg); + sstpivot1(neighseg, searchtet); + if (org(searchtet) != sorg(*parysh)) { + esymself(searchtet); + } + spintet = searchtet; + while (1) { + if (pmarktested(apex(spintet))) { + // A possible interior face. + neightet = spintet; + oldsh = *parysh; + // Try to recover an interior edge. + for (j = 0; j < 2; j++) { + enextself(neightet); + if (!issubseg(neightet)) { + if (j == 0) { + senext(oldsh, searchsh); + } else { + senext2(oldsh, searchsh); + sesymself(searchsh); + esymself(neightet); + } + // Calculate a lifted point. + pa = sorg(searchsh); + pb = sdest(searchsh); + pc = sapex(searchsh); + pd = dest(neightet); + calculateabovepoint4(pa, pb, pc, pd); + // The lifted point must lie above 'searchsh'. + ori = orient3d(pa, pb, pc, dummypoint); + if (ori > 0) { + sesymself(searchsh); + senextself(searchsh); + } else if (ori == 0) { + terminatetetgen(this, 2); + } + if (sscoutsegment(&searchsh,dest(neightet),0,0,1)==SHAREEDGE) { + // Insert a temp segment to protect the recovered edge. + face tmpseg; + makeshellface(subsegs, &tmpseg); + ssbond(searchsh, tmpseg); + spivotself(searchsh); + ssbond(searchsh, tmpseg); + // Recover locally Delaunay edges. + lawsonflip(); + // Delete the tmp segment. + spivot(tmpseg, searchsh); + ssdissolve(searchsh); + spivotself(searchsh); + ssdissolve(searchsh); + shellfacedealloc(subsegs, tmpseg.sh); + searchflag = 1; + } else { + // Undo the performed flips. + if (flipstack != NULL) { + lawsonflip(); + } + } + break; + } // if (!issubseg(neightet)) + } // j + if (searchflag) break; + } // if (pmarktested(apex(spintet))) + fnextself(spintet); + if (spintet.tet == searchtet.tet) break; + } + } // i + + if (searchflag) { + // Remove faked segments. + face checkseg; + // Remark: We should not use the array 'missingshbds', since the flips may + // change the subfaces. We search them from the subfaces in R. + for (i = 0; i < missingshs->objects; i++) { + parysh = (face *) fastlookup(missingshs, i); + oldsh = *parysh; + for (j = 0; j < 3; j++) { + if (isshsubseg(oldsh)) { + sspivot(oldsh, checkseg); + if (sinfected(checkseg)) { + // It's a faked segment. Delete it. + sstpivot1(checkseg, searchtet); + spintet = searchtet; + while (1) { + tssdissolve1(spintet); + fnextself(spintet); + if (spintet.tet == searchtet.tet) break; + } + shellfacedealloc(subsegs, checkseg.sh); + ssdissolve(oldsh); + } + } + senextself(oldsh); + } // j + } + + fillregioncount++; + + return 0; + } // if (i < missingshbds->objects) + + searchflag = -1; + + for (j = 0; j < missingshbds->objects && (searchflag == -1); j++) { + parysh = (face *) fastlookup(missingshbds, j); + sspivot(*parysh, neighseg); + sstpivot1(neighseg, searchtet); + interflag = 0; + // Let 'spintet' be [#,#,d,e] where [#,#] is the boundary edge of R. + spintet = searchtet; + while (1) { + pd = apex(spintet); + pe = oppo(spintet); + // Skip a hull edge. + if ((pd != dummypoint) && (pe != dummypoint)) { + // Skip an edge containing a vertex of R. + if (!pmarktested(pd) && !pmarktested(pe)) { + // Check if [d,e] intersects R. + for (i = 0; i < missingshs->objects && !interflag; i++) { + parysh = (face *) fastlookup(missingshs, i); + pa = sorg(*parysh); + pb = sdest(*parysh); + pc = sapex(*parysh); + interflag=tri_edge_test(pa, pb, pc, pd, pe, NULL, 1, types, poss); + if (interflag > 0) { + if (interflag == 2) { + // They intersect at a single point. + if ((types[0] == (int) ACROSSFACE) || + (types[0] == (int) ACROSSEDGE)) { + // Go to the crossing edge [d,e,#,#]. + edestoppo(spintet, crosstet); // // [d,e,#,#]. + if (issubseg(crosstet)) { + // It is a segment. Report a PLC problem. + //report_selfint_face(pa, pb, pc, parysh, &crosstet, + // interflag, types, poss); + terminatetetgen(this, 3); + } else { + triface chkface = crosstet; + while (1) { + if (issubface(chkface)) break; + fsymself(chkface); + if (chkface.tet == crosstet.tet) break; + } + if (issubface(chkface)) { + // Two subfaces are intersecting. + //report_selfint_face(pa, pb, pc, parysh, &chkface, + // interflag, types, poss); + terminatetetgen(this, 3); + } + } + // Adjust the edge such that d lies below [a,b,c]. + ori = orient3d(pa, pb, pc, pd); + if (ori < 0) { + esymself(crosstet); + } + searchflag = 1; + } else { + // An improper intersection type, ACROSSVERT, TOUCHFACE, + // TOUCHEDGE, SHAREVERT, ... + // Maybe it is due to a PLC problem. + //report_selfint_face(pa, pb, pc, parysh, &crosstet, + // interflag, types, poss); + terminatetetgen(this, 3); + } + } + break; + } // if (interflag > 0) + } + } + } + // Leave search at this bdry edge if an intersection is found. + if (interflag > 0) break; + // Go to the next tetrahedron. + fnextself(spintet); + if (spintet.tet == searchtet.tet) break; + } // while (1) + } // j + + return searchflag; +} + +//============================================================================// +// // +// formcavity() Form the cavity of a missing region. // +// // +// The missing region R is formed by a set of missing subfaces 'missingshs'. // +// In the following, we assume R is horizontal and oriented. (All subfaces // +// of R are oriented in the same way.) 'searchtet' is a tetrahedron [d,e,#, // +// #] which intersects R in its interior, where the edge [d,e] intersects R, // +// and d lies below R. // +// // +// 'crosstets' returns the set of crossing tets. Every tet in it has the // +// form [d,e,#,#] where [d,e] is a crossing edge, and d lies below R. The // +// set of tets form the cavity C, which is divided into two parts by R, one // +// at top and one at bottom. 'topfaces' and 'botfaces' return the upper and // +// lower boundary faces of C. 'toppoints' contains vertices of 'crosstets' // +// in the top part of C, and so does 'botpoints'. Both 'toppoints' and // +// 'botpoints' contain vertices of R. // +// // +// Important: This routine assumes all vertices of the facet containing this // +// subface are marked, i.e., pmarktested(p) returns true. // +// // +//============================================================================// + +bool tetgenmesh::formcavity(triface* searchtet, arraypool* missingshs, + arraypool* crosstets, arraypool* topfaces, + arraypool* botfaces, arraypool* toppoints, + arraypool* botpoints) +{ + arraypool *crossedges; + triface spintet, neightet, chkface, *parytet; + face *parysh = NULL; + point pa, pd, pe, *parypt; + bool testflag, invalidflag; + int intflag, types[2], poss[4]; + int t1ver; + int i, j, k; + + // Temporarily re-use 'topfaces' for all crossing edges. + crossedges = topfaces; + + if (b->verbose > 2) { + printf(" Form the cavity of a missing region.\n"); + } + // Mark this edge to avoid testing it later. + markedge(*searchtet); + crossedges->newindex((void **) &parytet); + *parytet = *searchtet; + + invalidflag = 0; + // Collect all crossing tets. Each cross tet is saved in the standard + // form [d,e,#,#], where [d,e] is a crossing edge, d lies below R. + // NEITHER d NOR e is a vertex of R (!pmarktested). + for (i = 0; i < crossedges->objects && !invalidflag; i++) { + // Get a crossing edge [d,e,#,#]. + searchtet = (triface *) fastlookup(crossedges, i); + // Sort vertices into the bottom and top arrays. + pd = org(*searchtet); + if (!pinfected(pd)) { + pinfect(pd); + botpoints->newindex((void **) &parypt); + *parypt = pd; + } + pe = dest(*searchtet); + if (!pinfected(pe)) { + pinfect(pe); + toppoints->newindex((void **) &parypt); + *parypt = pe; + } + + // All tets sharing this edge are crossing tets. + spintet = *searchtet; + while (1) { + if (!infected(spintet)) { + infect(spintet); + crosstets->newindex((void **) &parytet); + *parytet = spintet; + } + // Go to the next crossing tet. + fnextself(spintet); + if (spintet.tet == searchtet->tet) break; + } // while (1) + + // Detect new crossing edges. + spintet = *searchtet; + while (1) { + // spintet is [d,e,a,#], where d lies below R, and e lies above R. + pa = apex(spintet); + if (pa != dummypoint) { + if (!pmarktested(pa)) { + // There exists a crossing edge, either [e,a] or [a,d]. First check + // if the crossing edge has already be added, i.e.,to check if one + // of the tetrahedron at this edge has been marked. + testflag = true; + for (j = 0; j < 2 && testflag; j++) { + if (j == 0) { + enext(spintet, neightet); + } else { + eprev(spintet, neightet); + } + while (1) { + if (edgemarked(neightet)) { + // This crossing edge has already been tested. Skip it. + testflag = false; + break; + } + fnextself(neightet); + if (neightet.tet == spintet.tet) break; + } + } // j + if (testflag) { + // Test if [e,a] or [a,d] intersects R. + // Do a brute-force search in the set of subfaces of R. Slow! + // Need to be improved! + pd = org(spintet); + pe = dest(spintet); + for (k = 0; k < missingshs->objects; k++) { + parysh = (face *) fastlookup(missingshs, k); + intflag = tri_edge_test(sorg(*parysh), sdest(*parysh), + sapex(*parysh), pe, pa, NULL, 1, types, poss); + if (intflag > 0) { + // Found intersection. 'a' lies below R. + if (intflag == 2) { + enext(spintet, neightet); + if ((types[0] == (int) ACROSSFACE) || + (types[0] == (int) ACROSSEDGE)) { + // Only this case is valid. + } else { + // A non-valid intersection. Maybe a PLC problem. + invalidflag = 1; + } + } else { + // Coplanar intersection. Maybe a PLC problem. + invalidflag = 1; + } + break; + } + intflag = tri_edge_test(sorg(*parysh), sdest(*parysh), + sapex(*parysh), pa, pd, NULL, 1, types, poss); + if (intflag > 0) { + // Found intersection. 'a' lies above R. + if (intflag == 2) { + eprev(spintet, neightet); + if ((types[0] == (int) ACROSSFACE) || + (types[0] == (int) ACROSSEDGE)) { + // Only this case is valid. + } else { + // A non-valid intersection. Maybe a PLC problem. + invalidflag = 1; + } + } else { + // Coplanar intersection. Maybe a PLC problem. + invalidflag = 1; + } + break; + } + } // k + if (k < missingshs->objects) { + // Found a pair of triangle - edge intersection. + if (invalidflag) { + break; // the while (1) loop + } + // Adjust the edge direction, so that its origin lies below R, + // and its destination lies above R. + esymself(neightet); + // This edge may be a segment. + if (issubseg(neightet)) { + //report_selfint_face(sorg(*parysh), sdest(*parysh), + // sapex(*parysh),parysh,&neightet,intflag,types,poss); + terminatetetgen(this, 3); + } + // Check if it is an edge of a subface. + chkface = neightet; + while (1) { + if (issubface(chkface)) break; + fsymself(chkface); + if (chkface.tet == neightet.tet) break; + } + if (issubface(chkface)) { + // Two subfaces are intersecting. + //report_selfint_face(sorg(*parysh), sdest(*parysh), + // sapex(*parysh),parysh,&chkface,intflag,types,poss); + terminatetetgen(this, 3); + } + + // Mark this edge to avoid testing it again. + markedge(neightet); + crossedges->newindex((void **) &parytet); + *parytet = neightet; + } else { + // No intersection is found. It may be a PLC problem. + invalidflag = 1; + break; // the while (1) loop + } // if (k == missingshs->objects) + } // if (testflag) + } + } // if (pa != dummypoint) + // Go to the next crossing tet. + fnextself(spintet); + if (spintet.tet == searchtet->tet) break; + } // while (1) + } // i + + // Unmark all marked edges. + for (i = 0; i < crossedges->objects; i++) { + searchtet = (triface *) fastlookup(crossedges, i); + unmarkedge(*searchtet); + } + crossedges->restart(); + + + if (invalidflag) { + // Unmark all collected tets. + for (i = 0; i < crosstets->objects; i++) { + searchtet = (triface *) fastlookup(crosstets, i); + uninfect(*searchtet); + } + // Unmark all collected vertices. + for (i = 0; i < botpoints->objects; i++) { + parypt = (point *) fastlookup(botpoints, i); + puninfect(*parypt); + } + for (i = 0; i < toppoints->objects; i++) { + parypt = (point *) fastlookup(toppoints, i); + puninfect(*parypt); + } + crosstets->restart(); + botpoints->restart(); + toppoints->restart(); + + // Randomly split an interior edge of R. + i = randomnation(missingshs->objects - 1); + recentsh = * (face *) fastlookup(missingshs, i); + return false; + } + + if (b->verbose > 2) { + printf(" Formed cavity: %ld (%ld) cross tets (edges).\n", + crosstets->objects, crossedges->objects); + } + + // Collect the top and bottom faces and the middle vertices. Since all top + // and bottom vertices have been infected. Uninfected vertices must be + // middle vertices (i.e., the vertices of R). + // NOTE 1: Hull tets may be collected. Process them as a normal one. + // NOTE 2: Some previously recovered subfaces may be completely inside the + // cavity. In such case, we remove these subfaces from the cavity and put + // them into 'subfacstack'. They will be recovered later. + // NOTE 3: Some segments may be completely inside the cavity, e.g., they + // attached to a subface which is inside the cavity. Such segments are + // put in 'subsegstack'. They will be recovered later. + // NOTE4 : The interior subfaces and segments mentioned in NOTE 2 and 3 + // are identified in the routine "carvecavity()". + + for (i = 0; i < crosstets->objects; i++) { + searchtet = (triface *) fastlookup(crosstets, i); + // searchtet is [d,e,a,b]. + eorgoppo(*searchtet, spintet); + fsym(spintet, neightet); // neightet is [a,b,e,#] + if (!infected(neightet)) { + // A top face. + topfaces->newindex((void **) &parytet); + *parytet = neightet; + } + edestoppo(*searchtet, spintet); + fsym(spintet, neightet); // neightet is [b,a,d,#] + if (!infected(neightet)) { + // A bottom face. + botfaces->newindex((void **) &parytet); + *parytet = neightet; + } + // Add middle vertices if there are (skip dummypoint). + pa = org(neightet); + if (!pinfected(pa)) { + if (pa != dummypoint) { + pinfect(pa); + botpoints->newindex((void **) &parypt); + *parypt = pa; + toppoints->newindex((void **) &parypt); + *parypt = pa; + } + } + pa = dest(neightet); + if (!pinfected(pa)) { + if (pa != dummypoint) { + pinfect(pa); + botpoints->newindex((void **) &parypt); + *parypt = pa; + toppoints->newindex((void **) &parypt); + *parypt = pa; + } + } + } // i + + // Uninfect all collected top, bottom, and middle vertices. + for (i = 0; i < toppoints->objects; i++) { + parypt = (point *) fastlookup(toppoints, i); + puninfect(*parypt); + } + for (i = 0; i < botpoints->objects; i++) { + parypt = (point *) fastlookup(botpoints, i); + puninfect(*parypt); + } + cavitycount++; + + return true; +} + +//============================================================================// +// // +// delaunizecavity() Fill a cavity by Delaunay tetrahedra. // +// // +// The cavity C to be tetrahedralized is the top or bottom part of a whole // +// cavity. 'cavfaces' contains the boundary faces of C. NOTE: faces in 'cav- // +// faces' do not form a closed polyhedron. The "open" side are subfaces of // +// the missing facet. These faces will be recovered later in fillcavity(). // +// // +// This routine first constructs the DT of the vertices. Then it identifies // +// the half boundary faces of the cavity in DT. Possiblely the cavity C will // +// be enlarged. // +// // +// The DT is returned in 'newtets'. // +// // +//============================================================================// + +void tetgenmesh::delaunizecavity(arraypool *cavpoints, arraypool *cavfaces, + arraypool *cavshells, arraypool *newtets, + arraypool *crosstets, arraypool *misfaces) +{ + triface searchtet, neightet, *parytet, *parytet1; + face tmpsh, *parysh; + point pa, pb, pc, pd, pt[3], *parypt; + insertvertexflags ivf; + REAL ori; + long baknum, bakhullsize; + int bakchecksubsegflag, bakchecksubfaceflag; + int t1ver; + int i, j; + + if (b->verbose > 2) { + printf(" Delaunizing cavity: %ld points, %ld faces.\n", + cavpoints->objects, cavfaces->objects); + } + // Remember the current number of crossing tets. It may be enlarged later. + baknum = crosstets->objects; + bakhullsize = hullsize; + bakchecksubsegflag = checksubsegflag; + bakchecksubfaceflag = checksubfaceflag; + hullsize = 0l; + checksubsegflag = 0; + checksubfaceflag = 0; + b->verbose--; // Suppress informations for creating Delaunay tetra. + b->plc = 0; // Do not check near vertices. + + ivf.bowywat = 1; // Use Bowyer-Watson algorithm. + + // Get four non-coplanar points (no dummypoint). + pa = pb = pc = NULL; + for (i = 0; i < cavfaces->objects; i++) { + parytet = (triface *) fastlookup(cavfaces, i); + parytet->ver = epivot[parytet->ver]; + if (apex(*parytet) != dummypoint) { + pa = org(*parytet); + pb = dest(*parytet); + pc = apex(*parytet); + break; + } + } + pd = NULL; + for (; i < cavfaces->objects; i++) { + parytet = (triface *) fastlookup(cavfaces, i); + pt[0] = org(*parytet); + pt[1] = dest(*parytet); + pt[2] = apex(*parytet); + for (j = 0; j < 3; j++) { + if (pt[j] != dummypoint) { // Do not include a hull point. + ori = orient3d(pa, pb, pc, pt[j]); + if (ori != 0) { + pd = pt[j]; + if (ori > 0) { // Swap pa and pb. + pt[j] = pa; pa = pb; pb = pt[j]; + } + break; + } + } + } + if (pd != NULL) break; + } + + // Create an init DT. + initialdelaunay(pa, pb, pc, pd); + + // Incrementally insert the vertices (duplicated vertices are ignored). + for (i = 0; i < cavpoints->objects; i++) { + pt[0] = * (point *) fastlookup(cavpoints, i); + searchtet = recenttet; + ivf.iloc = (int) OUTSIDE; + insertpoint(pt[0], &searchtet, NULL, NULL, &ivf); + } + + if (b->verbose > 2) { + printf(" Identifying %ld boundary faces of the cavity.\n", + cavfaces->objects); + } + + while (1) { + + // Identify boundary faces. Mark interior tets. Save missing faces. + for (i = 0; i < cavfaces->objects; i++) { + parytet = (triface *) fastlookup(cavfaces, i); + // Skip an interior face (due to the enlargement of the cavity). + if (infected(*parytet)) continue; + parytet->ver = epivot[parytet->ver]; + pt[0] = org(*parytet); + pt[1] = dest(*parytet); + pt[2] = apex(*parytet); + // Create a temp subface. + makeshellface(subfaces, &tmpsh); + setshvertices(tmpsh, pt[0], pt[1], pt[2]); + // Insert tmpsh in DT. + searchtet.tet = NULL; + if (scoutsubface(&tmpsh, &searchtet, 0)) { // shflag = 0 + // Inserted! 'tmpsh' must face toward the inside of the cavity. + // Remember the boundary tet (outside the cavity) in tmpsh + // (use the adjacent tet slot). + tmpsh.sh[0] = (shellface) encode(*parytet); + // Save this subface. + cavshells->newindex((void **) &parysh); + *parysh = tmpsh; + } + else { + // This boundary face is missing. + shellfacedealloc(subfaces, tmpsh.sh); + // Save this face in list. + misfaces->newindex((void **) &parytet1); + *parytet1 = *parytet; + } + } // i + + if (misfaces->objects > 0) { + if (b->verbose > 2) { + printf(" Enlarging the cavity. %ld missing bdry faces\n", + misfaces->objects); + } + + // Removing all temporary subfaces. + for (i = 0; i < cavshells->objects; i++) { + parysh = (face *) fastlookup(cavshells, i); + stpivot(*parysh, neightet); + tsdissolve(neightet); // Detach it from adj. tets. + fsymself(neightet); + tsdissolve(neightet); + shellfacedealloc(subfaces, parysh->sh); + } + cavshells->restart(); + + // Infect the points which are of the cavity. + for (i = 0; i < cavpoints->objects; i++) { + pt[0] = * (point *) fastlookup(cavpoints, i); + pinfect(pt[0]); // Mark it as inserted. + } + + // Enlarge the cavity. + for (i = 0; i < misfaces->objects; i++) { + // Get a missing face. + parytet = (triface *) fastlookup(misfaces, i); + if (!infected(*parytet)) { + // Put it into crossing tet list. + infect(*parytet); + crosstets->newindex((void **) &parytet1); + *parytet1 = *parytet; + // Insert the opposite point if it is not in DT. + pd = oppo(*parytet); + if (!pinfected(pd)) { + searchtet = recenttet; + ivf.iloc = (int) OUTSIDE; + insertpoint(pd, &searchtet, NULL, NULL, &ivf); + pinfect(pd); + cavpoints->newindex((void **) &parypt); + *parypt = pd; + } + // Add three opposite faces into the boundary list. + for (j = 0; j < 3; j++) { + esym(*parytet, neightet); + fsymself(neightet); + if (!infected(neightet)) { + cavfaces->newindex((void **) &parytet1); + *parytet1 = neightet; + } + enextself(*parytet); + } // j + } // if (!infected(parytet)) + } // i + + // Uninfect the points which are of the cavity. + for (i = 0; i < cavpoints->objects; i++) { + pt[0] = * (point *) fastlookup(cavpoints, i); + puninfect(pt[0]); + } + + misfaces->restart(); + continue; + } // if (misfaces->objects > 0) + + break; + + } // while (1) + + // Collect all tets of the DT. All new tets are marktested. + marktest(recenttet); + newtets->newindex((void **) &parytet); + *parytet = recenttet; + for (i = 0; i < newtets->objects; i++) { + searchtet = * (triface *) fastlookup(newtets, i); + for (j = 0; j < 4; j++) { + decode(searchtet.tet[j], neightet); + if (!marktested(neightet)) { + marktest(neightet); + newtets->newindex((void **) &parytet); + *parytet = neightet; + } + } + } + + cavpoints->restart(); + cavfaces->restart(); + + if (crosstets->objects > baknum) { + // The cavity has been enlarged. + cavityexpcount++; + } + + // Restore the original values. + hullsize = bakhullsize; + checksubsegflag = bakchecksubsegflag; + checksubfaceflag = bakchecksubfaceflag; + b->verbose++; + b->plc = 1; +} + +//============================================================================// +// // +// fillcavity() Fill new tets into the cavity. // +// // +// The new tets are stored in two disjoint sets(which share the same facet). // +// 'topfaces' and 'botfaces' are the boundaries of these two sets, respect- // +// ively. 'midfaces' is empty on input, and will store faces in the facet. // +// // +// Important: This routine assumes all vertices of the missing region R are // +// marktested, i.e., pmarktested(p) returns true. // +// // +//============================================================================// + +bool tetgenmesh::fillcavity(arraypool* topshells, arraypool* botshells, + arraypool* midfaces, arraypool* missingshs, + arraypool* topnewtets, arraypool* botnewtets, + triface* crossedge) +{ + arraypool *cavshells; + triface bdrytet, neightet, *parytet; + triface searchtet, spintet; + face *parysh; + face checkseg; + point pa, pb, pc; + bool mflag; + int t1ver; + int i, j; + + // Connect newtets to tets outside the cavity. These connections are needed + // for identifying the middle faces (which belong to R). + for (j = 0; j < 2; j++) { + cavshells = (j == 0 ? topshells : botshells); + if (cavshells != NULL) { + for (i = 0; i < cavshells->objects; i++) { + // Get a temp subface. + parysh = (face *) fastlookup(cavshells, i); + // Get the boundary tet outside the cavity (saved in sh[0]). + decode(parysh->sh[0], bdrytet); + pa = org(bdrytet); + pb = dest(bdrytet); + pc = apex(bdrytet); + // Get the adjacent new tet inside the cavity. + stpivot(*parysh, neightet); + // Mark neightet as an interior tet of this cavity. + infect(neightet); + // Connect the two tets (the old connections are replaced). + bond(bdrytet, neightet); + tsdissolve(neightet); // Clear the pointer to tmpsh. + // Update the point-to-tets map. + setpoint2tet(pa, (tetrahedron) neightet.tet); + setpoint2tet(pb, (tetrahedron) neightet.tet); + setpoint2tet(pc, (tetrahedron) neightet.tet); + } // i + } // if (cavshells != NULL) + } // j + + if (crossedge != NULL) { + // Glue top and bottom tets at their common facet. + triface toptet, bottet, spintet, *midface; + point pd, pe; + REAL ori; + int types[2], poss[4]; + int interflag; + int bflag; + + mflag = false; + pd = org(*crossedge); + pe = dest(*crossedge); + + // Search the first (middle) face in R. + // Since R may be non-convex, we must make sure that the face is in the + // interior of R. We search a face in 'topnewtets' whose three vertices + // are on R and it intersects 'crossedge' in its interior. Then search + // a matching face in 'botnewtets'. + for (i = 0; i < topnewtets->objects && !mflag; i++) { + searchtet = * (triface *) fastlookup(topnewtets, i); + for (searchtet.ver = 0; searchtet.ver < 4 && !mflag; searchtet.ver++) { + pa = org(searchtet); + if (pmarktested(pa)) { + pb = dest(searchtet); + if (pmarktested(pb)) { + pc = apex(searchtet); + if (pmarktested(pc)) { + // Check if this face intersects [d,e]. + interflag = tri_edge_test(pa,pb,pc,pd,pe,NULL,1,types,poss); + if (interflag == 2) { + // They intersect at a single point. Found. + toptet = searchtet; + // The face lies in the interior of R. + // Get the tet (in topnewtets) which lies above R. + ori = orient3d(pa, pb, pc, pd); + if (ori < 0) { + fsymself(toptet); + pa = org(toptet); + pb = dest(toptet); + } else if (ori == 0) { + terminatetetgen(this, 2); + } + // Search the face [b,a,c] in 'botnewtets'. + for (j = 0; j < botnewtets->objects; j++) { + neightet = * (triface *) fastlookup(botnewtets, j); + // Is neightet contains 'b'. + if ((point) neightet.tet[4] == pb) { + neightet.ver = 11; + } else if ((point) neightet.tet[5] == pb) { + neightet.ver = 3; + } else if ((point) neightet.tet[6] == pb) { + neightet.ver = 7; + } else if ((point) neightet.tet[7] == pb) { + neightet.ver = 0; + } else { + continue; + } + // Is the 'neightet' contains edge [b,a]. + if (dest(neightet) == pa) { + // 'neightet' is just the edge. + } else if (apex(neightet) == pa) { + eprevesymself(neightet); + } else if (oppo(neightet) == pa) { + esymself(neightet); + enextself(neightet); + } else { + continue; + } + // Is 'neightet' the face [b,a,c]. + if (apex(neightet) == pc) { + bottet = neightet; + mflag = true; + break; + } + } // j + } // if (interflag == 2) + } // pc + } // pb + } // pa + } // toptet.ver + } // i + + if (mflag) { + // Found a pair of matched faces in 'toptet' and 'bottet'. + bond(toptet, bottet); + // Both are interior tets. + infect(toptet); + infect(bottet); + // Add this face into search list. + markface(toptet); + midfaces->newindex((void **) &parytet); + *parytet = toptet; + } else { + // No pair of 'toptet' and 'bottet'. + toptet.tet = NULL; + // Randomly split an interior edge of R. + i = randomnation(missingshs->objects - 1); + recentsh = * (face *) fastlookup(missingshs, i); + } + + // Find other middle faces, connect top and bottom tets. + for (i = 0; i < midfaces->objects && mflag; i++) { + // Get a matched middle face [a, b, c] + midface = (triface *) fastlookup(midfaces, i); + // Check the neighbors at the edges of this face. + for (j = 0; j < 3 && mflag; j++) { + toptet = *midface; + bflag = false; + while (1) { + // Go to the next face in the same tet. + esymself(toptet); + pc = apex(toptet); + if (pmarktested(pc)) { + break; // Find a subface. + } + if (pc == dummypoint) { + terminatetetgen(this, 2); // Check this case. + break; // Find a subface. + } + // Go to the adjacent tet. + fsymself(toptet); + // Do we walk outside the cavity? + if (!marktested(toptet)) { + // Yes, the adjacent face is not a middle face. + bflag = true; break; + } + } + if (!bflag) { + if (!facemarked(toptet)) { + fsym(*midface, bottet); + spintet = bottet; + while (1) { + esymself(bottet); + pd = apex(bottet); + if (pd == pc) break; // Face matched. + fsymself(bottet); + if (bottet.tet == spintet.tet) { + // Not found a matched bottom face. + mflag = false; + break; + } + } // while (1) + if (mflag) { + if (marktested(bottet)) { + // Connect two tets together. + bond(toptet, bottet); + // Both are interior tets. + infect(toptet); + infect(bottet); + // Add this face into list. + markface(toptet); + midfaces->newindex((void **) &parytet); + *parytet = toptet; + } + else { + // The 'bottet' is not inside the cavity! + terminatetetgen(this, 2); // Check this case + } + } else { // mflag == false + // Adjust 'toptet' and 'bottet' to be the crossing edges. + fsym(*midface, bottet); + spintet = bottet; + while (1) { + esymself(bottet); + pd = apex(bottet); + if (pmarktested(pd)) { + // assert(pd != pc); + // Let 'toptet' be [a,b,c,#], and 'bottet' be [b,a,d,*]. + // Adjust 'toptet' and 'bottet' to be the crossing edges. + // Test orient3d(b,c,#,d). + ori = orient3d(dest(toptet), pc, oppo(toptet), pd); + if (ori < 0) { + // Edges [a,d] and [b,c] cross each other. + enextself(toptet); // [b,c] + enextself(bottet); // [a,d] + } else if (ori > 0) { + // Edges [a,c] and [b,d] cross each other. + eprevself(toptet); // [c,a] + eprevself(bottet); // [d,b] + } else { + // b,c,#,and d are coplanar!. + terminatetetgen(this, 2); //assert(0); + } + break; // Not matched + } + fsymself(bottet); + } + } // if (!mflag) + } // if (!facemarked(toptet)) + } // if (!bflag) + enextself(*midface); + } // j + } // i + + if (mflag) { + if (b->verbose > 2) { + printf(" Found %ld middle subfaces.\n", midfaces->objects); + } + face oldsh, newsh, casout, casin, neighsh; + + oldsh = * (face *) fastlookup(missingshs, 0); + + // Create new subfaces to fill the region R. + for (i = 0; i < midfaces->objects; i++) { + // Get a matched middle face [a, b, c] + midface = (triface *) fastlookup(midfaces, i); + unmarkface(*midface); + makeshellface(subfaces, &newsh); + setsorg(newsh, org(*midface)); + setsdest(newsh, dest(*midface)); + setsapex(newsh, apex(*midface)); + // The new subface gets its markers from the old one. + setshellmark(newsh, shellmark(oldsh)); + if (checkconstraints) { + setareabound(newsh, areabound(oldsh)); + } + if (useinsertradius) { + setfacetindex(newsh, getfacetindex(oldsh)); + } + // Connect the new subface to adjacent tets. + tsbond(*midface, newsh); + fsym(*midface, neightet); + sesymself(newsh); + tsbond(neightet, newsh); + } + + // Connect new subfaces together and to the bdry of R. + // Delete faked segments. + for (i = 0; i < midfaces->objects; i++) { + // Get a matched middle face [a, b, c] + midface = (triface *) fastlookup(midfaces, i); + for (j = 0; j < 3; j++) { + tspivot(*midface, newsh); + spivot(newsh, casout); + if (casout.sh == NULL) { + // Search its neighbor. + fnext(*midface, searchtet); + while (1) { + // (1) First check if this side is a bdry edge of R. + tsspivot1(searchtet, checkseg); + if (checkseg.sh != NULL) { + // It's a bdry edge of R. + // Get the old subface. + checkseg.shver = 0; + spivot(checkseg, oldsh); + if (sinfected(checkseg)) { + // It's a faked segment. Delete it. + spintet = searchtet; + while (1) { + tssdissolve1(spintet); + fnextself(spintet); + if (spintet.tet == searchtet.tet) break; + } + shellfacedealloc(subsegs, checkseg.sh); + ssdissolve(oldsh); + checkseg.sh = NULL; + } + spivot(oldsh, casout); + if (casout.sh != NULL) { + casin = casout; + if (checkseg.sh != NULL) { + // Make sure that the subface has the right ori at the + // segment. + checkseg.shver = 0; + if (sorg(newsh) != sorg(checkseg)) { + sesymself(newsh); + } + spivot(casin, neighsh); + while (neighsh.sh != oldsh.sh) { + casin = neighsh; + spivot(casin, neighsh); + } + } + sbond1(newsh, casout); + sbond1(casin, newsh); + } + if (checkseg.sh != NULL) { + ssbond(newsh, checkseg); + } + break; + } // if (checkseg.sh != NULL) + // (2) Second check if this side is an interior edge of R. + tspivot(searchtet, neighsh); + if (neighsh.sh != NULL) { + // Found an adjacent subface of newsh (an interior edge). + sbond(newsh, neighsh); + break; + } + fnextself(searchtet); + } // while (1) + } // if (casout.sh == NULL) + enextself(*midface); + } // j + } // i + + // Delete old subfaces. + for (i = 0; i < missingshs->objects; i++) { + parysh = (face *) fastlookup(missingshs, i); + shellfacedealloc(subfaces, parysh->sh); + } + } else { + if (toptet.tet != NULL) { + // Faces at top and bottom are not matched. + // Choose a Steiner point in R. + // Split one of the crossing edges. + pa = org(toptet); + pb = dest(toptet); + pc = org(bottet); + pd = dest(bottet); + // Search an edge in R which is either [a,b] or [c,d]. + // Reminder: Subfaces in this list 'missingshs', except the first + // one, represents an interior edge of R. + parysh = NULL; // Avoid a warning in MSVC + for (i = 1; i < missingshs->objects; i++) { + parysh = (face *) fastlookup(missingshs, i); + if (((sorg(*parysh) == pa) && (sdest(*parysh) == pb)) || + ((sorg(*parysh) == pb) && (sdest(*parysh) == pa))) break; + if (((sorg(*parysh) == pc) && (sdest(*parysh) == pd)) || + ((sorg(*parysh) == pd) && (sdest(*parysh) == pc))) break; + } + if (i < missingshs->objects) { + // Found. Return it. + recentsh = *parysh; + } else { + terminatetetgen(this, 2); //assert(0); + } + } else { + //terminatetetgen(this, 2); // Report a bug + } + } + + midfaces->restart(); + } else { + mflag = true; + } + + // Delete the temp subfaces. + for (j = 0; j < 2; j++) { + cavshells = (j == 0 ? topshells : botshells); + if (cavshells != NULL) { + for (i = 0; i < cavshells->objects; i++) { + parysh = (face *) fastlookup(cavshells, i); + shellfacedealloc(subfaces, parysh->sh); + } + } + } + + topshells->restart(); + if (botshells != NULL) { + botshells->restart(); + } + + return mflag; +} + +//============================================================================// +// // +// carvecavity() Delete old tets and outer new tets of the cavity. // +// // +//============================================================================// + +void tetgenmesh::carvecavity(arraypool *crosstets, arraypool *topnewtets, + arraypool *botnewtets) +{ + arraypool *newtets; + shellface *sptr, *ssptr; + triface *parytet, *pnewtet, newtet, neightet, spintet; + face checksh, *parysh; + face checkseg, *paryseg; + int t1ver; + int i, j; + + if (b->verbose > 2) { + printf(" Carve cavity: %ld old tets.\n", crosstets->objects); + } + + // First process subfaces and segments which are adjacent to the cavity. + // They must be re-connected to new tets in the cavity. + // Comment: It is possible that some subfaces and segments are completely + // inside the cavity. This can happen even if the cavity is not enlarged. + // Before deleting the old tets, find and queue all interior subfaces + // and segments. They will be recovered later. 2010-05-06. + + // Collect all subfaces and segments which attached to the old tets. + for (i = 0; i < crosstets->objects; i++) { + parytet = (triface *) fastlookup(crosstets, i); + if ((sptr = (shellface*) parytet->tet[9]) != NULL) { + for (j = 0; j < 4; j++) { + if (sptr[j]) { + sdecode(sptr[j], checksh); + if (!sinfected(checksh)) { + sinfect(checksh); + cavetetshlist->newindex((void **) &parysh); + *parysh = checksh; + } + } + } // j + } + if ((ssptr = (shellface*) parytet->tet[8]) != NULL) { + for (j = 0; j < 6; j++) { + if (ssptr[j]) { + sdecode(ssptr[j], checkseg); + // Skip a deleted segment (was a faked segment) + if (checkseg.sh[3] != NULL) { + if (!sinfected(checkseg)) { + sinfect(checkseg); + cavetetseglist->newindex((void **) &paryseg); + *paryseg = checkseg; + } + } + } + } // j + } + } // i + + // Uninfect collected subfaces. + for (i = 0; i < cavetetshlist->objects; i++) { + parysh = (face *) fastlookup(cavetetshlist, i); + suninfect(*parysh); + } + // Uninfect collected segments. + for (i = 0; i < cavetetseglist->objects; i++) { + paryseg = (face *) fastlookup(cavetetseglist, i); + suninfect(*paryseg); + } + + // Connect subfaces to new tets. + for (i = 0; i < cavetetshlist->objects; i++) { + parysh = (face *) fastlookup(cavetetshlist, i); + // Get an adjacent tet at this subface. + stpivot(*parysh, neightet); + // Does this tet lie inside the cavity. + if (infected(neightet)) { + // Yes. Get the other adjacent tet at this subface. + sesymself(*parysh); + stpivot(*parysh, neightet); + // Does this tet lie inside the cavity. + if (infected(neightet)) { + checksh = *parysh; + stdissolve(checksh); + caveencshlist->newindex((void **) &parysh); + *parysh = checksh; + } + } + if (!infected(neightet)) { + // Found an outside tet. Re-connect this subface to a new tet. + fsym(neightet, newtet); + sesymself(*parysh); + tsbond(newtet, *parysh); + } + } // i + + + for (i = 0; i < cavetetseglist->objects; i++) { + checkseg = * (face *) fastlookup(cavetetseglist, i); + // Check if the segment is inside the cavity. + sstpivot1(checkseg, neightet); + spintet = neightet; + while (1) { + if (!infected(spintet)) { + // This segment is on the boundary of the cavity. + break; + } + fnextself(spintet); + if (spintet.tet == neightet.tet) { + sstdissolve1(checkseg); + caveencseglist->newindex((void **) &paryseg); + *paryseg = checkseg; + break; + } + } + if (!infected(spintet)) { + // A boundary segment. Connect this segment to the new tets. + sstbond1(checkseg, spintet); + neightet = spintet; + while (1) { + tssbond1(spintet, checkseg); + fnextself(spintet); + if (spintet.tet == neightet.tet) break; + } + } + } // i + + + cavetetshlist->restart(); + cavetetseglist->restart(); + + // Delete the old tets in cavity. + for (i = 0; i < crosstets->objects; i++) { + parytet = (triface *) fastlookup(crosstets, i); + if (ishulltet(*parytet)) { + hullsize--; + } + tetrahedrondealloc(parytet->tet); + } + + crosstets->restart(); // crosstets will be re-used. + + // Collect new tets in cavity. Some new tets have already been found + // (and infected) in the fillcavity(). We first collect them. + for (j = 0; j < 2; j++) { + newtets = (j == 0 ? topnewtets : botnewtets); + if (newtets != NULL) { + for (i = 0; i < newtets->objects; i++) { + parytet = (triface *) fastlookup(newtets, i); + if (infected(*parytet)) { + crosstets->newindex((void **) &pnewtet); + *pnewtet = *parytet; + } + } // i + } + } // j + + // Now we collect all new tets in cavity. + for (i = 0; i < crosstets->objects; i++) { + parytet = (triface *) fastlookup(crosstets, i); + for (j = 0; j < 4; j++) { + decode(parytet->tet[j], neightet); + if (marktested(neightet)) { // Is it a new tet? + if (!infected(neightet)) { + // Find an interior tet. + //assert((point) neightet.tet[7] != dummypoint); // SELF_CHECK + infect(neightet); + crosstets->newindex((void **) &pnewtet); + *pnewtet = neightet; + } + } + } // j + } // i + + parytet = (triface *) fastlookup(crosstets, 0); + recenttet = *parytet; // Remember a live handle. + + // Delete outer new tets. + for (j = 0; j < 2; j++) { + newtets = (j == 0 ? topnewtets : botnewtets); + if (newtets != NULL) { + for (i = 0; i < newtets->objects; i++) { + parytet = (triface *) fastlookup(newtets, i); + if (infected(*parytet)) { + // This is an interior tet. + uninfect(*parytet); + unmarktest(*parytet); + if (ishulltet(*parytet)) { + hullsize++; + } + } else { + // An outer tet. Delete it. + tetrahedrondealloc(parytet->tet); + } + } + } + } + + crosstets->restart(); + topnewtets->restart(); + if (botnewtets != NULL) { + botnewtets->restart(); + } +} + +//============================================================================// +// // +// restorecavity() Reconnect old tets and delete new tets of the cavity. // +// // +//============================================================================// + +void tetgenmesh::restorecavity(arraypool *crosstets, arraypool *topnewtets, + arraypool *botnewtets, arraypool *missingshbds) +{ + triface *parytet, neightet, spintet; + face *parysh; + face checkseg; + point *ppt; + int t1ver; + int i, j; + + // Reconnect crossing tets to cavity boundary. + for (i = 0; i < crosstets->objects; i++) { + parytet = (triface *) fastlookup(crosstets, i); + parytet->ver = 0; + for (parytet->ver = 0; parytet->ver < 4; parytet->ver++) { + fsym(*parytet, neightet); + if (!infected(neightet)) { + // Restore the old connections of tets. + bond(*parytet, neightet); + } + } + // Update the point-to-tet map. + parytet->ver = 0; + ppt = (point *) &(parytet->tet[4]); + for (j = 0; j < 4; j++) { + setpoint2tet(ppt[j], encode(*parytet)); + } + } + + // Uninfect all crossing tets. + for (i = 0; i < crosstets->objects; i++) { + parytet = (triface *) fastlookup(crosstets, i); + uninfect(*parytet); + } + + // Remember a live handle. + if (crosstets->objects > 0) { + recenttet = * (triface *) fastlookup(crosstets, 0); + } + + // Delete faked segments. + for (i = 0; i < missingshbds->objects; i++) { + parysh = (face *) fastlookup(missingshbds, i); + sspivot(*parysh, checkseg); + if (checkseg.sh[3] != NULL) { + if (sinfected(checkseg)) { + // It's a faked segment. Delete it. + sstpivot1(checkseg, neightet); + spintet = neightet; + while (1) { + tssdissolve1(spintet); + fnextself(spintet); + if (spintet.tet == neightet.tet) break; + } + shellfacedealloc(subsegs, checkseg.sh); + ssdissolve(*parysh); + //checkseg.sh = NULL; + } + } + } // i + + // Delete new tets. + for (i = 0; i < topnewtets->objects; i++) { + parytet = (triface *) fastlookup(topnewtets, i); + tetrahedrondealloc(parytet->tet); + } + + if (botnewtets != NULL) { + for (i = 0; i < botnewtets->objects; i++) { + parytet = (triface *) fastlookup(botnewtets, i); + tetrahedrondealloc(parytet->tet); + } + } + + crosstets->restart(); + topnewtets->restart(); + if (botnewtets != NULL) { + botnewtets->restart(); + } +} + +//============================================================================// +// // +// flipcertify() Insert a crossing face into priority queue. // +// // +// A crossing face of a facet must have at least one top and one bottom ver- // +// tex of the facet. // +// // +//============================================================================// + +void tetgenmesh::flipcertify(triface *chkface,badface **pqueue,point plane_pa, + point plane_pb, point plane_pc) +{ + badface *parybf, *prevbf, *nextbf; + triface neightet; + face checksh; + point p[5]; + REAL w[5]; + REAL insph, ori4; + int topi, boti; + int i; + + // Compute the flip time \tau. + fsym(*chkface, neightet); + + p[0] = org(*chkface); + p[1] = dest(*chkface); + p[2] = apex(*chkface); + p[3] = oppo(*chkface); + p[4] = oppo(neightet); + + // Check if the face is a crossing face. + topi = boti = 0; + for (i = 0; i < 3; i++) { + if (pmarktest2ed(p[i])) topi++; + if (pmarktest3ed(p[i])) boti++; + } + if ((topi == 0) || (boti == 0)) { + // It is not a crossing face. + // return; + for (i = 3; i < 5; i++) { + if (pmarktest2ed(p[i])) topi++; + if (pmarktest3ed(p[i])) boti++; + } + if ((topi == 0) || (boti == 0)) { + // The two tets sharing at this face are on one side of the facet. + // Check if this face is locally Delaunay (due to rounding error). + if ((p[3] != dummypoint) && (p[4] != dummypoint)) { + // Do not check it if it is a subface. + tspivot(*chkface, checksh); + if (checksh.sh == NULL) { + insph = insphere_s(p[1], p[0], p[2], p[3], p[4]); + if (insph > 0) { + // Add the face into queue. + if (b->verbose > 2) { + printf(" A locally non-Delanay face (%d, %d, %d)-%d,%d\n", + pointmark(p[0]), pointmark(p[1]), pointmark(p[2]), + pointmark(p[3]), pointmark(p[4])); + } + parybf = (badface *) flippool->alloc(); + parybf->key = 0.; // tau = 0, do immediately. + parybf->tt = *chkface; + parybf->forg = p[0]; + parybf->fdest = p[1]; + parybf->fapex = p[2]; + parybf->foppo = p[3]; + parybf->noppo = p[4]; + // Add it at the top of the priority queue. + if (*pqueue == NULL) { + *pqueue = parybf; + parybf->nextitem = NULL; + } else { + parybf->nextitem = *pqueue; + *pqueue = parybf; + } + } // if (insph > 0) + } // if (checksh.sh == NULL) + } + } + return; // Test: omit this face. + } + + // Decide the "height" for each point. + for (i = 0; i < 5; i++) { + if (pmarktest2ed(p[i])) { + // A top point has a positive weight. + w[i] = orient3dfast(plane_pa, plane_pb, plane_pc, p[i]); + if (w[i] < 0) w[i] = -w[i]; + } else { + w[i] = 0; + } + } + + insph = insphere(p[1], p[0], p[2], p[3], p[4]); + ori4 = orient4d(p[1], p[0], p[2], p[3], p[4], w[1], w[0], w[2], w[3], w[4]); + if (ori4 > 0) { + // Add the face into queue. + if (b->verbose > 2) { + printf(" Insert face (%d, %d, %d) - %d, %d\n", pointmark(p[0]), + pointmark(p[1]), pointmark(p[2]), pointmark(p[3]), pointmark(p[4])); + } + + parybf = (badface *) flippool->alloc(); + + parybf->key = -insph / ori4; + parybf->tt = *chkface; + parybf->forg = p[0]; + parybf->fdest = p[1]; + parybf->fapex = p[2]; + parybf->foppo = p[3]; + parybf->noppo = p[4]; + + // Push the face into priority queue. + //pq.push(bface); + if (*pqueue == NULL) { + *pqueue = parybf; + parybf->nextitem = NULL; + } else { + // Search an item whose key is larger or equal to current key. + prevbf = NULL; + nextbf = *pqueue; + //if (!b->flipinsert_random) { // Default use a priority queue. + // Insert the item into priority queue. + while (nextbf != NULL) { + if (nextbf->key < parybf->key) { + prevbf = nextbf; + nextbf = nextbf->nextitem; + } else { + break; + } + } + //} // if (!b->flipinsert_random) + // Insert the new item between prev and next items. + if (prevbf == NULL) { + *pqueue = parybf; + } else { + prevbf->nextitem = parybf; + } + parybf->nextitem = nextbf; + } + } else if (ori4 == 0) { + + } +} + +//============================================================================// +// // +// flipinsertfacet() Insert a facet into a CDT by flips. // +// // +// The algorithm is described in Shewchuk's paper "Updating and Constructing // +// Constrained Delaunay and Constrained Regular Triangulations by Flips", in // +// Proc. 19th Ann. Symp. on Comput. Geom., 86--95, 2003. // +// // +// 'crosstets' contains the set of crossing tetrahedra (infected) of the // +// facet. 'toppoints' and 'botpoints' are points lies above and below the // +// facet, not on the facet. // +// // +//============================================================================// + +void tetgenmesh::flipinsertfacet(arraypool *crosstets, arraypool *toppoints, + arraypool *botpoints, arraypool *midpoints) +{ + arraypool *crossfaces, *bfacearray; + triface fliptets[6], baktets[2], fliptet, newface; + triface neightet, *parytet; + badface *pqueue; + badface *popbf, bface; + point plane_pa, plane_pb, plane_pc; + point p1, p2, pd, pe; + point *parypt; + flipconstraints fc; + REAL ori[3]; + int convcount, copcount; + int flipflag, fcount; + int n, i; + long f23count, f32count, f44count; + long totalfcount; + + f23count = flip23count; + f32count = flip32count; + f44count = flip44count; + + // Get three affinely independent vertices in the missing region R. + calculateabovepoint(midpoints, &plane_pa, &plane_pb, &plane_pc); + + // Mark top and bottom points. Do not mark midpoints. + for (i = 0; i < toppoints->objects; i++) { + parypt = (point *) fastlookup(toppoints, i); + if (!pmarktested(*parypt)) { + pmarktest2(*parypt); + } + } + for (i = 0; i < botpoints->objects; i++) { + parypt = (point *) fastlookup(botpoints, i); + if (!pmarktested(*parypt)) { + pmarktest3(*parypt); + } + } + + // Collect crossing faces. + crossfaces = cavetetlist; // Re-use array 'cavetetlist'. + + // Each crossing face contains at least one bottom vertex and + // one top vertex. + for (i = 0; i < crosstets->objects; i++) { + parytet = (triface *) fastlookup(crosstets, i); + fliptet = *parytet; + for (fliptet.ver = 0; fliptet.ver < 4; fliptet.ver++) { + fsym(fliptet, neightet); + if (infected(neightet)) { // It is an interior face. + if (!marktested(neightet)) { // It is an unprocessed face. + crossfaces->newindex((void **) &parytet); + *parytet = fliptet; + } + } + } + marktest(fliptet); + } + + if (b->verbose > 1) { + printf(" Found %ld crossing faces.\n", crossfaces->objects); + } + + for (i = 0; i < crosstets->objects; i++) { + parytet = (triface *) fastlookup(crosstets, i); + unmarktest(*parytet); + uninfect(*parytet); + } + + // Initialize the priority queue. + pqueue = NULL; + + for (i = 0; i < crossfaces->objects; i++) { + parytet = (triface *) fastlookup(crossfaces, i); + flipcertify(parytet, &pqueue, plane_pa, plane_pb, plane_pc); + } + crossfaces->restart(); + + // The list for temporarily storing unflipable faces. + bfacearray = new arraypool(sizeof(triface), 4); + + + fcount = 0; // Count the number of flips. + + // Flip insert the facet. + while (pqueue != NULL) { + + // Pop a face from the priority queue. + popbf = pqueue; + bface = *popbf; + // Update the queue. + pqueue = pqueue->nextitem; + // Delete the popped item from the pool. + flippool->dealloc((void *) popbf); + + if (!isdeadtet(bface.tt)) { + if ((org(bface.tt) == bface.forg) && (dest(bface.tt) == bface.fdest) && + (apex(bface.tt) == bface.fapex) && (oppo(bface.tt) == bface.foppo)) { + // It is still a crossing face of R. + fliptet = bface.tt; + fsym(fliptet, neightet); + if (oppo(neightet) == bface.noppo) { + pd = oppo(fliptet); + pe = oppo(neightet); + + if (b->verbose > 2) { + printf(" Get face (%d, %d, %d) - %d, %d, tau = %.17g\n", + pointmark(bface.forg), pointmark(bface.fdest), + pointmark(bface.fapex), pointmark(bface.foppo), + pointmark(bface.noppo), bface.key); + } + flipflag = 0; + + // Check for which type of flip can we do. + convcount = 3; + copcount = 0; + for (i = 0; i < 3; i++) { + p1 = org(fliptet); + p2 = dest(fliptet); + ori[i] = orient3d(p1, p2, pd, pe); + if (ori[i] < 0) { + convcount--; + //break; + } else if (ori[i] == 0) { + convcount--; // Possible 4-to-4 flip. + copcount++; + //break; + } + enextself(fliptet); + } + + if (convcount == 3) { + // A 2-to-3 flip is found. + fliptets[0] = fliptet; // abcd, d may be the new vertex. + fliptets[1] = neightet; // bace. + flip23(fliptets, 1, &fc); + // Put the link faces into check list. + for (i = 0; i < 3; i++) { + eprevesym(fliptets[i], newface); + crossfaces->newindex((void **) &parytet); + *parytet = newface; + } + for (i = 0; i < 3; i++) { + enextesym(fliptets[i], newface); + crossfaces->newindex((void **) &parytet); + *parytet = newface; + } + flipflag = 1; + } else if (convcount == 2) { + //if (copcount <= 1) { + // A 3-to-2 or 4-to-4 may be possible. + // Get the edge which is locally non-convex or flat. + for (i = 0; i < 3; i++) { + if (ori[i] <= 0) break; + enextself(fliptet); + } + + // Collect tets sharing at this edge. + esym(fliptet, fliptets[0]); // [b,a,d,c] + n = 0; + do { + p1 = apex(fliptets[n]); + if (!(pmarktested(p1) || pmarktest2ed(p1) || pmarktest3ed(p1))) { + // This apex is not on the cavity. Hence the face does not + // lie inside the cavity. Do not flip this edge. + n = 1000; break; + } + fnext(fliptets[n], fliptets[n + 1]); + n++; + } while ((fliptets[n].tet != fliptet.tet) && (n < 5)); + + if (n == 3) { + // Found a 3-to-2 flip. + flip32(fliptets, 1, &fc); + // Put the link faces into check list. + for (i = 0; i < 3; i++) { + esym(fliptets[0], newface); + crossfaces->newindex((void **) &parytet); + *parytet = newface; + enextself(fliptets[0]); + } + for (i = 0; i < 3; i++) { + esym(fliptets[1], newface); + crossfaces->newindex((void **) &parytet); + *parytet = newface; + enextself(fliptets[1]); + } + flipflag = 1; + } else if (n == 4) { + if (copcount == 1) { + // Found a 4-to-4 flip. + // Let the six vertices are: a,b,c,d,e,f, where + // fliptets[0] = [b,a,d,c] + // [1] = [b,a,c,e] + // [2] = [b,a,e,f] + // [3] = [b,a,f,d] + // After the 4-to-4 flip, edge [a,b] is flipped, edge [e,d] + // is created. + // First do a 2-to-3 flip. + // Comment: This flip temporarily creates a degenerated + // tet (whose volume is zero). It will be removed by the + // followed 3-to-2 flip. + fliptets[0] = fliptet; // = [a,b,c,d], d is the new vertex. + // fliptets[1]; // = [b,a,c,e]. + baktets[0] = fliptets[2]; // = [b,a,e,f] + baktets[1] = fliptets[3]; // = [b,a,f,d] + // The flip may involve hull tets. + flip23(fliptets, 1, &fc); + // Put the "outer" link faces into check list. + // fliptets[0] = [e,d,a,b] => will be flipped, so + // [a,b,d] and [a,b,e] are not "outer" link faces. + for (i = 1; i < 3; i++) { + eprevesym(fliptets[i], newface); + crossfaces->newindex((void **) &parytet); + *parytet = newface; + } + for (i = 1; i < 3; i++) { + enextesym(fliptets[i], newface); + crossfaces->newindex((void **) &parytet); + *parytet = newface; + } + // Then do a 3-to-2 flip. + enextesymself(fliptets[0]); // fliptets[0] is [e,d,a,b]. + eprevself(fliptets[0]); // = [b,a,d,c], d is the new vertex. + fliptets[1] = baktets[0]; // = [b,a,e,f] + fliptets[2] = baktets[1]; // = [b,a,f,d] + flip32(fliptets, 1, &fc); + // Put the "outer" link faces into check list. + // fliptets[0] = [d,e,f,a] + // fliptets[1] = [e,d,f,b] + // Faces [a,b,d] and [a,b,e] are not "outer" link faces. + enextself(fliptets[0]); + for (i = 1; i < 3; i++) { + esym(fliptets[0], newface); + crossfaces->newindex((void **) &parytet); + *parytet = newface; + enextself(fliptets[0]); + } + enextself(fliptets[1]); + for (i = 1; i < 3; i++) { + esym(fliptets[1], newface); + crossfaces->newindex((void **) &parytet); + *parytet = newface; + enextself(fliptets[1]); + } + flip23count--; + flip32count--; + flip44count++; + flipflag = 1; + } + } + } else { + // There are more than 1 non-convex or coplanar cases. + flipflag = -1; // Ignore this face. + if (b->verbose > 2) { + printf(" Ignore face (%d, %d, %d) - %d, %d, tau = %.17g\n", + pointmark(bface.forg), pointmark(bface.fdest), + pointmark(bface.fapex), pointmark(bface.foppo), + pointmark(bface.noppo), bface.key); + } + } // if (convcount == 1) + + if (flipflag == 1) { + // Update the priority queue. + for (i = 0; i < crossfaces->objects; i++) { + parytet = (triface *) fastlookup(crossfaces, i); + flipcertify(parytet, &pqueue, plane_pa, plane_pb, plane_pc); + } + crossfaces->restart(); + if (1) { // if (!b->flipinsert_random) { + // Insert all queued unflipped faces. + for (i = 0; i < bfacearray->objects; i++) { + parytet = (triface *) fastlookup(bfacearray, i); + // This face may be changed. + if (!isdeadtet(*parytet)) { + flipcertify(parytet, &pqueue, plane_pa, plane_pb, plane_pc); + } + } + bfacearray->restart(); + } + fcount++; + } else if (flipflag == 0) { + // Queue an unflippable face. To process it later. + bfacearray->newindex((void **) &parytet); + *parytet = fliptet; + } + } // if (pe == bface.noppo) + } // if ((pa == bface.forg) && ...) + } // if (bface.tt != NULL) + + } // while (pqueue != NULL) + + if (bfacearray->objects > 0) { + if (fcount == 0) { + printf("!! No flip is found in %ld faces.\n", bfacearray->objects); + terminatetetgen(this, 2); //assert(0); + } + } + + delete bfacearray; + + // Un-mark top and bottom points. + for (i = 0; i < toppoints->objects; i++) { + parypt = (point *) fastlookup(toppoints, i); + punmarktest2(*parypt); + } + for (i = 0; i < botpoints->objects; i++) { + parypt = (point *) fastlookup(botpoints, i); + punmarktest3(*parypt); + } + + f23count = flip23count - f23count; + f32count = flip32count - f32count; + f44count = flip44count - f44count; + totalfcount = f23count + f32count + f44count; + if (b->verbose > 2) { + printf(" Total %ld flips. f23(%ld), f32(%ld), f44(%ld).\n", + totalfcount, f23count, f32count, f44count); + } +} + +//============================================================================// +// // +// insertpoint_cdt() Insert a new point into a CDT. // +// // +//============================================================================// + +int tetgenmesh::insertpoint_cdt(point newpt, triface *searchtet, face *splitsh, + face *splitseg, insertvertexflags *ivf, + arraypool *cavpoints, arraypool *cavfaces, + arraypool *cavshells, arraypool *newtets, + arraypool *crosstets, arraypool *misfaces) +{ + triface neightet, *parytet; + face checksh, *parysh, *parysh1; + face *paryseg, *paryseg1; + point *parypt; + int t1ver; + int i; + + if (b->verbose > 2) { + printf(" Insert point %d into CDT\n", pointmark(newpt)); + } + + if (!insertpoint(newpt, searchtet, NULL, NULL, ivf)) { + // Point is not inserted. Check ivf->iloc for reason. + return 0; + } + + + for (i = 0; i < cavetetvertlist->objects; i++) { + cavpoints->newindex((void **) &parypt); + *parypt = * (point *) fastlookup(cavetetvertlist, i); + } + // Add the new point into the point list. + cavpoints->newindex((void **) &parypt); + *parypt = newpt; + + for (i = 0; i < cavebdrylist->objects; i++) { + cavfaces->newindex((void **) &parytet); + *parytet = * (triface *) fastlookup(cavebdrylist, i); + } + + for (i = 0; i < caveoldtetlist->objects; i++) { + crosstets->newindex((void **) &parytet); + *parytet = * (triface *) fastlookup(caveoldtetlist, i); + } + + cavetetvertlist->restart(); + cavebdrylist->restart(); + caveoldtetlist->restart(); + + // Insert the point using the cavity algorithm. + delaunizecavity(cavpoints, cavfaces, cavshells, newtets, crosstets, + misfaces); + fillcavity(cavshells, NULL, NULL, NULL, NULL, NULL, NULL); + carvecavity(crosstets, newtets, NULL); + + if ((splitsh != NULL) || (splitseg != NULL)) { + // Insert the point into the surface mesh. + sinsertvertex(newpt, splitsh, splitseg, ivf->sloc, ivf->sbowywat, 0); + + // Put all new subfaces into stack. + for (i = 0; i < caveshbdlist->objects; i++) { + // Get an old subface at edge [a, b]. + parysh = (face *) fastlookup(caveshbdlist, i); + spivot(*parysh, checksh); // The new subface [a, b, p]. + // Do not recover a deleted new face (degenerated). + if (checksh.sh[3] != NULL) { + subfacstack->newindex((void **) &parysh); + *parysh = checksh; + } + } + + if (splitseg != NULL) { + // Queue two new subsegments in C(p) for recovery. + for (i = 0; i < cavesegshlist->objects; i++) { + paryseg = (face *) fastlookup(cavesegshlist, i); + subsegstack->newindex((void **) &paryseg1); + *paryseg1 = *paryseg; + } + } // if (splitseg != NULL) + + // Delete the old subfaces in sC(p). + for (i = 0; i < caveshlist->objects; i++) { + parysh = (face *) fastlookup(caveshlist, i); + if (checksubfaceflag) { + // It is possible that this subface still connects to adjacent + // tets which are not in C(p). If so, clear connections in the + // adjacent tets at this subface. + stpivot(*parysh, neightet); + if (neightet.tet != NULL) { + if (neightet.tet[4] != NULL) { + // Found an adjacent tet. It must be not in C(p). + tsdissolve(neightet); + fsymself(neightet); + tsdissolve(neightet); + } + } + } + shellfacedealloc(subfaces, parysh->sh); + } + if (splitseg != NULL) { + // Delete the old segment in sC(p). + shellfacedealloc(subsegs, splitseg->sh); + } + + // Clear working lists. + caveshlist->restart(); + caveshbdlist->restart(); + cavesegshlist->restart(); + } // if ((splitsh != NULL) || (splitseg != NULL)) + + // Put all interior subfaces into stack for recovery. + // They were collected in carvecavity(). + // Note: Some collected subfaces may be deleted by sinsertvertex(). + for (i = 0; i < caveencshlist->objects; i++) { + parysh = (face *) fastlookup(caveencshlist, i); + if (parysh->sh[3] != NULL) { + subfacstack->newindex((void **) &parysh1); + *parysh1 = *parysh; + } + } + + // Put all interior segments into stack for recovery. + // They were collected in carvecavity(). + // Note: Some collected segments may be deleted by sinsertvertex(). + for (i = 0; i < caveencseglist->objects; i++) { + paryseg = (face *) fastlookup(caveencseglist, i); + if (paryseg->sh[3] != NULL) { + subsegstack->newindex((void **) &paryseg1); + *paryseg1 = *paryseg; + } + } + + caveencshlist->restart(); + caveencseglist->restart(); + + return 1; +} + +//============================================================================// +// // +// refineregion() Refine a missing region by inserting points. // +// // +// 'splitsh' represents an edge of the facet to be split. It must not be a // +// segment. // +// // +// Assumption: The current mesh is a CDT and is convex. // +// // +//============================================================================// + +void tetgenmesh::refineregion(face &splitsh, arraypool *cavpoints, + arraypool *cavfaces, arraypool *cavshells, + arraypool *newtets, arraypool *crosstets, + arraypool *misfaces) +{ + triface searchtet, spintet; + face splitseg, *paryseg; + point steinpt, pa, pb, refpt; + insertvertexflags ivf; + enum interresult dir; + long baknum = points->items; + int t1ver; + int i; + + // Do not split a segment. + for (i = 0; i < 3; i++) { + sspivot(splitsh, splitseg); + if (splitseg.sh == NULL) break; + senextself(splitsh); + } + + if (b->verbose > 2) { + printf(" Refining region at edge (%d, %d, %d).\n", + pointmark(sorg(splitsh)), pointmark(sdest(splitsh)), + pointmark(sapex(splitsh))); + } + + // Add the Steiner point at the barycenter of the face. + pa = sorg(splitsh); + pb = sdest(splitsh); + // Create a new point. + makepoint(&steinpt, FREEFACETVERTEX); + for (i = 0; i < 3; i++) { + steinpt[i] = 0.5 * (pa[i] + pb[i]); + } + + ivf.bowywat = 1; // Use the Bowyer-Watson algorrithm. + ivf.cdtflag = 1; // Only create the initial cavity. + ivf.sloc = (int) ONEDGE; + ivf.sbowywat = 1; + ivf.assignmeshsize = b->metric; + ivf.smlenflag = useinsertradius; // Return the closet mesh vertex. + + point2tetorg(pa, searchtet); // Start location from it. + ivf.iloc = (int) OUTSIDE; + + ivf.rejflag = 1; // Reject it if it encroaches upon any segment. + if (!insertpoint_cdt(steinpt, &searchtet, &splitsh, NULL, &ivf, cavpoints, + cavfaces, cavshells, newtets, crosstets, misfaces)) { + if (ivf.iloc == (int) ENCSEGMENT) { + pointdealloc(steinpt); + // Split an encroached segment. + i = randomnation(encseglist->objects); + paryseg = (face *) fastlookup(encseglist, i); + splitseg = *paryseg; + encseglist->restart(); + + // Split the segment. + pa = sorg(splitseg); + pb = sdest(splitseg); + // Create a new point. + makepoint(&steinpt, FREESEGVERTEX); + for (i = 0; i < 3; i++) { + steinpt[i] = 0.5 * (pa[i] + pb[i]); + } + point2tetorg(pa, searchtet); + ivf.iloc = (int) OUTSIDE; + ivf.rejflag = 0; + if (!insertpoint_cdt(steinpt, &searchtet, &splitsh, &splitseg, &ivf, + cavpoints, cavfaces, cavshells, newtets, + crosstets, misfaces)) { + terminatetetgen(this, 2); + } + if (useinsertradius) { + //save_segmentpoint_insradius(steinpt, ivf.parentpt, ivf.smlen); + } + st_segref_count++; + if (steinerleft > 0) steinerleft--; + } else { + terminatetetgen(this, 2); // assert(0); + } + } else { + if (useinsertradius) { + //save_facetpoint_insradius(steinpt, ivf.parentpt, ivf.smlen); + } + st_facref_count++; + if (steinerleft > 0) steinerleft--; + } + + while (subsegstack->objects > 0l) { + // seglist is used as a stack. + subsegstack->objects--; + paryseg = (face *) fastlookup(subsegstack, subsegstack->objects); + splitseg = *paryseg; + + // Check if this segment has been recovered. + sstpivot1(splitseg, searchtet); + if (searchtet.tet != NULL) continue; + + // Search the segment. + dir = scoutsegment(sorg(splitseg), sdest(splitseg), &splitseg, &searchtet, + &refpt, NULL); + if (dir == SHAREEDGE) { + // Found this segment, insert it. + // Let the segment remember an adjacent tet. + sstbond1(splitseg, searchtet); + // Bond the segment to all tets containing it. + spintet = searchtet; + do { + tssbond1(spintet, splitseg); + fnextself(spintet); + } while (spintet.tet != searchtet.tet); + } else { + if ((dir == ACROSSFACE) || (dir == ACROSSEDGE)) { + // Split the segment. + makepoint(&steinpt, FREESEGVERTEX); + getsteinerptonsegment(&splitseg, refpt, steinpt); + ivf.iloc = (int) OUTSIDE; + ivf.rejflag = 0; + if (!insertpoint_cdt(steinpt, &searchtet, &splitsh, &splitseg, &ivf, + cavpoints, cavfaces, cavshells, newtets, + crosstets, misfaces)) { + terminatetetgen(this, 2); + } + if (useinsertradius) { + //save_segmentpoint_insradius(steinpt, ivf.parentpt, ivf.smlen); + } + st_segref_count++; + if (steinerleft > 0) steinerleft--; + } else { + terminatetetgen(this, 2); + } + } + } // while + + if (b->verbose > 2) { + printf(" Added %ld Steiner points.\n", points->items - baknum); + } +} + +//============================================================================// +// // +// constrainedfacets() Recover constrained facets in a CDT. // +// // +// All unrecovered subfaces are queued in 'subfacestack'. // +// // +//============================================================================// + +void tetgenmesh::constrainedfacets() +{ + arraypool *tg_crosstets, *tg_topnewtets, *tg_botnewtets; + arraypool *tg_topfaces, *tg_botfaces, *tg_midfaces; + arraypool *tg_topshells, *tg_botshells, *tg_facfaces; + arraypool *tg_toppoints, *tg_botpoints; + arraypool *tg_missingshs, *tg_missingshbds, *tg_missingshverts; + triface searchtet, neightet, crossedge; + face searchsh, *parysh, *parysh1; + face *paryseg; + point *parypt; + enum interresult dir; + int facetcount; + int success; + int t1ver; + int i, j; + + // Initialize arrays. + tg_crosstets = new arraypool(sizeof(triface), 10); + tg_topnewtets = new arraypool(sizeof(triface), 10); + tg_botnewtets = new arraypool(sizeof(triface), 10); + tg_topfaces = new arraypool(sizeof(triface), 10); + tg_botfaces = new arraypool(sizeof(triface), 10); + tg_midfaces = new arraypool(sizeof(triface), 10); + tg_toppoints = new arraypool(sizeof(point), 8); + tg_botpoints = new arraypool(sizeof(point), 8); + tg_facfaces = new arraypool(sizeof(face), 10); + tg_topshells = new arraypool(sizeof(face), 10); + tg_botshells = new arraypool(sizeof(face), 10); + tg_missingshs = new arraypool(sizeof(face), 10); + tg_missingshbds = new arraypool(sizeof(face), 10); + tg_missingshverts = new arraypool(sizeof(point), 8); + // This is a global array used by refineregion(). + encseglist = new arraypool(sizeof(face), 4); + + facetcount = 0; + + while (subfacstack->objects > 0l) { + + subfacstack->objects--; + parysh = (face *) fastlookup(subfacstack, subfacstack->objects); + searchsh = *parysh; + + if (searchsh.sh[3] == NULL) continue; // It is dead. + if (isshtet(searchsh)) continue; // It is recovered. + + // Collect all unrecovered subfaces which are co-facet. + smarktest(searchsh); + tg_facfaces->newindex((void **) &parysh); + *parysh = searchsh; + for (i = 0; i < tg_facfaces->objects; i++) { + parysh = (face *) fastlookup(tg_facfaces, i); + for (j = 0; j < 3; j++) { + if (!isshsubseg(*parysh)) { + spivot(*parysh, searchsh); + if (!smarktested(searchsh)) { + if (!isshtet(searchsh)) { + smarktest(searchsh); + tg_facfaces->newindex((void **) &parysh1); + *parysh1 = searchsh; + } + } + } + senextself(*parysh); + } // j + } // i + // Have found all facet subfaces. Unmark them. + for (i = 0; i < tg_facfaces->objects; i++) { + parysh = (face *) fastlookup(tg_facfaces, i); + sunmarktest(*parysh); + } + + + if (b->verbose > 1) { + printf(" Recovering facet #%d: %ld subfaces.\n", facetcount + 1, + tg_facfaces->objects); + } + facetcount++; + + while (tg_facfaces->objects > 0l) { + + tg_facfaces->objects--; + parysh = (face *) fastlookup(tg_facfaces, tg_facfaces->objects); + searchsh = *parysh; + + if (searchsh.sh[3] == NULL) continue; // It is dead. + if (isshtet(searchsh)) continue; // It is recovered. + + searchtet.tet = NULL; + if (scoutsubface(&searchsh, &searchtet, 1)) continue; + + // The subface is missing. Form the missing region. + // Re-use 'tg_crosstets' for 'adjtets'. + formregion(&searchsh, tg_missingshs, tg_missingshbds, tg_missingshverts); + + int searchflag = scoutcrossedge(searchtet, tg_missingshbds, tg_missingshs); + if (searchflag > 0) { + // Save this crossing edge, will be used by fillcavity(). + crossedge = searchtet; + // Form a cavity of crossing tets. + success = formcavity(&searchtet, tg_missingshs, tg_crosstets, + tg_topfaces, tg_botfaces, tg_toppoints, + tg_botpoints); + if (success) { + if (!b->flipinsert) { + // Tetrahedralize the top part. Re-use 'tg_midfaces'. + delaunizecavity(tg_toppoints, tg_topfaces, tg_topshells, + tg_topnewtets, tg_crosstets, tg_midfaces); + // Tetrahedralize the bottom part. Re-use 'tg_midfaces'. + delaunizecavity(tg_botpoints, tg_botfaces, tg_botshells, + tg_botnewtets, tg_crosstets, tg_midfaces); + // Fill the cavity with new tets. + success = fillcavity(tg_topshells, tg_botshells, tg_midfaces, + tg_missingshs, tg_topnewtets, tg_botnewtets, + &crossedge); + if (success) { + // Cavity is remeshed. Delete old tets and outer new tets. + carvecavity(tg_crosstets, tg_topnewtets, tg_botnewtets); + } else { + restorecavity(tg_crosstets, tg_topnewtets, tg_botnewtets, + tg_missingshbds); + } + } else { + // Use the flip algorithm of Shewchuk to recover the subfaces. + flipinsertfacet(tg_crosstets, tg_toppoints, tg_botpoints, + tg_missingshverts); + // Put all subfaces in R back to tg_facfaces. + for (i = 0; i < tg_missingshs->objects; i++) { + parysh = (face *) fastlookup(tg_missingshs, i); + tg_facfaces->newindex((void **) &parysh1); + *parysh1 = *parysh; + } + success = 1; + // Clear working lists. + tg_crosstets->restart(); + tg_topfaces->restart(); + tg_botfaces->restart(); + tg_toppoints->restart(); + tg_botpoints->restart(); + } // b->flipinsert + + if (success) { + // Recover interior subfaces. + for (i = 0; i < caveencshlist->objects; i++) { + parysh = (face *) fastlookup(caveencshlist, i); + if (!scoutsubface(parysh, &searchtet, 1)) { + // Add this face at the end of the list, so it will be + // processed immediately. + tg_facfaces->newindex((void **) &parysh1); + *parysh1 = *parysh; + } + } + caveencshlist->restart(); + // Recover interior segments. This should always be recovered. + for (i = 0; i < caveencseglist->objects; i++) { + paryseg = (face *) fastlookup(caveencseglist, i); + dir = scoutsegment(sorg(*paryseg), sdest(*paryseg), paryseg, + &searchtet, NULL, NULL); + if (dir != SHAREEDGE) { + terminatetetgen(this, 2); + } + // Insert this segment. + // Let the segment remember an adjacent tet. + sstbond1(*paryseg, searchtet); + // Bond the segment to all tets containing it. + neightet = searchtet; + do { + tssbond1(neightet, *paryseg); + fnextself(neightet); + } while (neightet.tet != searchtet.tet); + } + caveencseglist->restart(); + } // success - remesh cavity + } // success - form cavity + else { + terminatetetgen(this, 2); // Report a bug. + } // Not success - form cavity + } else { + // Put all subfaces in R back to tg_facfaces. + for (i = 0; i < tg_missingshs->objects; i++) { + parysh = (face *) fastlookup(tg_missingshs, i); + tg_facfaces->newindex((void **) &parysh1); + *parysh1 = *parysh; + } + if (searchflag != -1) { + // Some edge(s) in the missing regions were flipped. + success = 1; + } else { + restorecavity(tg_crosstets, tg_topnewtets, tg_botnewtets, + tg_missingshbds); // Only remove fake segments. + // Choose an edge to split (set in recentsh) + recentsh = searchsh; + success = 0; // Do refineregion(); + } + } // if (scoutcrossedge) + + // Unmarktest all points of the missing region. + for (i = 0; i < tg_missingshverts->objects; i++) { + parypt = (point *) fastlookup(tg_missingshverts, i); + punmarktest(*parypt); + } + tg_missingshverts->restart(); + tg_missingshbds->restart(); + tg_missingshs->restart(); + + if (!success) { + // The missing region can not be recovered. Refine it. + refineregion(recentsh, tg_toppoints, tg_topfaces, tg_topshells, + tg_topnewtets, tg_crosstets, tg_midfaces); + } + } // while (tg_facfaces->objects) + + } // while ((subfacstack->objects) + + // Accumulate the dynamic memory. + totalworkmemory += (tg_crosstets->totalmemory + tg_topnewtets->totalmemory + + tg_botnewtets->totalmemory + tg_topfaces->totalmemory + + tg_botfaces->totalmemory + tg_midfaces->totalmemory + + tg_toppoints->totalmemory + tg_botpoints->totalmemory + + tg_facfaces->totalmemory + tg_topshells->totalmemory + + tg_botshells->totalmemory + tg_missingshs->totalmemory + + tg_missingshbds->totalmemory + + tg_missingshverts->totalmemory + + encseglist->totalmemory); + + // Delete arrays. + delete tg_crosstets; + delete tg_topnewtets; + delete tg_botnewtets; + delete tg_topfaces; + delete tg_botfaces; + delete tg_midfaces; + delete tg_toppoints; + delete tg_botpoints; + delete tg_facfaces; + delete tg_topshells; + delete tg_botshells; + delete tg_missingshs; + delete tg_missingshbds; + delete tg_missingshverts; + delete encseglist; + encseglist = NULL; +} + +//============================================================================// +// // +// constraineddelaunay() Create a constrained Delaunay tetrahedralization. // +// // +//============================================================================// + +void tetgenmesh::constraineddelaunay(clock_t& tv) +{ + face searchsh, *parysh; + face searchseg, *paryseg; + int s, i; + + // Statistics. + long bakfillregioncount; + long bakcavitycount, bakcavityexpcount; + long bakseg_ref_count; + + if (!b->quiet) { + printf("Constrained Delaunay...\n"); + } + + makesegmentendpointsmap(); + makefacetverticesmap(); + + if (b->verbose) { + printf(" Delaunizing segments.\n"); + } + + checksubsegflag = 1; + + // Put all segments into the list (in random order). + subsegs->traversalinit(); + for (i = 0; i < subsegs->items; i++) { + s = randomnation(i + 1); + // Move the s-th seg to the i-th. + subsegstack->newindex((void **) &paryseg); + *paryseg = * (face *) fastlookup(subsegstack, s); + // Put i-th seg to be the s-th. + searchseg.sh = shellfacetraverse(subsegs); + //sinfect(searchseg); // Only save it once. + paryseg = (face *) fastlookup(subsegstack, s); + *paryseg = searchseg; + } + + // Recover non-Delaunay segments. + delaunizesegments(); + + if (b->verbose) { + printf(" Inserted %ld Steiner points.\n", st_segref_count); + } + + tv = clock(); + + if (b->verbose) { + printf(" Constraining facets.\n"); + } + + // Subfaces will be introduced. + checksubfaceflag = 1; + + bakfillregioncount = fillregioncount; + bakcavitycount = cavitycount; + bakcavityexpcount = cavityexpcount; + bakseg_ref_count = st_segref_count; + + // Randomly order the subfaces. + subfaces->traversalinit(); + for (i = 0; i < subfaces->items; i++) { + s = randomnation(i + 1); + // Move the s-th subface to the i-th. + subfacstack->newindex((void **) &parysh); + *parysh = * (face *) fastlookup(subfacstack, s); + // Put i-th subface to be the s-th. + searchsh.sh = shellfacetraverse(subfaces); + parysh = (face *) fastlookup(subfacstack, s); + *parysh = searchsh; + } + + // Recover facets. + constrainedfacets(); + + if (b->verbose) { + if (fillregioncount > bakfillregioncount) { + printf(" Remeshed %ld regions.\n", fillregioncount-bakfillregioncount); + } + if (cavitycount > bakcavitycount) { + printf(" Remeshed %ld cavities", cavitycount - bakcavitycount); + if (cavityexpcount - bakcavityexpcount) { + printf(" (%ld enlarged)", cavityexpcount - bakcavityexpcount); + } + printf(".\n"); + } + if (st_segref_count + st_facref_count - bakseg_ref_count > 0) { + printf(" Inserted %ld (%ld, %ld) refine points.\n", + st_segref_count + st_facref_count - bakseg_ref_count, + st_segref_count - bakseg_ref_count, st_facref_count); + } + } +} + +// // +// // +//== constrained_cxx =========================================================// + +//== steiner_cxx =============================================================// +// // +// // + +void tetgenmesh::sort_2pts(point p1, point p2, point ppt[2]) +{ + if (pointmark(p1) < pointmark(p2)) { + ppt[0] = p1; + ppt[1] = p2; + } else { + ppt[0] = p2; + ppt[1] = p1; + } +} + +void tetgenmesh::sort_3pts(point p1, point p2, point p3, point ppt[3]) +{ + int i1 = pointmark(p1); + int i2 = pointmark(p2); + int i3 = pointmark(p3); + + if (i1 < i2) { + if (i1 < i3) { + ppt[0] = p1; + if (i2 < i3) { + ppt[1] = p2; + ppt[2] = p3; + } else { + ppt[1] = p3; + ppt[2] = p2; + } + } else { + ppt[0] = p3; + ppt[1] = p1; + ppt[2] = p2; + } + } else { // i1 > i2 + if (i2 < i3) { + ppt[0] = p2; + if (i1 < i3) { + ppt[1] = p1; + ppt[2] = p3; + } else { + ppt[1] = p3; + ppt[2] = p1; + } + } else { + ppt[0] = p3; + ppt[1] = p2; + ppt[2] = p1; + } + } +} + + +//============================================================================// +// // +// is_collinear_at() Check if three vertices (from left to right): left, // +// mid, and right are collinear. // +// // +//============================================================================// + +bool tetgenmesh::is_collinear_at(point mid, point left, point right) +{ + REAL v1[3], v2[3]; + + v1[0] = left[0] - mid[0]; + v1[1] = left[1] - mid[1]; + v1[2] = left[2] - mid[2]; + + v2[0] = right[0] - mid[0]; + v2[1] = right[1] - mid[1]; + v2[2] = right[2] - mid[2]; + + REAL L1 = sqrt(v1[0]*v1[0]+v1[1]*v1[1]+v1[2]*v1[2]); + REAL L2 = sqrt(v2[0]*v2[0]+v2[1]*v2[1]+v2[2]*v2[2]); + REAL D = (v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2]); + + REAL cos_ang = D / (L1 * L2); + return cos_ang < cos_collinear_ang_tol; +} + +//============================================================================// +// // +// is_segment() Check if the two vertices are endpoints of a segment. // +// // +//============================================================================// + +bool tetgenmesh::is_segment(point p1, point p2) +{ + if (pointtype(p1) == RIDGEVERTEX) { + if (pointtype(p2) == RIDGEVERTEX) { + // Check if p2 is connect to p1. + int idx = pointmark(p1); + for (int i = idx_segment_ridge_vertex_list[idx]; + i < idx_segment_ridge_vertex_list[idx+1]; i++) { + if (segment_ridge_vertex_list[i] == p2) { + return true; + } + } + } else if (pointtype(p2) == FREESEGVERTEX) { + // Check if the segment contains p2 has one if its endpoints be p1. + face parsentseg; + sdecode(point2sh(p2), parsentseg); + int segidx = getfacetindex(parsentseg); + if ((segmentendpointslist[segidx*2] == p1) || + (segmentendpointslist[segidx*2+1] == p1)) { + return true; + } + } + } else { + if (pointtype(p1) == FREESEGVERTEX) { + if (pointtype(p2) == RIDGEVERTEX) { + face parsentseg; + sdecode(point2sh(p1), parsentseg); + int segidx = getfacetindex(parsentseg); + if ((segmentendpointslist[segidx*2] == p2) || + (segmentendpointslist[segidx*2+1] == p2)) { + return true; + } + } else if (pointtype(p2) == FREESEGVERTEX) { + face parsentseg1, parsentseg2; + sdecode(point2sh(p1), parsentseg1); + sdecode(point2sh(p2), parsentseg2); + int segidx1 = getfacetindex(parsentseg1); + int segidx2 = getfacetindex(parsentseg2); + if (segidx1 == segidx2) { + return true; + } + } + } + } + + return false; +} + +//============================================================================// +// // +// valid_constrained_f23() Validate a 2-3 flip. // +// // +// The purpose of the following check is to avoid creating a degenrated face // +// (and subface) whose three vertices are nearly on one segment or on two // +// nearly collinear segments. // +// // +// "checktet" is a face (a,b,c) which is 2-3 flippable, and (d,e) will be // +// the new edge after this flip. // +// // +// return true if this 2-3 flip is good, otherwise, return false. // +// // +//============================================================================// + +bool tetgenmesh::valid_constrained_f23(triface& checktet, point pd, point pe) +{ + bool validflag = true; + + triface spintet; + face checkseg1, checkseg2; + point checkpt; + + for (int k = 0; k < 3; k++) { + checkpt = org(checktet); + esym(checktet, spintet); + enextself(spintet); // [x, d], x = a,b,c + tsspivot1(spintet, checkseg1); + bool isseg = (checkseg1.sh != NULL); + if (!isseg && boundary_recovery_flag) { + isseg = is_segment(checkpt, pd); + } + if (isseg) { + fsym(checktet, spintet); + esymself(spintet); + eprevself(spintet); + tsspivot1(spintet, checkseg2); + isseg = (checkseg2.sh != NULL); + if (!isseg && boundary_recovery_flag) { + isseg = is_segment(checkpt, pe); + } + if (isseg) { + if (pointtype(checkpt) == FREESEGVERTEX) { + // In this case, the two subsegments (checkseg1, checkseg2) + // must belong to the same segment, do not flip. + validflag = false; + break; + } else { + // Check if three vertices are nearly collinear. The middle + // vertex is checkpt. + if ((checkpt != dummypoint) && + (pe != dummypoint) && + (pd != dummypoint)) { + if (is_collinear_at(checkpt, pe, pd)) { + validflag = false; + break; + } + } + } + } // if (isseg) + } // if (isseg) + enextself(checktet); + } // k + + return validflag; +} + +//============================================================================// +// // +// valid_constrained_f32() Validate a 3-2 flip. // +// // +// Avoid creating a degenerated tetrahedral face whose three vertices are on // +// one (sub)segment. abtets[0], abdtets[1], abtets[2] are three tets // +// at the flipping edge (a,b), the new face will be (c, d, e). // +// The only new face we will create is (c,d,e), make sure that it is not // +// a (nearly) degenerated face. If the vertex c is RIDGEVEETEX or // +// FREESEGVERTEX, then the edges (c, d) and (c, e) should not on one segment.// +// The same for the vertex d and e. // +// // +// return true if this 3-2 flip is good, otherwise, return false. // +// // +//============================================================================// + +bool tetgenmesh::valid_constrained_f32(triface* abtets, point pa, point pb) +{ + bool validflag = true; // default. + + triface spintet; + face checksegs[3]; // edges: [c,d], [d,e], and [e,c] + point chkpt, leftpt, rightpt; + + // Check edges [c,d], [d,e], and [e,c] + for (int k = 0; k < 3; k++) { // [a,b,c], [a,b,d], [a,b,e] + enext(abtets[k], spintet); + esymself(spintet); + eprevself(spintet); // [c,d], [d,e], and [e,c] + tsspivot1(spintet, checksegs[k]); + // Ignore a temporaray segment (used in recoversubfaces()). + if (checksegs[k].sh != NULL) { + if (smarktest2ed(checksegs[k])) { + checksegs[k].sh = NULL; + } + } + } // k + + for (int k = 0; k < 3; k++) { + chkpt = apex(abtets[k]); // pc + leftpt = apex(abtets[(k+2)%3]); // pe + rightpt = apex(abtets[(k+1)%3]); // pd + bool isseg = (checksegs[k].sh != NULL); // [c,d] + if (!isseg && boundary_recovery_flag) { + isseg = is_segment(chkpt, rightpt); + } + if (isseg) { + isseg = (checksegs[(k+2)%3].sh != NULL); // [e,c] + if (!isseg && boundary_recovery_flag) { + isseg = is_segment(chkpt, leftpt); + } + if (isseg) { + if (pointtype(chkpt) == FREESEGVERTEX) { + validflag = false; + break; + } else { + if ((chkpt != dummypoint) && + (leftpt != dummypoint) && + (rightpt != dummypoint)) { + if (is_collinear_at(chkpt, leftpt, rightpt)) { + validflag = false; + break; + } + } + } + } + } + } // k + + return validflag; +} + +//============================================================================// +// // +// checkflipeligibility() A call back function for boundary recovery. // +// // +// 'fliptype' indicates which elementary flip will be performed: 1 : 2-to-3, // +// and 2 : 3-to-2, respectively. // +// // +// 'pa, ..., pe' are the vertices involved in this flip, where [a,b,c] is // +// the flip face, and [d,e] is the flip edge. NOTE: 'pc' may be 'dummypoint', // +// other points must not be 'dummypoint'. // +// // +//============================================================================// + +int tetgenmesh::checkflipeligibility(int fliptype, point pa, point pb, + point pc, point pd, point pe, + int level, int edgepivot, + flipconstraints* fc) +{ + point tmppts[3]; + enum interresult dir; + int types[2], poss[4]; + int intflag; + int rejflag = 0; + int i; + + if (fc->seg[0] != NULL) { + // A constraining edge is given (e.g., for edge recovery). + if (fliptype == 1) { + // A 2-to-3 flip: [a,b,c] => [e,d,a], [e,d,b], [e,d,c]. + tmppts[0] = pa; + tmppts[1] = pb; + tmppts[2] = pc; + for (i = 0; i < 3 && !rejflag; i++) { + if (tmppts[i] != dummypoint) { + // Test if the face [e,d,#] intersects the edge. + intflag = tri_edge_test(pe, pd, tmppts[i], fc->seg[0], fc->seg[1], + NULL, 1, types, poss); + if (intflag == 2) { + // They intersect at a single point. + dir = (enum interresult) types[0]; + if (dir == ACROSSFACE) { + // The interior of [e,d,#] intersect the segment. + rejflag = 1; + } else if (dir == ACROSSEDGE) { + if (poss[0] == 0) { + // The interior of [e,d] intersect the segment. + // Since [e,d] is the newly created edge. Reject this flip. + rejflag = 1; + } + } + else { + if ((dir == ACROSSVERT) || (dir == TOUCHEDGE) || + (dir == TOUCHFACE)) { + // should be a self-intersection. + rejflag = 1; + } + } // dir + } else if (intflag == 4) { + // They may intersect at either a point or a line segment. + dir = (enum interresult) types[0]; + if (dir == ACROSSEDGE) { + if (poss[0] == 0) { + // The interior of [e,d] intersect the segment. + // Since [e,d] is the newly created edge. Reject this flip. + rejflag = 1; + } + } + else if (dir == ACROSSFACE) { + //assert(0); // This should be not possible. + terminatetetgen(this, 2); + } + else { + if ((dir == ACROSSVERT) || (dir == TOUCHEDGE) || + (dir == TOUCHFACE)) { + // This should be caused by a self-intersection. + rejflag = 1; // Do not flip. + } + } + } + } // if (tmppts[0] != dummypoint) + } // i + } else if (fliptype == 2) { + // A 3-to-2 flip: [e,d,a], [e,d,b], [e,d,c] => [a,b,c] + if (pc != dummypoint) { + // Check if the new face [a,b,c] intersect the edge in its interior. + intflag = tri_edge_test(pa, pb, pc, fc->seg[0], fc->seg[1], NULL, + 1, types, poss); + if (intflag == 2) { + // They intersect at a single point. + dir = (enum interresult) types[0]; + if (dir == ACROSSFACE) { + // The interior of [a,b,c] intersect the segment. + rejflag = 1; // Do not flip. + } + } else if (intflag == 4) { + // [a,b,c] is coplanar with the edge. + dir = (enum interresult) types[0]; + if (dir == ACROSSEDGE) { + // The boundary of [a,b,c] intersect the segment. + rejflag = 1; // Do not flip. + } + } + } // if (pc != dummypoint) + } + } // if (fc->seg[0] != NULL) + + if ((fc->fac[0] != NULL) && !rejflag) { + // A constraining face is given (e.g., for face recovery). + if (fliptype == 1) { + // A 2-to-3 flip. + // Test if the new edge [e,d] intersects the face. + intflag = tri_edge_test(fc->fac[0], fc->fac[1], fc->fac[2], pe, pd, + NULL, 1, types, poss); + if (intflag == 2) { + // They intersect at a single point. + dir = (enum interresult) types[0]; + if (dir == ACROSSFACE) { + rejflag = 1; + } else if (dir == ACROSSEDGE) { + rejflag = 1; + } + } else if (intflag == 4) { + // The edge [e,d] is coplanar with the face. + // There may be two intersections. + for (i = 0; i < 2 && !rejflag; i++) { + dir = (enum interresult) types[i]; + if (dir == ACROSSFACE) { + rejflag = 1; + } else if (dir == ACROSSEDGE) { + rejflag = 1; + } + } + } + } // if (fliptype == 1) + } // if (fc->fac[0] != NULL) + + if ((fc->remvert != NULL) && !rejflag) { + // The vertex is going to be removed. Do not create a new edge which + // contains this vertex. + if (fliptype == 1) { + // A 2-to-3 flip. + if ((pd == fc->remvert) || (pe == fc->remvert)) { + rejflag = 1; + } + } + } + + if (fc->remove_large_angle && !rejflag) { + // Remove a large dihedral angle. Do not create a new small angle. + badface bf; // used by get_tetqual(...) + REAL cosmaxd = 0, diff; + if (fliptype == 1) { + // We assume that neither 'a' nor 'b' is dummypoint. + // A 2-to-3 flip: [a,b,c] => [e,d,a], [e,d,b], [e,d,c]. + // The new tet [e,d,a,b] will be flipped later. Only two new tets: + // [e,d,b,c] and [e,d,c,a] need to be checked. + if ((pc != dummypoint) && (pe != dummypoint) && (pd != dummypoint)) { + REAL min_cosmaxd = 1.0, max_asp = 0; // record the worst quality. + // Get the largest dihedral angle of [e,d,b,c]. + if (get_tetqual(pe, pd, pb, pc, &bf)) { + cosmaxd = bf.cent[0]; + diff = cosmaxd - fc->cosdihed_in; + if (fabs(diff/fc->cosdihed_in) < b->epsilon) diff = 0.0; // Rounding. + } else { + diff = 0.0; // no improve. + } + if (diff <= 0) { //if (cosmaxd <= fc->cosdihed_in) { + rejflag = 1; + } else { + // Record the largest new angle. + min_cosmaxd = (min_cosmaxd < cosmaxd ? min_cosmaxd : cosmaxd); + max_asp = (max_asp > bf.key ? max_asp : bf.key); + // Get the largest dihedral angle of [e,d,c,a]. + if (get_tetqual(pe, pd, pc, pa, &bf)) { + cosmaxd = bf.cent[0]; + diff = cosmaxd - fc->cosdihed_in; + if (fabs(diff/fc->cosdihed_in) < b->epsilon) diff = 0.0; // Rounding. + } else { + diff = 0.0; // no improve. + } + if (diff <= 0) { //if (cosmaxd <= fc->cosdihed_in) { + rejflag = 1; + } else { + // Record the largest new angle. + min_cosmaxd = (min_cosmaxd < cosmaxd ? min_cosmaxd : cosmaxd); + max_asp = (max_asp > bf.key ? max_asp : bf.key); + // save the worst quality. + fc->cosdihed_out = (fc->cosdihed_out < min_cosmaxd ? fc->cosdihed_out : min_cosmaxd); + fc->max_asp_out = (fc->max_asp_out > max_asp ? fc->max_asp_out : max_asp); + } + } + } // if (pc != dummypoint && ...) + } else if (fliptype == 2) { + // A 3-to-2 flip: [e,d,a], [e,d,b], [e,d,c] => [a,b,c] + // We assume that neither 'e' nor 'd' is dummypoint. + if (level == 0) { + // Both new tets [a,b,c,d] and [b,a,c,e] are new tets. + if ((pa != dummypoint) && (pb != dummypoint) && (pc != dummypoint)) { + REAL min_cosmaxd = 1.0, max_asp = 0; // record the worst quality. + // Get the largest dihedral angle of [a,b,c,d]. + if (get_tetqual(pa, pb, pc, pd, &bf)) { + cosmaxd = bf.cent[0]; + diff = cosmaxd - fc->cosdihed_in; + if (fabs(diff/fc->cosdihed_in) < b->epsilon) diff = 0.0; // Rounding. + } else { + diff = 0.0; // no improve. + } + if (diff <= 0) { //if (cosmaxd <= fc->cosdihed_in) { + rejflag = 1; + } else { + // Record the largest new angle. + min_cosmaxd = (min_cosmaxd < cosmaxd ? min_cosmaxd : cosmaxd); + max_asp = (max_asp > bf.key ? max_asp : bf.key); + // Get the largest dihedral angle of [b,a,c,e]. + if (get_tetqual(pb, pa, pc, pe, &bf)) { + cosmaxd = bf.cent[0]; + diff = cosmaxd - fc->cosdihed_in; + if (fabs(diff/fc->cosdihed_in) < b->epsilon) diff = 0.0; // Rounding. + } else { + diff = 0.0; // no improve. + } + if (diff <= 0) { //if (cosmaxd <= fc->cosdihed_in) { + rejflag = 1; + } else { + // Record the largest new angle. + min_cosmaxd = (min_cosmaxd < cosmaxd ? min_cosmaxd : cosmaxd); + max_asp = (max_asp > bf.key ? max_asp : bf.key); + // save the worst quality. + fc->cosdihed_out = (fc->cosdihed_out < min_cosmaxd ? fc->cosdihed_out : min_cosmaxd); + fc->max_asp_out = (fc->max_asp_out > max_asp ? fc->max_asp_out : max_asp); + } + } + } + } else { // level > 0 + if (edgepivot == 1) { + // The new tet [a,b,c,d] will be flipped. Only check [b,a,c,e]. + if ((pa != dummypoint) && (pb != dummypoint) && (pc != dummypoint)) { + // Get the largest dihedral angle of [b,a,c,e]. + if (get_tetqual(pb, pa, pc, pe, &bf)) { + cosmaxd = bf.cent[0]; + diff = cosmaxd - fc->cosdihed_in; + if (fabs(diff/fc->cosdihed_in) < b->epsilon) diff = 0.0; // Rounding. + } else { + diff = 0.0; // no improve. + } + if (diff <= 0) { //if (cosmaxd <= fc->cosdihed_in) { + rejflag = 1; + } else { + // Record the largest new angle. + // save the worst quality. + fc->cosdihed_out = (fc->cosdihed_out < cosmaxd ? fc->cosdihed_out : cosmaxd); + fc->max_asp_out = (fc->max_asp_out > bf.key ? fc->max_asp_out : bf.key); + } + } + } else { + // The new tet [b,a,c,e] will be flipped. Only check [a,b,c,d]. + if ((pa != dummypoint) && (pb != dummypoint) && (pc != dummypoint)) { + // Get the largest dihedral angle of [b,a,c,e]. + if (get_tetqual(pa, pb, pc, pd, &bf)) { + cosmaxd = bf.cent[0]; + diff = cosmaxd - fc->cosdihed_in; + if (fabs(diff/fc->cosdihed_in) < b->epsilon) diff = 0.0; // Rounding. + } else { + diff = 0.0; // no improve. + } + if (diff <= 0) { //if (cosmaxd <= fc->cosdihed_in) { + rejflag = 1; + } else { + // Record the largest new angle. + // save the worst quality. + fc->cosdihed_out = (fc->cosdihed_out < cosmaxd ? fc->cosdihed_out : cosmaxd); + fc->max_asp_out = (fc->max_asp_out > bf.key ? fc->max_asp_out : bf.key); + } + } + } // edgepivot + } // level + } + } // if (fc->remove_large_angle && !rejflag) + + return rejflag; +} + +//============================================================================// +// // +// removeedgebyflips() Attempt to remove an edge by flips. // +// // +// 'flipedge' is a non-convex or flat edge [a,b,#,#] to be removed. // +// // +// The return value is a positive integer, it indicates whether the edge is // +// removed or not. A value "2" means the edge is removed, otherwise, the // +// edge is not removed and the value (must >= 3) is the current number of // +// tets in the edge star. // +// // +//============================================================================// + +int tetgenmesh::removeedgebyflips(triface *flipedge, flipconstraints* fc) +{ + triface *abtets, spintet; + int t1ver; + int n, nn, i; + + + if (checksubsegflag) { + // Do not flip a segment. + if (issubseg(*flipedge)) { + if (fc->collectencsegflag) { + face checkseg, *paryseg; + tsspivot1(*flipedge, checkseg); + if (!sinfected(checkseg)) { + // Queue this segment in list. + sinfect(checkseg); + caveencseglist->newindex((void **) &paryseg); + *paryseg = checkseg; + } + } + return 0; + } + } + + // Count the number of tets at edge [a,b]. + int subface_count = 0; // count the # of subfaces at this edge. + n = 0; + spintet = *flipedge; + while (1) { + if (issubface(spintet)) subface_count++; + n++; + fnextself(spintet); + if (spintet.tet == flipedge->tet) break; + } + if (n < 3) { + // It is only possible when the mesh contains inverted tetrahedra. + terminatetetgen(this, 2); // Report a bug + } + + if (fc->noflip_in_surface) { + if (subface_count > 0) { + return 0; + } + } + + //if ((b->flipstarsize > 0) && (n > (b->flipstarsize+4))) { + if ((b->flipstarsize > 0) && (n > b->flipstarsize)) { + // The star size exceeds the limit. + return 0; // Do not flip it. + } + + // Allocate spaces. + abtets = new triface[n]; + // Collect the tets at edge [a,b]. + spintet = *flipedge; + for (i = 0; i < n; i++) { + abtets[i] = spintet; + setelemcounter(abtets[i], 1); + fnextself(spintet); + } + + + // Try to flip the edge (level = 0, edgepivot = 0). + nn = flipnm(abtets, n, 0, 0, fc); + + + if (nn > 2) { + // Edge is not flipped. Unmarktest the remaining tets in Star(ab). + for (i = 0; i < nn; i++) { + setelemcounter(abtets[i], 0); + } + // Restore the input edge (needed by Lawson's flip). + *flipedge = abtets[0]; + } + + // Release the temporary allocated spaces. + // NOTE: fc->unflip must be 0. + int bakunflip = fc->unflip; + fc->unflip = 0; + flipnm_post(abtets, n, nn, 0, fc); + fc->unflip = bakunflip; + + delete [] abtets; + + return nn; +} + +//============================================================================// +// // +// removefacebyflips() Remove a face by flips. // +// // +// Return 1 if the face is removed. Otherwise, return 0. // +// // +// ASSUMPTION: 'flipface' must not be a subface or a hull face. // +// // +//============================================================================// + +int tetgenmesh::removefacebyflips(triface *flipface, flipconstraints* fc) +{ + triface fliptets[3], flipedge; + point pa, pb, pc, pd, pe; + REAL ori; + int reducflag = 0; + + fliptets[0] = *flipface; + fsym(*flipface, fliptets[1]); + pa = org(fliptets[0]); + pb = dest(fliptets[0]); + pc = apex(fliptets[0]); + pd = oppo(fliptets[0]); + pe = oppo(fliptets[1]); + + ori = orient3d(pa, pb, pd, pe); + if (ori > 0) { + ori = orient3d(pb, pc, pd, pe); + if (ori > 0) { + ori = orient3d(pc, pa, pd, pe); + if (ori > 0) { + // Found a 2-to-3 flip. + reducflag = 1; + } else { + eprev(*flipface, flipedge); // [c,a] + } + } else { + enext(*flipface, flipedge); // [b,c] + } + } else { + flipedge = *flipface; // [a,b] + } + + if (reducflag) { + triface checkface = fliptets[0]; + if (!valid_constrained_f23(checkface, pd, pe)) { + return 0; //reducflag = 0; + } + } + + if (reducflag) { + // A 2-to-3 flip is found. + flip23(fliptets, 0, fc); + return 1; + } else { + // Try to flip the selected edge of this face. + if (removeedgebyflips(&flipedge, fc) == 2) { + if (b->verbose > 3) { + printf(" Face is removed by removing an edge.\n"); + } + return 1; + } + } + + // Face is not removed. + return 0; +} + +//============================================================================// +// // +// recoveredgebyflips() Recover an edge in current tetrahedralization. // +// // +// If the edge is recovered, 'searchtet' returns a tet containing the edge. // +// // +// If the parameter 'fullsearch' is set, it tries to flip any face or edge // +// that intersects the recovering edge. Otherwise, only the face or edge // +// which is visible by 'startpt' is tried. // +// // +// The parameter 'sedge' is used to report self-intersection. If it is not // +// a NULL, it is EITHER a segment OR a subface that contains this edge. // +// // +// This routine assumes that the tetrahedralization is convex. // +// // +//============================================================================// + +int tetgenmesh::recoveredgebyflips(point startpt, point endpt, face *sedge, + triface* searchtet, int fullsearch, int& idir) +{ + flipconstraints fc; + enum interresult dir; + + idir = (int) DISJOINT; // init. + + fc.seg[0] = startpt; + fc.seg[1] = endpt; + fc.checkflipeligibility = 1; + + // The mainloop of the edge reocvery. + while (1) { // Loop I + + // Search the edge from 'startpt'. + point2tetorg(startpt, *searchtet); + dir = finddirection(searchtet, endpt); + + if (dir == ACROSSVERT) { + if (dest(*searchtet) == endpt) { + return 1; // Edge is recovered. + } else { + if (sedge != NULL) { + // It is a segment or a subedge (an edge of a facet). + // Check and report if there exists a self-intersection. + insertvertexflags ivf; + bool intersect_flag = false; + point nearpt = dest(*searchtet); + ivf.iloc = ONVERTEX; + + if (sedge->sh[5] == NULL) { + // It is a segment. + if (!issteinerpoint(nearpt)) { + // It is an input point. + if (!b->quiet && !b->nowarning) { + int segidx = getfacetindex(*sedge); + point p1 = segmentendpointslist[segidx*2]; + point p2 = segmentendpointslist[segidx*2+1]; + point tmppt = NULL; + if (is_segment(p1, nearpt)) tmppt = p1; + else if (is_segment(p2, nearpt)) tmppt = p2; + if (tmppt != NULL) { + printf("Warning: Two line segments are %s overlapping.\n", + ivf.iloc == NEARVERTEX ? "nearly" : "exactly"); + printf(" 1st: [%d,%d].\n", pointmark(p1), pointmark(p2)); + printf(" 2nd: [%d,%d].\n", pointmark(tmppt), pointmark(nearpt)); + } else { + printf("Warning: A vertex lies %s on a line segment.\n", + ivf.iloc == NEARVERTEX ? "nearly" : "exactly"); + printf(" 1st: [%d,%d].\n", pointmark(p1), pointmark(p2)); + printf(" 2nd: [%d].\n", pointmark(nearpt)); + } + } + intersect_flag = true; + } else { + if (pointtype(nearpt) == FREESEGVERTEX) { + // Check if two segments are (nearly) intersecting. + int segidx = getfacetindex(*sedge); + face parsentseg; + sdecode(point2sh(nearpt), parsentseg); + int segidx2 = getfacetindex(parsentseg); + if (segidx2 != segidx) { + if (!b->quiet && !b->nowarning) { // -no -Q no -W + point p1 = segmentendpointslist[segidx*2]; + point p2 = segmentendpointslist[segidx*2+1]; + point p3 = segmentendpointslist[segidx2*2]; + point p4 = segmentendpointslist[segidx2*2+1]; + printf("Warning: Two line segments are %s crossing.\n", + ivf.iloc == NEARVERTEX ? "nearly" : "exactly"); + printf(" 1st: [%d,%d].\n", pointmark(p1), pointmark(p2)); + printf(" 2nd: [%d,%d].\n", pointmark(p3), pointmark(p4)); + } + intersect_flag = true; + } else { + //if (ivf.iloc == ONVERTEX) { + terminatetetgen(this, 2); // This should not be possible. + //} + } + } else if (pointtype(nearpt) == FREEFACETVERTEX) { + // This case is very unlikely. + terminatetetgen(this, 2); // to debug... + if (!b->quiet && !b->nowarning) { // -no -Q no -W + //face parsentsh; + //sdecode(point2sh(nearpt), parsentsh); + printf("Warning: A segment and a facet intersect.\n"); + } + intersect_flag = true; + } else { + // other cases... + terminatetetgen(this, 2); // to be checked. + } + } + } else { + // It is an edge of a facet. + if (!issteinerpoint(nearpt)) { + if (!b->quiet && !b->nowarning) { // no "-Q -W" + point p1 = sorg(*sedge); + point p2 = sdest(*sedge); + point p3 = sapex(*sedge); + printf("Warning: A vertex lies on a facet.\n"); + printf(" vertex: [%d]\n", pointmark(nearpt)); + printf(" facet triangle: [%d,%d,%d], tag(%d).\n", + pointmark(p1), pointmark(p2), pointmark(p3), + shellmark(*sedge)); + } + intersect_flag = true; + } else { + // A Steiner point. + if (pointtype(nearpt) == FREESEGVERTEX) { + // A facet and a segment is intersecting. + if (!b->quiet && !b->nowarning) { + printf("Warning: A facet and a segment intersect.\n"); + printf(" ...\n"); + } + intersect_flag = true; + } else if (pointtype(nearpt) == FREEFACETVERTEX) { + // Check if two facets are intersecting. + if (!b->quiet && !b->nowarning) { + printf("Warning: Two facets intersect.\n"); + printf(" ...\n"); + } + intersect_flag = true; + } else { + // A FREEVOLVERTEX. + // This is not a real self-intersection. + terminatetetgen(this, 2); // check this case. + } + } + } + + if (intersect_flag) { + idir = (int) SELF_INTERSECT; + } + } // if (sedge != NULL) + return 0; + } + } // if (dir == ACROSSVERT) + + // The edge is missing. + + // Try to remove the first intersecting face/edge. + enextesymself(*searchtet); // Go to the opposite face. + + if (dir == ACROSSFACE) { + if (checksubfaceflag) { + if (issubface(*searchtet)) { + if (sedge) { + // A self-intersection is detected. + if (!b->quiet && !b->nowarning) { + bool is_seg = (sedge->sh[5] == NULL); + if (is_seg) { + face fac; tspivot(*searchtet, fac); + int segidx = getfacetindex(*sedge); + point p1 = segmentendpointslist[segidx*2]; + point p2 = segmentendpointslist[segidx*2+1]; + printf("Warning: A segment and a facet exactly intersect.\n"); + printf(" seg : [%d,%d].\n", pointmark(p1), pointmark(p2)); + printf(" facet triangle: [%d,%d,%d] tag(%d).\n", + pointmark(sorg(fac)), pointmark(sdest(fac)), + pointmark(sapex(fac)), shellmark(fac)); + } else { + // It is a subedge of a facet. + point *ppt = (point *) &(sedge->sh[3]); + printf("Warning: Two facets exactly intersect.\n"); + printf(" 1st facet triangle: [%d,%d,%d] tag(%d).\n", + pointmark(ppt[0]), pointmark(ppt[1]), + pointmark(ppt[2]), shellmark(*sedge)); + face fac; tspivot(*searchtet, fac); + ppt = (point *) &(fac.sh[3]); + printf(" 2nd facet triangle: [%d,%d,%d] tag(%d).\n", + pointmark(ppt[0]), pointmark(ppt[1]), + pointmark(ppt[2]), shellmark(fac)); + } + } + idir = (int) SELF_INTERSECT; + } + return 0; + } // if (issubface(*searchtet)) + } + // Try to flip a crossing face. + if (removefacebyflips(searchtet, &fc)) { + continue; + } + } else if (dir == ACROSSEDGE) { + if (checksubsegflag) { + if (issubseg(*searchtet)) { + if (sedge) { + // A self-intersection is detected. + if (!b->quiet && !b->nowarning) { // no -Q, -W + bool is_seg = (sedge->sh[5] == NULL); + if (is_seg) { + face seg; tsspivot1(*searchtet, seg); + int segidx = getfacetindex(*sedge); + int segidx2 = getfacetindex(seg); + if (segidx != segidx2) { + point p1 = segmentendpointslist[segidx*2]; + point p2 = segmentendpointslist[segidx*2+1]; + point p3 = segmentendpointslist[segidx2*2]; + point p4 = segmentendpointslist[segidx2*2+1]; + printf("Warning: Two segments exactly intersect.\n"); + printf(" 1st seg [%d,%d] tag(%d).\n", + pointmark(p1), pointmark(p2), shellmark(*sedge)); + printf(" 2nd seg: [%d,%d] tag(%d).\n", + pointmark(p3), pointmark(p4), shellmark(seg)); + } else { + terminatetetgen(this, 2); + } + } else { + // It is a subedge of a facet. + point *ppt = (point *) &(sedge->sh[3]); + printf("Warning: A facet and a segment exactly intersect.\n"); + printf(" facet triangle: [%d,%d,%d] tag(%d).\n", + pointmark(ppt[0]), pointmark(ppt[1]), + pointmark(ppt[2]), shellmark(*sedge)); + face seg; tsspivot1(*searchtet, seg); + ppt = (point *) &(seg.sh[3]); + printf(" seg: [%d,%d] tag(%d).\n", + pointmark(ppt[0]), pointmark(ppt[1]), shellmark(seg)); + } + } + idir = (int) SELF_INTERSECT; + } + return 0; + } + } + // Try to flip an intersecting edge. + if (removeedgebyflips(searchtet, &fc) == 2) { + continue; + } + } else { + terminatetetgen(this, 2); // report a bug + } + + // The edge is missing. + + if (fullsearch) { + // Try to flip one of the faces/edges which intersects the edge. + triface neightet, spintet; + point pa, pb, pc, pd; + badface bakface; + enum interresult dir1; + int types[2], poss[4], pos = 0; + int success = 0; + int t1ver; + int i, j; + + // Loop through the sequence of intersecting faces/edges from + // 'startpt' to 'endpt'. + point2tetorg(startpt, *searchtet); + dir = finddirection(searchtet, endpt); + + // Go to the face/edge intersecting the searching edge. + enextesymself(*searchtet); // Go to the opposite face. + // This face/edge has been tried in previous step. + + while (1) { // Loop I-I + + // Find the next intersecting face/edge. + fsymself(*searchtet); + if (dir == ACROSSFACE) { + neightet = *searchtet; + j = (neightet.ver & 3); // j is the current face number. + for (i = j + 1; i < j + 4; i++) { + neightet.ver = (i % 4); + pa = org(neightet); + pb = dest(neightet); + pc = apex(neightet); + pd = oppo(neightet); // The above point. + if (tri_edge_test(pa,pb,pc,startpt,endpt, pd, 1, types, poss)) { + dir = (enum interresult) types[0]; + pos = poss[0]; + break; + } else { + dir = DISJOINT; + pos = 0; + } + } // i + // There must be an intersection face/edge. + if (dir == DISJOINT) { + terminatetetgen(this, 2); + } + } else if (dir == ACROSSEDGE) { + while (1) { // Loop I-I-I + // Check the two opposite faces (of the edge) in 'searchtet'. + for (i = 0; i < 2; i++) { + if (i == 0) { + enextesym(*searchtet, neightet); + } else { + eprevesym(*searchtet, neightet); + } + pa = org(neightet); + pb = dest(neightet); + pc = apex(neightet); + pd = oppo(neightet); // The above point. + if (tri_edge_test(pa,pb,pc,startpt,endpt,pd,1, types, poss)) { + dir = (enum interresult) types[0]; + pos = poss[0]; + break; // for loop + } else { + dir = DISJOINT; + pos = 0; + } + } // i + if (dir != DISJOINT) { + // Find an intersection face/edge. + break; // Loop I-I-I + } + // No intersection. Rotate to the next tet at the edge. + fnextself(*searchtet); + } // while (1) // Loop I-I-I + } else { + terminatetetgen(this, 2); // Report a bug + } + + // Adjust to the intersecting edge/vertex. + for (i = 0; i < pos; i++) { + enextself(neightet); + } + + if (dir == SHAREVERT) { + // Check if we have reached the 'endpt'. + pd = org(neightet); + if (pd == endpt) { + // Failed to recover the edge. + break; // Loop I-I + } else { + return 0; + } + } + + // The next to be flipped face/edge. + *searchtet = neightet; + + // Bakup this face (tetrahedron). + bakface.forg = org(*searchtet); + bakface.fdest = dest(*searchtet); + bakface.fapex = apex(*searchtet); + bakface.foppo = oppo(*searchtet); + + // Try to flip this intersecting face/edge. + if (dir == ACROSSFACE) { + if (checksubfaceflag) { + if (issubface(*searchtet)) { + return 0; + } + } + if (removefacebyflips(searchtet, &fc)) { + success = 1; + break; // Loop I-I + } + } else if (dir == ACROSSEDGE) { + if (checksubsegflag) { + if (issubseg(*searchtet)) { + return 0; + } + } + if (removeedgebyflips(searchtet, &fc) == 2) { + success = 1; + break; // Loop I-I + } + } else if (dir == ACROSSVERT) { + return 0; + } else { + terminatetetgen(this, 2); + } + + // The face/edge is not flipped. + if ((searchtet->tet == NULL) || + (org(*searchtet) != bakface.forg) || + (dest(*searchtet) != bakface.fdest) || + (apex(*searchtet) != bakface.fapex) || + (oppo(*searchtet) != bakface.foppo)) { + // 'searchtet' was flipped. We must restore it. + point2tetorg(bakface.forg, *searchtet); + dir1 = finddirection(searchtet, bakface.fdest); + if (dir1 == ACROSSVERT) { + if (dest(*searchtet) == bakface.fdest) { + spintet = *searchtet; + while (1) { + if (apex(spintet) == bakface.fapex) { + // Found the face. + *searchtet = spintet; + break; + } + fnextself(spintet); + if (spintet.tet == searchtet->tet) { + searchtet->tet = NULL; + break; // Not find. + } + } // while (1) + if (searchtet->tet != NULL) { + if (oppo(*searchtet) != bakface.foppo) { + fsymself(*searchtet); + if (oppo(*searchtet) != bakface.foppo) { + // The original (intersecting) tet has been flipped. + searchtet->tet = NULL; + break; // Not find. + } + } + } + } else { + searchtet->tet = NULL; // Not find. + } + } else { + searchtet->tet = NULL; // Not find. + } + if (searchtet->tet == NULL) { + success = 0; // This face/edge has been destroyed. + break; // Loop I-I + } + } + } // while (1) // Loop I-I + + if (success) { + // One of intersecting faces/edges is flipped. + continue; + } + + } // if (fullsearch) + + // The edge is missing. + break; // Loop I + + } // while (1) // Loop I + + return 0; +} + +//============================================================================// +// // +// add_steinerpt_in_schoenhardtpoly() Insert a Steiner point in a Schoen- // +// hardt polyhedron. // +// // +// 'abtets' is an array of n tets which all share at the edge [a,b]. Let the // +// tets are [a,b,p0,p1], [a,b,p1,p2], ..., [a,b,p_(n-2),p_(n-1)]. Moreover, // +// the edge [p0,p_(n-1)] intersects all of the tets in 'abtets'. A special // +// case is that the edge [p0,p_(n-1)] is coplanar with the edge [a,b]. // +// Such set of tets arises when we want to recover an edge from 'p0' to 'p_ // +// (n-1)', and the number of tets at [a,b] can not be reduced by any flip. // +// // +//============================================================================// + +int tetgenmesh::add_steinerpt_in_schoenhardtpoly(triface *abtets, int n, + int splitsliverflag, int chkencflag) +{ + triface worktet, *parytet; + triface faketet1, faketet2; + point pc, pd, steinerpt; + insertvertexflags ivf; + optparameters opm; + REAL vcd[3], sampt[3], smtpt[3]; + REAL maxminvol = 0.0, minvol = 0.0, ori; + int success, maxidx = 0; + int it, i; + + + if (splitsliverflag) { + // randomly pick a tet. + int idx = rand() % n; + + // Calulcate the barycenter of this tet. + point pa = org(abtets[idx]); + point pb = dest(abtets[idx]); + pc = apex(abtets[idx]); + pd = oppo(abtets[idx]); + + makepoint(&steinerpt, FREEVOLVERTEX); + for (i = 0; i < 3; i++) { + steinerpt[i] = (pa[i] + pb[i] + pc[i] + pd[i]) / 4.; + } + + + worktet = abtets[idx]; + ivf.iloc = (int) OUTSIDE; // need point location. + ivf.bowywat = 1; + //ivf.lawson = 0; + ivf.lawson = 2; // Do flips to recover Delaunayness. + ivf.rejflag = 0; + ivf.chkencflag = chkencflag; + ivf.sloc = 0; + ivf.sbowywat = 0; + ivf.splitbdflag = 0; + ivf.validflag = 1; + ivf.respectbdflag = 1; + ivf.assignmeshsize = b->metric; + + if (insertpoint(steinerpt, &worktet, NULL, NULL, &ivf)) { + // The vertex has been inserted. + if (flipstack != NULL) { + recoverdelaunay(); + } + st_volref_count++; + if (steinerleft > 0) steinerleft--; + return 1; + } else { + // Not inserted. + pointdealloc(steinerpt); + return 0; + } + } // if (splitsliverflag) + + pc = apex(abtets[0]); // pc = p0 + pd = oppo(abtets[n-1]); // pd = p_(n-1) + + // Find an optimial point in edge [c,d]. It is visible by all outer faces + // of 'abtets', and it maxmizes the min volume. + + // initialize the list of 2n boundary faces. + for (i = 0; i < n; i++) { + edestoppo(abtets[i], worktet); // [p_i,p_i+1,a] + cavetetlist->newindex((void **) &parytet); + *parytet = worktet; + eorgoppo(abtets[i], worktet); // [p_i+1,p_i,b] + cavetetlist->newindex((void **) &parytet); + *parytet = worktet; + } + + int N = 100; + REAL stepi = 0.01; + + // Search the point along the edge [c,d]. + for (i = 0; i < 3; i++) vcd[i] = pd[i] - pc[i]; + + // Sample N points in edge [c,d]. + for (it = 1; it < N; it++) { + for (i = 0; i < 3; i++) { + sampt[i] = pc[i] + (stepi * (double) it) * vcd[i]; + } + for (i = 0; i < cavetetlist->objects; i++) { + parytet = (triface *) fastlookup(cavetetlist, i); + ori = orient3d(dest(*parytet), org(*parytet), apex(*parytet), sampt); + if (i == 0) { + minvol = ori; + } else { + if (minvol > ori) minvol = ori; + } + } // i + if (it == 1) { + maxminvol = minvol; + maxidx = it; + } else { + if (maxminvol < minvol) { + maxminvol = minvol; + maxidx = it; + } + } + } // it + + if (maxminvol <= 0) { + cavetetlist->restart(); + return 0; + } + + for (i = 0; i < 3; i++) { + smtpt[i] = pc[i] + (stepi * (double) maxidx) * vcd[i]; + } + + // Create two faked tets to hold the two non-existing boundary faces: + // [d,c,a] and [c,d,b]. + maketetrahedron(&faketet1); + setvertices(faketet1, pd, pc, org(abtets[0]), dummypoint); + cavetetlist->newindex((void **) &parytet); + *parytet = faketet1; + maketetrahedron(&faketet2); + setvertices(faketet2, pc, pd, dest(abtets[0]), dummypoint); + cavetetlist->newindex((void **) &parytet); + *parytet = faketet2; + + // Point smooth options. + opm.max_min_volume = 1; + opm.numofsearchdirs = 20; + opm.searchstep = 0.001; + opm.maxiter = 100; // Limit the maximum iterations. + opm.initval = 0.0; // Initial volume is zero. + + // Try to relocate the point into the inside of the polyhedron. + success = smoothpoint(smtpt, cavetetlist, 1, &opm); + + if (success) { + while (opm.smthiter == 100) { + // It was relocated and the prescribed maximum iteration reached. + // Try to increase the search stepsize. + opm.searchstep *= 10.0; + //opm.maxiter = 100; // Limit the maximum iterations. + opm.initval = opm.imprval; + opm.smthiter = 0; // Init. + smoothpoint(smtpt, cavetetlist, 1, &opm); + } + } // if (success) + + // Delete the two faked tets. + tetrahedrondealloc(faketet1.tet); + tetrahedrondealloc(faketet2.tet); + + cavetetlist->restart(); + + if (success) { + // Insert this Steiner point. + + // Insert the Steiner point. + makepoint(&steinerpt, FREEVOLVERTEX); + for (i = 0; i < 3; i++) steinerpt[i] = smtpt[i]; + + // Insert the created Steiner point. + for (i = 0; i < n; i++) { + infect(abtets[i]); + caveoldtetlist->newindex((void **) &parytet); + *parytet = abtets[i]; + } + worktet = abtets[0]; // No need point location. + ivf.iloc = (int) INSTAR; + ivf.chkencflag = chkencflag; + ivf.assignmeshsize = b->metric; + if (ivf.assignmeshsize) { + // Search the tet containing 'steinerpt' for size interpolation. + locate(steinerpt, &(abtets[0])); + worktet = abtets[0]; + } + + // Insert the new point into the tetrahedralization T. + if (insertpoint(steinerpt, &worktet, NULL, NULL, &ivf)) { + // The vertex has been inserted. + st_volref_count++; + if (steinerleft > 0) steinerleft--; + return 1; + } else { + // Not inserted. + pointdealloc(steinerpt); + return 0; + } + } + + //if (!success) { + return 0; + //} +} + +//============================================================================// +// // +// add_steinerpt_in_segment() Add a Steiner point inside a segment. // +// // +//============================================================================// + +int tetgenmesh::add_steinerpt_in_segment(face* misseg, int searchlevel, int& idir) +{ + triface searchtet; + face *paryseg, candseg; + point startpt, endpt, pc, pd; + flipconstraints fc; + enum interresult dir; + REAL P[3], Q[3], tp, tq; + REAL len, smlen = 0, split = 0, split_q = 0; + int success; + int i; + + startpt = sorg(*misseg); + endpt = sdest(*misseg); + + idir = DISJOINT; // init. + + // sort the vertices + //if (pointmark(startpt) > pointmark(endpt)) { + // endpt = sorg(*misseg); + // startpt = sdest(*misseg); + //} + + + fc.seg[0] = startpt; + fc.seg[1] = endpt; + fc.checkflipeligibility = 1; + fc.collectencsegflag = 1; + + point2tetorg(startpt, searchtet); + dir = finddirection(&searchtet, endpt); + if (dir == ACROSSVERT) { + return 0; + } + + // Try to flip the first intersecting face/edge. + enextesymself(searchtet); // Go to the opposite face. + + int bak_fliplinklevel = b->fliplinklevel; + b->fliplinklevel = searchlevel; + + if (dir == ACROSSFACE) { + // A face is intersected with the segment. Try to flip it. + success = removefacebyflips(&searchtet, &fc); + } else if (dir == ACROSSEDGE) { + // An edge is intersected with the segment. Try to flip it. + success = removeedgebyflips(&searchtet, &fc); + } + + split = 0; + for (i = 0; i < caveencseglist->objects; i++) { + paryseg = (face *) fastlookup(caveencseglist, i); + suninfect(*paryseg); + // Calculate the shortest edge between the two lines. + pc = sorg(*paryseg); + pd = sdest(*paryseg); + + // sort the vertices + //if (pointmark(pc) > pointmark(pd)) { + // pd = sorg(*paryseg); + // pc = sdest(*paryseg); + //} + + tp = tq = 0; + if (linelineint(startpt, endpt, pc, pd, P, Q, &tp, &tq)) { + // Does the shortest edge lie between the two segments? + // Round tp and tq. + if ((tp > 0) && (tq < 1)) { + if (tp < 0.5) { + if (tp < (b->epsilon * 1e+3)) tp = 0.0; + } else { + if ((1.0 - tp) < (b->epsilon * 1e+3)) tp = 1.0; + } + } + if ((tp <= 0) || (tp >= 1)) continue; + if ((tq > 0) && (tq < 1)) { + if (tq < 0.5) { + if (tq < (b->epsilon * 1e+3)) tq = 0.0; + } else { + if ((1.0 - tq) < (b->epsilon * 1e+3)) tq = 1.0; + } + } + if ((tq <= 0) || (tq >= 1)) continue; + // It is a valid shortest edge. Calculate its length. + len = distance(P, Q); + if (split == 0) { + smlen = len; + split = tp; + split_q = tq; + candseg = *paryseg; + } else { + if (len < smlen) { + smlen = len; + split = tp; + split_q = tq; + candseg = *paryseg; + } + } + } + } + + caveencseglist->restart(); + b->fliplinklevel = bak_fliplinklevel; + + if (split == 0) { + // Found no crossing segment. + return 0; + } + + face splitsh; + face splitseg; + point steinerpt, *parypt; + insertvertexflags ivf; + + if (b->addsteiner_algo == 1) { + // Split the segment at the closest point to a near segment. + makepoint(&steinerpt, FREESEGVERTEX); + for (i = 0; i < 3; i++) { + steinerpt[i] = startpt[i] + split * (endpt[i] - startpt[i]); + } + } else { // b->addsteiner_algo == 2 + for (i = 0; i < 3; i++) { + P[i] = startpt[i] + split * (endpt[i] - startpt[i]); + } + pc = sorg(candseg); + pd = sdest(candseg); + for (i = 0; i < 3; i++) { + Q[i] = pc[i] + split_q * (pd[i] - pc[i]); + } + makepoint(&steinerpt, FREEVOLVERTEX); + for (i = 0; i < 3; i++) { + steinerpt[i] = 0.5 * (P[i] + Q[i]); + } + } + + // Check if the two segments are nearly crossing each other. + pc = sorg(candseg); + pd = sdest(candseg); + if (is_collinear_at(steinerpt, pc, pd)) { // -p///#, default 179.9 degree + if (!b->quiet && !b->nowarning) { // no -Q, -W + int segidx = getfacetindex(*misseg); + point p1 = segmentendpointslist[segidx*2]; + point p2 = segmentendpointslist[segidx*2+1]; + int segidx2 = getfacetindex(candseg); + point p3 = segmentendpointslist[segidx2*2]; + point p4 = segmentendpointslist[segidx2*2+1]; + printf("Warning: Two line segments are almost crossing.\n"); + printf(" 1st: [%d,%d].\n", pointmark(p1), pointmark(p2)); + printf(" 2nd: [%d,%d].\n", pointmark(p3), pointmark(p4)); + } + + // calculate a new angle tolerance. + REAL collinear_ang = interiorangle(steinerpt, pc, pd, NULL) / PI * 180.; + double ang_diff = collinear_ang - b->collinear_ang_tol; + double new_ang_tol = collinear_ang + ang_diff / 180.; + + if (new_ang_tol < 180.0) { // no -Q, -W + // Reduce the angle tolerance to detect collinear event. + if (!b->quiet && !b->nowarning) { + printf(" Reducing collinear tolerance from %g to %g degree.\n", + b->collinear_ang_tol, new_ang_tol); + } + b->collinear_ang_tol = new_ang_tol; + cos_collinear_ang_tol = cos(b->collinear_ang_tol / 180.0 * PI); + } else { + // Report a self-intersection event due to epsilon. + if (!b->quiet && !b->nowarning) { // no -Q, -W + printf(" Cannot reduce the current collinear tolerance (=%g degree).\n", + b->collinear_ang_tol); + } + idir = SELF_INTERSECT; + pointdealloc(steinerpt); + return 0; + } + } + + // We need to locate the point. Start searching from 'searchtet'. + if (split < 0.5) { + point2tetorg(startpt, searchtet); + } else { + point2tetorg(endpt, searchtet); + } + if (b->addsteiner_algo == 1) { + splitseg = *misseg; + spivot(*misseg, splitsh); + // for create_a_shorter_edge(). + setpoint2sh(steinerpt, sencode(*misseg)); + } else { + splitsh.sh = NULL; + splitseg.sh = NULL; + } + ivf.iloc = (int) OUTSIDE; + ivf.bowywat = 1; + //ivf.lawson = 0; + ivf.lawson = 2; // Do flips to recover Delaunayness. + ivf.rejflag = 0; + ivf.chkencflag = 0; + ivf.sloc = (int) ONEDGE; + ivf.sbowywat = 1; // split surface mesh separately, new subsegments are + // pushed into "subsegstack". + ivf.splitbdflag = 0; + ivf.validflag = 1; + ivf.respectbdflag = 1; + ivf.assignmeshsize = b->metric; + + if (insertpoint(steinerpt, &searchtet, &splitsh, &splitseg, &ivf)) { + if (flipstack != NULL) { + recoverdelaunay(); + } + } else { + pointdealloc(steinerpt); + return 0; + } + + if (b->addsteiner_algo == 1) { + // Save this Steiner point (for removal). + // Re-use the array 'subvertstack'. + subvertstack->newindex((void **) &parypt); + *parypt = steinerpt; + st_segref_count++; + } else { // b->addsteiner_algo == 2 + // Queue the segment for recovery. + subsegstack->newindex((void **) &paryseg); + *paryseg = *misseg; + st_volref_count++; + } + if (steinerleft > 0) steinerleft--; + + return 1; +} + +//============================================================================// +// // +// addsteiner4recoversegment() Add a Steiner point for recovering a seg. // +// // +// Tries to add a Steiner point in the volume (near this segment) which will // +// help to recover this segment. This segment itself is not split. // +// // +// 'splitsliverflag' is a parameter used in the subroutine add_steiner_in_ // +// schonhardpoly(). // +// // +//============================================================================// + +int tetgenmesh::add_steinerpt_to_recover_edge(point startpt, point endpt, + face* misseg, int splitsegflag, int splitsliverflag, int& idir) +{ + triface *abtets, searchtet, spintet; + face splitsh; + face *paryseg; + point pa, pb, pd, steinerpt, *parypt; + enum interresult dir; + insertvertexflags ivf; + int types[2], poss[4]; + int n, endi, success; + int t1ver; + int i; + + idir = (int) DISJOINT; + + if (misseg != NULL) { + startpt = sorg(*misseg); + if (pointtype(startpt) == FREESEGVERTEX) { + sesymself(*misseg); + startpt = sorg(*misseg); + } + endpt = sdest(*misseg); + } + + + point2tetorg(startpt, searchtet); + dir = finddirection(&searchtet, endpt); + + + if (dir == ACROSSVERT) { + if (dest(searchtet) == endpt) { + // This edge exists. + if ((misseg != NULL) && (subsegstack != NULL)) { + // Add the missing segment back to the recovering list. + subsegstack->newindex((void **) &paryseg); + *paryseg = *misseg; + } + return 1; + } else { + // This edge crosses a vertex (not endpt). + bool intersect_flag = false; // return + if (misseg != NULL) { + // Check whether there exists a self-intersection. + point nearpt = dest(searchtet); + ivf.iloc = ONVERTEX; + // report_seg_vertex_intersect(misseg, dest(searchtet), ONVERTEX); + int segidx = getfacetindex(*misseg); + point p1 = segmentendpointslist[segidx*2]; + point p2 = segmentendpointslist[segidx*2+1]; + + if (!issteinerpoint(nearpt)) { + // It is an input point. + if (!b->quiet && !b->nowarning) { + point tmppt = NULL; + if (is_segment(p1, nearpt)) tmppt = p1; + else if (is_segment(p2, nearpt)) tmppt = p2; + if (tmppt != NULL) { + printf("Warning: Two line segments are %s overlapping.\n", + ivf.iloc == NEARVERTEX ? "nearly" : "exactly"); + printf(" 1st: [%d,%d].\n", pointmark(p1), pointmark(p2)); + printf(" 2nd: [%d,%d].\n", pointmark(tmppt), pointmark(nearpt)); + } else { + printf("Warning: A vertex lies %s on a line segment.\n", + ivf.iloc == NEARVERTEX ? "nearly" : "exactly"); + printf(" segment: [%d,%d].\n", pointmark(p1), pointmark(p2)); + printf(" vertex : [%d].\n", pointmark(nearpt)); + } + } + intersect_flag = true; + } else { + if (pointtype(nearpt) == FREESEGVERTEX) { + // Check if two segments are exactly intersecting. + face parsentseg; + sdecode(point2sh(nearpt), parsentseg); + int segidx2 = getfacetindex(parsentseg); + if (segidx2 != segidx) { + if (!b->quiet && !b->nowarning) { + point p3 = segmentendpointslist[segidx2*2]; + point p4 = segmentendpointslist[segidx2*2+1]; + printf("Warning: Two line segments are %s crossing.\n", + ivf.iloc == NEARVERTEX ? "nearly" : "exactly"); + printf(" 1st: [%d,%d].\n", pointmark(p1), pointmark(p2)); + printf(" 2nd: [%d,%d].\n", pointmark(p3), pointmark(p4)); + } + intersect_flag = true; + } else { + if (ivf.iloc == ONVERTEX) { + terminatetetgen(this, 2); // This should not be possible. + } + } + } else { + // other cases... + terminatetetgen(this, 2); + } + } + } // if (misseg != NULL) + if (intersect_flag) { + idir = (int) SELF_INTERSECT; + } + return 0; + } + } // if (dir == ACROSSVERT) { + + enextself(searchtet); + + if (dir == ACROSSFACE) { + // The segment is crossing at least 3 faces. Find the common edge of + // the first 3 crossing faces. + esymself(searchtet); + fsym(searchtet, spintet); + pd = oppo(spintet); + + if (pd == endpt) { + if (misseg != NULL) { + // Calclate the smallest angle between (a,b,c) and (startpt, endpt). + triface tmptet; + REAL ang, collinear_ang = 0.; + for (int k = 0; k < 3; k++) { + ang = interiorangle(org(searchtet), startpt, endpt, NULL); // in [0, PI] + if (ang > collinear_ang) { + collinear_ang = ang; + tmptet = searchtet; // org(tmptet) + } + enextself(searchtet); + } + collinear_ang = collinear_ang / PI * 180.; // in degree + + if (collinear_ang > b->collinear_ang_tol) { // -p///#, default 179.9 degree + // Report a self-intersection event due to epsilon. + if (!b->quiet && !b->nowarning) { // no -Q, -W + point nearpt = org(tmptet); + ivf.iloc = NEARVERTEX; + // report_seg_vertex_intersect(misseg, dest(searchtet), ONVERTEX); + int segidx = getfacetindex(*misseg); + point p1 = segmentendpointslist[segidx*2]; + point p2 = segmentendpointslist[segidx*2+1]; + + if (!issteinerpoint(nearpt)) { + point tmppt = NULL; + if (is_segment(p1, nearpt)) tmppt = p1; + else if (is_segment(p2, nearpt)) tmppt = p2; + if (tmppt != NULL) { + printf("Warning: Two line segments are %s overlapping.\n", + ivf.iloc == NEARVERTEX ? "nearly" : "exactly"); + printf(" 1st: [%d,%d].\n", pointmark(p1), pointmark(p2)); + printf(" 2nd: [%d,%d].\n", pointmark(tmppt), pointmark(nearpt)); + } else { + printf("Warning: A vertex lies %s on a line segment.\n", + ivf.iloc == NEARVERTEX ? "nearly" : "exactly"); + printf(" segment: [%d,%d].\n", pointmark(p1), pointmark(p2)); + printf(" vertex : [%d].\n", pointmark(nearpt)); + } + } else { + if (pointtype(nearpt) == FREESEGVERTEX) { + // Check if two segments are nearly intersecting. + face parsentseg; + sdecode(point2sh(nearpt), parsentseg); + int segidx2 = getfacetindex(parsentseg); + if (segidx2 != segidx) { + //if (!b->quiet && !b->nowarning) { + point p3 = segmentendpointslist[segidx2*2]; + point p4 = segmentendpointslist[segidx2*2+1]; + printf("Warning: Two line segments are %s crossing.\n", + ivf.iloc == NEARVERTEX ? "nearly" : "exactly"); + printf(" 1st: [%d,%d].\n", pointmark(p1), pointmark(p2)); + printf(" 2nd: [%d,%d].\n", pointmark(p3), pointmark(p4)); + //} + //intersect_flag = true; + } else { + //if (ivf.iloc == ONVERTEX) { + terminatetetgen(this, 2); // This should not be possible. + //} + } + } else { + // Other case to report. + // assert(0); // to do... + terminatetetgen(this, 2); + } + } + } + + // calculate a new angle tolerance. + double ang_diff = collinear_ang - b->collinear_ang_tol; + double new_ang_tol = collinear_ang + ang_diff / 180.; + + if (new_ang_tol < 180.) { + // Reduce the angle tolerance to detect collinear event. + if (!b->quiet && !b->nowarning) { + printf(" Reducing collinear tolerance from %g to %g degree.\n", + b->collinear_ang_tol, new_ang_tol); + } + b->collinear_ang_tol = new_ang_tol; + cos_collinear_ang_tol = cos(b->collinear_ang_tol / 180. * PI); + + // This segment can be recovered by a 2-3 flip. + if (subsegstack != NULL) { + // Add the missing segment back to the recovering list. + subsegstack->newindex((void **) &paryseg); + *paryseg = *misseg; + } + return 1; + } else { + if (!b->quiet && !b->nowarning) { + printf(" Cannot reduce the current collinear tolerance (=%g degree).\n", + b->collinear_ang_tol); + } + idir = (int) SELF_INTERSECT; + return 0; + } + } else { + // This segment can be recovered by a 2-3 flip. + if (subsegstack != NULL) { + // Add the missing segment back to the recovering list. + subsegstack->newindex((void **) &paryseg); + *paryseg = *misseg; + } + return 1; + } + } else { + // This edge (not a segment) can be recovered by a 2-3 flip. + return 1; + } + } // if (pd == endpt) + + if (issubface(searchtet)) { + if (misseg != NULL) { + terminatetetgen(this, 2); + // Report a segment and a facet intersect. + if (!b->quiet && !b->nowarning) { + face fac; tspivot(searchtet, fac); + int segidx = getfacetindex(*misseg); + point p1 = segmentendpointslist[segidx*2]; + point p2 = segmentendpointslist[segidx*2+1]; + printf("Warning: A segment and a facet exactly intersect.\n"); + printf(" segment : [%d,%d].\n", pointmark(p1), pointmark(p2)); + printf(" facet triangle: [%d,%d,%d] tag(%d).\n", + pointmark(org(searchtet)), pointmark(dest(searchtet)), + pointmark(apex(searchtet)), shellmark(fac)); + } + idir = (int) SELF_INTERSECT; + } + return 0; + } // if (issubface(searchtet)) + + for (i = 0; i < 3; i++) { + pa = org(spintet); + pb = dest(spintet); + if (tri_edge_test(pa, pb, pd, startpt, endpt, NULL, 1, types, poss)) { + break; // Found the edge. + } + enextself(spintet); + eprevself(searchtet); + } + esymself(searchtet); + } + else { // dir == ACROSSEDGE; + if (issubseg(searchtet)) { + terminatetetgen(this, 2); + if (misseg != NULL) { + // Report a self_intersection. + //bool intersect_flag = false; + //point nearpt = dest(searchtet); + ivf.iloc = ONVERTEX; + // report_seg_vertex_intersect(misseg, dest(searchtet), ONVERTEX); + int segidx = getfacetindex(*misseg); + point p1 = segmentendpointslist[segidx*2]; + point p2 = segmentendpointslist[segidx*2+1]; + face parsentseg; + //sdecode(point2sh(nearpt), parsentseg); + tsspivot1(searchtet, parsentseg); + int segidx2 = getfacetindex(parsentseg); + if (segidx2 != segidx) { + if (!b->quiet && !b->nowarning) { + point p3 = segmentendpointslist[segidx2*2]; + point p4 = segmentendpointslist[segidx2*2+1]; + printf("Warning: Two line segments are %s crossing.\n", + ivf.iloc == NEARVERTEX ? "nearly" : "exactly"); + printf(" 1st: [%d,%d].\n", pointmark(p1), pointmark(p2)); + printf(" 2nd: [%d,%d].\n", pointmark(p3), pointmark(p4)); + } + //intersect_flag = true; + } else { + if (ivf.iloc == ONVERTEX) { + terminatetetgen(this, 2); // This should not be possible. + } + } + idir = (int) SELF_INTERSECT; + } // if (misseg != NULL) + return 0; + } + } + + if (!splitsegflag) { + // Try to recover this segment by adding Steiner points near it. + + spintet = searchtet; + n = 0; endi = -1; + while (1) { + // Check if the endpt appears in the star. + if (apex(spintet) == endpt) { + endi = n; // Remember the position of endpt. + } + n++; // Count a tet in the star. + fnextself(spintet); + if (spintet.tet == searchtet.tet) break; + } + + if (endi > 0) { + // endpt is also in the edge star + // Get all tets in the edge star. + abtets = new triface[n]; + spintet = searchtet; + for (i = 0; i < n; i++) { + abtets[i] = spintet; + fnextself(spintet); + } + + success = 0; + + if (dir == ACROSSFACE) { + // Find a Steiner points inside the polyhedron. + if (add_steinerpt_in_schoenhardtpoly(abtets, endi, splitsliverflag, 0)) { + success = 1; + } + } else if (dir == ACROSSEDGE) { + // PLC check. + if (issubseg(searchtet)) { + terminatetetgen(this, 2); + } + if (n > 4) { + // In this case, 'abtets' is separated by the plane (containing the + // two intersecting edges) into two parts, P1 and P2, where P1 + // consists of 'endi' tets: abtets[0], abtets[1], ..., + // abtets[endi-1], and P2 consists of 'n - endi' tets: + // abtets[endi], abtets[endi+1], abtets[n-1]. + if (endi > 2) { // P1 + // There are at least 3 tets in the first part. + if (add_steinerpt_in_schoenhardtpoly(abtets, endi, splitsliverflag, 0)) { + success++; + } + } + if ((n - endi) > 2) { // P2 + // There are at least 3 tets in the first part. + if (add_steinerpt_in_schoenhardtpoly(&(abtets[endi]), n - endi, splitsliverflag, 0)) { + success++; + } + } + } else { + // In this case, a 4-to-4 flip should be re-cover the edge [c,d]. + // However, there will be invalid tets (either zero or negtive + // volume). Otherwise, [c,d] should already be recovered by the + // recoveredge() function. + } + } else { + terminatetetgen(this, 2); + } + + delete [] abtets; + + if (success && (misseg != NULL)) { + // Add the missing segment back to the recovering list. + subsegstack->newindex((void **) &paryseg); + *paryseg = *misseg; + } + + if (success) { + return 1; + } + } // if (endi > 0) + + return 0; + } // if (!splitsegflag) + + if (b->verbose > 3) { + printf(" Recover segment (%d, %d) by splitting it.\n", + pointmark(startpt), pointmark(endpt)); + } + steinerpt = NULL; + + if (b->addsteiner_algo > 0) { // -Y/1 or -Y/2 + if (add_steinerpt_in_segment(misseg, 3, idir)) { + return 1; + } + if (idir == SELF_INTERSECT) { + return 0; + } + sesymself(*misseg); + if (add_steinerpt_in_segment(misseg, 3, idir)) { + return 1; + } + sesymself(*misseg); + if (idir == SELF_INTERSECT) { + return 0; + } + } + + + // Let the face [a,b,d] be the first intersecting face of the segment + // [startpt, endpt]. We add the interseting point. + REAL ip[3], u; + point2tetorg(startpt, searchtet); + dir = finddirection(&searchtet, endpt); + if (dir == ACROSSVERT) { + if (dest(searchtet) == endpt) { + // This edge exists. + if (misseg != NULL) { + // Add the missing segment back to the recovering list. + subsegstack->newindex((void **) &paryseg); + *paryseg = *misseg; + } + return 1; + } else { + // This should be a self-intersection. + if (misseg != NULL) { + terminatetetgen(this, 2); + // report_seg_vertex_intersect(misseg, dest(searchtet), ONVERTEX); + idir = (int) SELF_INTERSECT; + } + return 0; + } + } + + enextself(searchtet); + pa = org(searchtet); + pb = dest(searchtet); + pd = oppo(searchtet); + + // Calculate the intersection of the face [a,b,d] and the segment. + //planelineint(pa, pb, pd, startpt, endpt, ip, &u); + + point fpt[3], ept[2]; + sort_3pts(pa, pb, pd, fpt); + sort_2pts(startpt, endpt, ept); + planelineint(fpt[0], fpt[1], fpt[2], ept[0], ept[1], ip, &u); + + if ((u > 0) && (u < 1)) { + // Create a Steiner point. + makepoint(&steinerpt, FREESEGVERTEX); + for (i = 0; i < 3; i++) steinerpt[i] = ip[i]; + + // for create_a_shorter_edge(). + setpoint2sh(steinerpt, sencode(*misseg)); + + esymself(searchtet); // The crossing face/edge. + spivot(*misseg, splitsh); + if (dir == ACROSSFACE) { + //ivf.iloc = (int) ONFACE; + ivf.refineflag = 4; // Check if the crossing face is removed. + } else { + //ivf.iloc = (int) ONEDGE; + ivf.refineflag = 8; // Check if the crossing edge is removed. + } + ivf.iloc = (int) OUTSIDE; // do point location. + ivf.refinetet = searchtet; // The crossing face/edge. + ivf.bowywat = 1; + // ivf.lawson = 0; + ivf.lawson = 2; // Recover Delaunay after inserting this vertex. + ivf.rejflag = 0; + ivf.chkencflag = 0; + ivf.sloc = (int) ONEDGE; + ivf.sbowywat = 1; // split surface mesh separately, new subsegments are + // pushed into "subsegstack". + ivf.splitbdflag = 0; + ivf.validflag = 1; + ivf.respectbdflag = 1; + ivf.assignmeshsize = b->metric; + + if (insertpoint(steinerpt, &searchtet, &splitsh, misseg, &ivf)) { + if (flipstack != NULL) { + recoverdelaunay(); + } + + // Save this Steiner point (for removal). + // Re-use the array 'subvertstack'. + subvertstack->newindex((void **) &parypt); + *parypt = steinerpt; + + st_segref_count++; + if (steinerleft > 0) steinerleft--; + + return 1; + } else { + // Check if this failure is due to a self-intersection. + if ((ivf.iloc == ONVERTEX) || (ivf.iloc == NEARVERTEX)) { + if (misseg != NULL) { + // report_seg_vertex_intersect(misseg, nearpt, ivf.iloc); + int segidx = getfacetindex(*misseg); + point p1 = segmentendpointslist[segidx*2]; + point p2 = segmentendpointslist[segidx*2+1]; + bool intersect_flag = false; + point nearpt = org(searchtet); + if (!issteinerpoint(nearpt)) { + // 'nearpt' is an input vertex. + if (!b->quiet && !b->nowarning) { + point tmppt = NULL; + if (is_segment(p1, nearpt)) tmppt = p1; + else if (is_segment(p2, nearpt)) tmppt = p2; + if (tmppt != NULL) { + // Two input segments are nearly overlapping. + printf("Warning: Two line segments are %s overlapping.\n", + ivf.iloc == NEARVERTEX ? "nearly" : "exactly"); + printf(" 1st: [%d,%d].\n", pointmark(p1), pointmark(p2)); + printf(" 2nd: [%d,%d].\n", pointmark(tmppt), pointmark(nearpt)); + } else { + // An input vertex is very close to a segment. + printf("Warning: A vertex lies %s on a line segment.\n", + ivf.iloc == NEARVERTEX ? "nearly" : "exactly"); + printf(" segment: [%d,%d].\n", pointmark(p1), pointmark(p2)); + printf(" vertex : [%d].\n", pointmark(nearpt)); + } + } // if (!b->quiet && !b->nowarning) + intersect_flag = true; + } else { + if (pointtype(nearpt) == FREESEGVERTEX) { + // Check if two segments are nearly intersecting. + face parsentseg; + sdecode(point2sh(nearpt), parsentseg); + int segidx2 = getfacetindex(parsentseg); + if (segidx2 != segidx) { + point p3 = segmentendpointslist[segidx2*2]; + point p4 = segmentendpointslist[segidx2*2+1]; + printf("Warning: Two line segments are %s crossing.\n", + ivf.iloc == NEARVERTEX ? "nearly" : "exactly"); + printf(" 1st: [%d,%d].\n", pointmark(p1), pointmark(p2)); + printf(" 2nd: [%d,%d].\n", pointmark(p3), pointmark(p4)); + intersect_flag = true; + } + } else { + // report other cases. + // to do... + terminatetetgen(this, 2); + } + } + if (intersect_flag) { + if (!b->quiet && !b->nowarning) { + if (ivf.iloc == NEARVERTEX) { + double dd = distance(steinerpt, nearpt); + double new_dd = minedgelength - dd / longest; + double new_eps = new_dd / longest; + printf("You can ignore this warning by using -T%e (default is %e) option.\n", + new_eps, b->epsilon); + printf(" This will allow a short edge (len = %.17g) (default limit is %g).\n", + dd, minedgelength); + } + } + // A self-intersection is detected. + idir = (int) SELF_INTERSECT; + } // if (intersect_flag) + } // if (misseg != NULL) + } // if ((ivf.iloc == ONVERTEX) || (ivf.iloc == NEARVERTEX)) + + // The vertex is not inserted. + pointdealloc(steinerpt); + steinerpt = NULL; + } + } // if ((u > 0) && (u < 1)) + + return 0; // Failed to reocver this segment. + + // [2020-05-02] The following code is skipped. + if (steinerpt == NULL) { + // Split the segment at its midpoint. + makepoint(&steinerpt, FREESEGVERTEX); + for (i = 0; i < 3; i++) { + steinerpt[i] = 0.5 * (startpt[i] + endpt[i]); + } + + // We need to locate the point. + spivot(*misseg, splitsh); + ivf.iloc = (int) OUTSIDE; + ivf.bowywat = 1; + //ivf.lawson = 0; + ivf.lawson = 2; // do flip to recover locally Delaunay faces. + ivf.rejflag = 0; + ivf.chkencflag = 0; + ivf.sloc = (int) ONEDGE; + ivf.sbowywat = 1; // mesh surface separately + ivf.splitbdflag = 0; + ivf.validflag = 1; + ivf.respectbdflag = 1; + ivf.assignmeshsize = b->metric; + if (insertpoint(steinerpt, &searchtet, &splitsh, misseg, &ivf)) { + if (flipstack != NULL) { + recoverdelaunay(); + } + } else { + terminatetetgen(this, 2); + } + } // if (endi > 0) + + // Save this Steiner point (for removal). + // Re-use the array 'subvertstack'. + subvertstack->newindex((void **) &parypt); + *parypt = steinerpt; + + st_segref_count++; + if (steinerleft > 0) steinerleft--; + + return 1; +} + +//============================================================================// +// // +// recoversegments() Recover all segments. // +// // +// All segments need to be recovered are in 'subsegstack'. // +// // +// This routine first tries to recover each segment by only using flips. If // +// no flip is possible, and the flag 'steinerflag' is set, it then tries to // +// insert Steiner points near or in the segment. // +// // +//============================================================================// + +int tetgenmesh::recoversegments(arraypool *misseglist, int fullsearch, + int steinerflag) +{ + triface searchtet, spintet; + face sseg, *paryseg; + point startpt, endpt; + int success, idir; + int t1ver; + + long bak_inpoly_count = st_volref_count; + long bak_segref_count = st_segref_count; + + if (b->verbose > 1) { + printf(" Recover segments [%s level = %2d] #: %ld.\n", + (b->fliplinklevel > 0) ? "fixed" : "auto", + (b->fliplinklevel > 0) ? b->fliplinklevel : autofliplinklevel, + subsegstack->objects); + } + + // Loop until 'subsegstack' is empty. + while (subsegstack->objects > 0l) { + // seglist is used as a stack. + subsegstack->objects--; + paryseg = (face *) fastlookup(subsegstack, subsegstack->objects); + sseg = *paryseg; + + // Check if this segment has been recovered. + sstpivot1(sseg, searchtet); + if (searchtet.tet != NULL) { + continue; // Not a missing segment. + } + + startpt = sorg(sseg); + endpt = sdest(sseg); + + if (b->verbose > 2) { + printf(" Recover segment (%d, %d).\n", pointmark(startpt), + pointmark(endpt)); + } + + success = 0; + + if (recoveredgebyflips(startpt, endpt, &sseg, &searchtet, 0, idir)) { + success = 1; + } else { + // Try to recover it from the other direction. + if ((idir != (int) SELF_INTERSECT) && + recoveredgebyflips(endpt, startpt, &sseg, &searchtet, 0, idir)) { + success = 1; + } + } + + + if (!success && fullsearch) { + if ((idir != (int) SELF_INTERSECT) && + recoveredgebyflips(startpt, endpt, &sseg, &searchtet, fullsearch, idir)) { + success = 1; + } + } + + if (success) { + // Segment is recovered. Insert it. + // Let the segment remember an adjacent tet. + sstbond1(sseg, searchtet); + // Bond the segment to all tets containing it. + spintet = searchtet; + do { + tssbond1(spintet, sseg); + fnextself(spintet); + } while (spintet.tet != searchtet.tet); + } else { + if ((idir != (int) SELF_INTERSECT) && (steinerflag > 0)) { + // Try to recover the segment but do not split it. + if (add_steinerpt_to_recover_edge(startpt, endpt, &sseg, 0, 0, idir)) { + success = 1; + } + if (!success && (idir != (int) SELF_INTERSECT) && (steinerflag > 1)) { + // Split the segment. + if (add_steinerpt_to_recover_edge(startpt, endpt, &sseg, 1, 0, idir)) { + success = 1; + } + } + } + + if (!success) { + if (idir != (int) SELF_INTERSECT) { + if (misseglist != NULL) { + // Save this segment (recover it later). + misseglist->newindex((void **) &paryseg); + *paryseg = sseg; + } + } else { + // Save this segment (do not recover it again). + if (skipped_segment_list == NULL) { + skipped_segment_list = new arraypool(sizeof(badface), 10); + } + badface *bf; + skipped_segment_list->newindex((void **) &bf); + bf->init(); + bf->ss = sseg; + bf->forg = sorg(sseg); + bf->fdest = sdest(sseg); + bf->key = (double) shellmark(sseg); + smarktest3(sseg); + // Save all subfaces at this segment, do not recover them later. + if (skipped_facet_list == NULL) { + skipped_facet_list = new arraypool(sizeof(badface), 10); + } + face neighsh, spinsh; + bf->ss.shver = 0; + spivot(bf->ss, neighsh); + spinsh = neighsh; + while (spinsh.sh != NULL) { + skipped_facet_list->newindex((void **) &bf); + bf->init(); + bf->ss = spinsh; + bf->forg = (point) spinsh.sh[3]; + bf->fdest = (point) spinsh.sh[4]; + bf->fapex = (point) spinsh.sh[5]; + bf->key = (double) shellmark(spinsh); + smarktest3(spinsh); // do not recover it. + spivotself(spinsh); + if (spinsh.sh == neighsh.sh) break; + } + } + } // if (!success) + } + + } // while (subsegstack->objects > 0l) + + if (steinerflag) { + if (b->verbose > 1) { + // Report the number of added Steiner points. + if (st_volref_count > bak_inpoly_count) { + printf(" Add %ld Steiner points in volume.\n", + st_volref_count - bak_inpoly_count); + } + if (st_segref_count > bak_segref_count) { + printf(" Add %ld Steiner points in segments.\n", + st_segref_count - bak_segref_count); + } + } + } + + return 0; +} + +//============================================================================// +// // +// recoverfacebyflips() Recover a face by flips. // +// // +// 'pa', 'pb', and 'pc' are the three vertices of this face. This routine // +// tries to recover it in the tetrahedral mesh. It is assumed that the three // +// edges, i.e., pa->pb, pb->pc, and pc->pa all exist. // +// // +// If the face is recovered, it is returned by 'searchtet'. // +// // +// If 'searchsh' is not NULL, it is a subface to be recovered. Its vertices // +// must be pa, pb, and pc. It is mainly used to check self-intersections. // +// Another use of this subface is to split it when a Steiner point is found // +// inside this subface. // +// // +//============================================================================// + +int tetgenmesh::recoverfacebyflips(point pa, point pb, point pc, + face *searchsh, triface* searchtet, + int &dir, point *p1, point *p2) +{ + triface spintet, flipedge; + point pd, pe; + flipconstraints fc; + int types[2], poss[4], intflag; + int success; + int t1ver; + int i, j; + + + fc.fac[0] = pa; + fc.fac[1] = pb; + fc.fac[2] = pc; + fc.checkflipeligibility = 1; + + dir = (int) DISJOINT; + success = 0; + + for (i = 0; i < 3 && !success; i++) { + while (1) { + // Get a tet containing the edge [a,b]. + point2tetorg(fc.fac[i], *searchtet); + finddirection(searchtet, fc.fac[(i+1)%3]); + // Search the face [a,b,c] + spintet = *searchtet; + while (1) { + if (apex(spintet) == fc.fac[(i+2)%3]) { + // Found the face. + *searchtet = spintet; + // Return the face [a,b,c]. + for (j = i; j > 0; j--) { + eprevself(*searchtet); + } + dir = (int) SHAREFACE; + success = 1; + break; + } + fnextself(spintet); + if (spintet.tet == searchtet->tet) break; + } // while (1) + + if (success) break; + + // The face is missing. Try to recover it. + flipedge.tet = NULL; + // Find a crossing edge of this face. + spintet = *searchtet; + while (1) { + pd = apex(spintet); + pe = oppo(spintet); + if ((pd != dummypoint) && (pe != dummypoint)) { + // Check if [d,e] intersects [a,b,c] + intflag = tri_edge_test(pa, pb, pc, pd, pe, NULL, 1, types, poss); + if (intflag > 0) { + // By the assumption that all edges of the face exist, they can + // only intersect at a single point. + if (intflag == 2) { + // Go to the edge [d,e]. + edestoppo(spintet, flipedge); // [d,e,a,b] + if (searchsh != NULL) { + // Check the intersection type. + dir = types[0]; // return this value. + if ((types[0] == (int) ACROSSFACE) || + (types[0] == (int) ACROSSEDGE)) { + // Check if [e,d] is a segment. + if (issubseg(flipedge)) { + // This subface intersects with a segment. + if (!b->quiet && !b->nowarning) { + if (!b->quiet && !b->nowarning) { + printf("Warning: A segment and a facet intersect.\n"); + face sseg; tsspivot1(flipedge, sseg); + int segidx = getfacetindex(sseg); + point p1 = segmentendpointslist[segidx*2]; + point p2 = segmentendpointslist[segidx*2+1]; + printf(" segment: [%d,%d] tag(%d).\n", + pointmark(p1), pointmark(p2), shellmark(sseg)); + point *ppt = (point *) &(searchsh->sh[3]); + printf(" facet triangle: [%d,%d,%d] tag(%d)\n", + pointmark(ppt[0]), pointmark(ppt[1]), + pointmark(ppt[2]), shellmark(*searchsh)); + } + } + dir = (int) SELF_INTERSECT; + return 0; // Found a self-intersection. + } else { + // Check if [e,d] is an edge of a subface. + triface chkface = flipedge; + while (1) { + if (issubface(chkface)) break; + fsymself(chkface); + if (chkface.tet == flipedge.tet) break; + } + if (issubface(chkface)) { + if (searchsh != NULL) { + // Two subfaces are intersecting. + if (!b->quiet && !b->nowarning) { + printf("Warning: Found two facets intersect.\n"); + point *ppt = (point *) &(searchsh->sh[3]); + printf(" 1st facet triangle: [%d,%d,%d] tag(%d)\n", + pointmark(ppt[0]), pointmark(ppt[1]), + pointmark(ppt[2]), shellmark(*searchsh)); + face fa; tspivot(chkface, fa); + ppt = (point *) &(fa.sh[3]); + printf(" 2nd facet triangle: [%d,%d,%d] tag(%d)\n", + pointmark(ppt[0]), pointmark(ppt[1]), + pointmark(ppt[2]), shellmark(fa)); + } + dir = (int) SELF_INTERSECT; + } + return 0; // Found a self-intersection. + } + } + } else if (types[0] == TOUCHFACE) { + // This is possible when a Steiner point was added on it. + point touchpt, *parypt; + if (poss[1] == 0) { + touchpt = pd; // pd is a coplanar vertex. + } else { + touchpt = pe; // pe is a coplanar vertex. + } + if (!issteinerpoint(touchpt)) { + if (!b->quiet && !b->nowarning) { + printf("Warning: A vertex lies on a facet.\n"); + printf(" vertex : [%d]\n", pointmark(touchpt)); + point *ppt = (point *) &(searchsh->sh[3]); + printf(" facet triangle: [%d,%d,%d] tag(%d)\n", + pointmark(ppt[0]), pointmark(ppt[1]), + pointmark(ppt[2]), shellmark(*searchsh)); + } + dir = (int) SELF_INTERSECT; + return 0; + } else if (pointtype(touchpt) == FREESEGVERTEX) { + if (!b->quiet && !b->nowarning) { + printf("Warning: A segment and a facet intersect.\n"); + face sseg; + sdecode(point2sh(touchpt), sseg); + int segidx = getfacetindex(sseg); + point p1 = segmentendpointslist[segidx*2]; + point p2 = segmentendpointslist[segidx*2+1]; + printf(" segment: [%d,%d] tag(%d).\n", + pointmark(p1), pointmark(p2), shellmark(sseg)); + point *ppt = (point *) &(searchsh->sh[3]); + printf(" facet triangle: [%d,%d,%d] tag(%d)\n", + pointmark(ppt[0]), pointmark(ppt[1]), + pointmark(ppt[2]), shellmark(*searchsh)); + } + dir = (int) SELF_INTERSECT; + return 0; + } else if (pointtype(touchpt) == FREEFACETVERTEX) { + if (!b->quiet && !b->nowarning) { + printf("Warning: Found two facets intersect.\n"); + point *ppt = (point *) &(searchsh->sh[3]); + printf(" 1st facet triangle: [%d,%d,%d] tag(%d)\n", + pointmark(ppt[0]), pointmark(ppt[1]), + pointmark(ppt[2]), shellmark(*searchsh)); + face fa; + sdecode(point2sh(touchpt), fa); + ppt = (point *) &(fa.sh[3]); + printf(" 2nd facet triangle: [%d,%d,%d] tag(%d)\n", + pointmark(ppt[0]), pointmark(ppt[1]), + pointmark(ppt[2]), shellmark(fa)); + } + dir = (int) SELF_INTERSECT; + return 0; + } else if (pointtype(touchpt) == FREEVOLVERTEX) { + // A volume Steiner point was added in this subface. + // Split this subface by this point. + face checksh, *parysh; + int siloc = (int) ONFACE; + int sbowat = 0; // Only split this subface. A 1-to-3 flip. + setpointtype(touchpt, FREEFACETVERTEX); + sinsertvertex(touchpt, searchsh, NULL, siloc, sbowat, 0); + st_volref_count--; + st_facref_count++; + // Queue this vertex for removal. + subvertstack->newindex((void **) &parypt); + *parypt = touchpt; + // Queue new subfaces for recovery. + // Put all new subfaces into stack for recovery. + for (i = 0; i < caveshbdlist->objects; i++) { + // Get an old subface at edge [a, b]. + parysh = (face *) fastlookup(caveshbdlist, i); + spivot(*parysh, checksh); // The new subface [a, b, p]. + // Do not recover a deleted new face (degenerated). + if (checksh.sh[3] != NULL) { + subfacstack->newindex((void **) &parysh); + *parysh = checksh; + } + } + // Delete the old subfaces in sC(p). + for (i = 0; i < caveshlist->objects; i++) { + parysh = (face *) fastlookup(caveshlist, i); + shellfacedealloc(subfaces, parysh->sh); + } + // Clear working lists. + caveshlist->restart(); + caveshbdlist->restart(); + cavesegshlist->restart(); + // We can return this function. + searchsh->sh = NULL; // It has been split. + return 1; + } else { + // Other cases may be due to a bug or a PLC error. + //return report_selfint_face(pa, pb, pc, searchsh, &flipedge, + // intflag, types, poss); + terminatetetgen(this, 2); // to debug... + dir = (int) SELF_INTERSECT; + return 0; // Found a self-intersection. + } + } else { + // The other intersection types: ACROSSVERT, TOUCHEDGE, + // SHAREVERTEX should not be possible or due to a PLC error. + //return report_selfint_face(pa, pb, pc, searchsh, &flipedge, + // intflag, types, poss); + terminatetetgen(this, 2); // to report + dir = (int) SELF_INTERSECT; + return 0; + } + } // if (searchsh != NULL) + } else { // intflag == 4. Coplanar case. + // Found a mesh edge is coplanar with this subface. + // It migh be caused by a self-intersection. + terminatetetgen(this, 2); // report this bug + } + break; + } // if (intflag > 0) + } + fnextself(spintet); + if (spintet.tet == searchtet->tet) { + terminatetetgen(this, 2); + } + } // while (1) + // Try to flip the edge [d,e]. + // Remember a crossing edge. + *p1 = org(flipedge); + *p2 = dest(flipedge); + + if (removeedgebyflips(&flipedge, &fc) == 2) { + // A crossing edge is removed. + continue; + } + + // Unable to remove a crossing edge of this face. + break; + } // while (1) + } // i + + return success; +} + + +//============================================================================// +// // +// recoversubfaces() Recover all subfaces. // +// // +//============================================================================// + +int tetgenmesh::recoversubfaces(arraypool *misshlist, int steinerflag) +{ + triface searchtet, neightet, spintet; + face searchsh, neighsh, neineish, *parysh; + face bdsegs[3]; + point startpt, endpt, apexpt, *parypt; + point cross_e1 = NULL, cross_e2 = NULL; // endpoints of a crossing edge. + point steinerpt; + insertvertexflags ivf; + int success, dir; + int t1ver; + int i, j; + + if (b->verbose > 1) { + printf(" Recover subfaces [%s level = %2d] #: %ld.\n", + (b->fliplinklevel > 0) ? "fixed" : "auto", + (b->fliplinklevel > 0) ? b->fliplinklevel : autofliplinklevel, + subfacstack->objects); + } + + // Loop until 'subfacstack' is empty. + while (subfacstack->objects > 0l) { + + subfacstack->objects--; + parysh = (face *) fastlookup(subfacstack, subfacstack->objects); + searchsh = *parysh; + + if (searchsh.sh[3] == NULL) continue; // Skip a dead subface. + if (smarktest3ed(searchsh)) continue; // Skip a self-intersected subface. + + stpivot(searchsh, neightet); + if (neightet.tet != NULL) continue; // Skip a recovered subface. + + if (b->verbose > 2) { + printf(" Recover subface (%d, %d, %d).\n",pointmark(sorg(searchsh)), + pointmark(sdest(searchsh)), pointmark(sapex(searchsh))); + } + dir = (int) DISJOINT; // No self intersection is detected. + + // The three edges of the face need to be existed first. + for (i = 0; i < 3; i++) { + sspivot(searchsh, bdsegs[i]); + if (bdsegs[i].sh != NULL) { + // Check if this segment exist. + sstpivot1(bdsegs[i], searchtet); + if (searchtet.tet == NULL) { + // This segment is not recovered yet. Try to recover it. + success = 0; + startpt = sorg(searchsh); + endpt = sdest(searchsh); + if (recoveredgebyflips(startpt, endpt, &bdsegs[i], &searchtet, 0, dir)) { + success = 1; + } else { + if ((dir != (int) SELF_INTERSECT) && + recoveredgebyflips(endpt, startpt, &bdsegs[i], &searchtet, 0, dir)) { + success = 1; + } + } + if (success) { + // Segment is recovered. Insert it. + // Let the segment remember an adjacent tet. + sstbond1(bdsegs[i], searchtet); + // Bond the segment to all tets containing it. + spintet = searchtet; + do { + tssbond1(spintet, bdsegs[i]); + fnextself(spintet); + } while (spintet.tet != searchtet.tet); + } else { + // An edge of this subface is missing. Can't recover this subface. + // Delete any temporary segment that has been created. + for (j = (i - 1); j >= 0; j--) { + if (smarktest2ed(bdsegs[j])) { + spivot(bdsegs[j], neineish); + ssdissolve(neineish); + spivot(neineish, neighsh); + if (neighsh.sh != NULL) { + ssdissolve(neighsh); + } + sstpivot1(bdsegs[j], searchtet); + spintet = searchtet; + while (1) { + tssdissolve1(spintet); + fnextself(spintet); + if (spintet.tet == searchtet.tet) break; + } + shellfacedealloc(subsegs, bdsegs[j].sh); + } + } // j + break; // i + } // if (success) else + } // if (searchtet.tet == NULL) + } else { + // This edge is not a segment. + // Check whether it exists or not. + success = 0; + startpt = sorg(searchsh); + endpt = sdest(searchsh); + point2tetorg(startpt, searchtet); + finddirection(&searchtet, endpt); + if (dest(searchtet) == endpt) { + success = 1; // Found this edge. + } else { + // The edge is missing. Try to recover it. + if (recoveredgebyflips(startpt, endpt, &searchsh, &searchtet, 0, dir)) { + success = 1; + } else { + if ((dir != (int) SELF_INTERSECT) && + recoveredgebyflips(endpt, startpt, &searchsh, &searchtet, 0, dir)) { + success = 1; + } + } + } + + if (success) { + // This edge exists. + if (issubseg(searchtet)) { + // A segment already exists at this edge! + //terminatetetgen(this, 2); // to debug + //dir = SELF_INTERSECT; + // We contnue to recover this subface instead of reporting a + // SELF_INTERSECT event. + // Eventually, we will find "a duplicated triangle" event. + } + } + + if (success && (dir != SELF_INTERSECT)) { + // This edge exists. + //if (!issubseg(searchtet)) { + // Insert a temporary segment to protect this edge. + makeshellface(subsegs, &(bdsegs[i])); + setshvertices(bdsegs[i], startpt, endpt, NULL); + smarktest2(bdsegs[i]); // It's a temporary segment. + // Insert this segment into surface mesh. + ssbond(searchsh, bdsegs[i]); + spivot(searchsh, neighsh); + if (neighsh.sh != NULL) { + ssbond(neighsh, bdsegs[i]); + } + // Insert this segment into tetrahedralization. + sstbond1(bdsegs[i], searchtet); + // Bond the segment to all tets containing it. + spintet = searchtet; + do { + tssbond1(spintet, bdsegs[i]); + fnextself(spintet); + } while (spintet.tet != searchtet.tet); + //} + } else { + // An edge of this subface is missing. Can't recover this subface. + // Delete any temporary segment that has been created. + for (j = (i - 1); j >= 0; j--) { + if (smarktest2ed(bdsegs[j])) { + spivot(bdsegs[j], neineish); + ssdissolve(neineish); + spivot(neineish, neighsh); + if (neighsh.sh != NULL) { + ssdissolve(neighsh); + } + sstpivot1(bdsegs[j], searchtet); + spintet = searchtet; + while (1) { + tssdissolve1(spintet); + fnextself(spintet); + if (spintet.tet == searchtet.tet) break; + } + shellfacedealloc(subsegs, bdsegs[j].sh); + } + } // j + + break; + } + } + senextself(searchsh); + } // i + + if (i == 3) { + // All edges of this subface exist (or have been recovered). + // Recover the subface. + startpt = sorg(searchsh); + endpt = sdest(searchsh); + apexpt = sapex(searchsh); + + success = recoverfacebyflips(startpt, endpt, apexpt,&searchsh, &searchtet, + dir, &cross_e1, &cross_e2); + + // Delete any temporary segment that has been created. + for (j = 0; j < 3; j++) { + if (smarktest2ed(bdsegs[j])) { + spivot(bdsegs[j], neineish); + ssdissolve(neineish); + spivot(neineish, neighsh); + if (neighsh.sh != NULL) { + ssdissolve(neighsh); + } + sstpivot1(bdsegs[j], neightet); + spintet = neightet; + while (1) { + tssdissolve1(spintet); + fnextself(spintet); + if (spintet.tet == neightet.tet) break; + } + shellfacedealloc(subsegs, bdsegs[j].sh); + } + } // j + + if (success) { + if (searchsh.sh != NULL) { + // Face is recovered. Insert it. + face chkface; + tspivot(searchtet, chkface); + if (chkface.sh == NULL) { + tsbond(searchtet, searchsh); + fsymself(searchtet); + sesymself(searchsh); + tsbond(searchtet, searchsh); + } else { + // A duplicated facet is found. + if (shellmark(chkface) == shellmark(searchsh)) { + if (!b->quiet && !b->nowarning) { + point *ppt = (point *) &(searchsh.sh[3]); + printf("Warning: A duplicated triangle (%d,%d,%d) tag(%d) is ignored.\n", + pointmark(ppt[0]), pointmark(ppt[1]), pointmark(ppt[2]), + shellmark(searchsh)); + } + duplicated_facets_count++; + smarktest3(searchsh); // do not recover it. + sinfect(searchsh); // it is an igonred duplicated facet. + } else { + if (!b->quiet && !b->nowarning) { + point *ppt = (point *) &(chkface.sh[3]); + printf("Warning: Two facets are overlapping at triangle (%d,%d,%d).\n", + pointmark(ppt[0]), pointmark(ppt[1]), pointmark(ppt[2])); + printf(" 1st facet tag(%d).\n", shellmark(chkface)); + printf(" 2nd facet tag(%d).\n", shellmark(searchsh)); + } + dir = SELF_INTERSECT; + success = 0; + } + } + } + } else { + if ((dir != (int) SELF_INTERSECT) && steinerflag) { + // Add a Steiner point at the barycenter of this subface. + REAL ip[3], u; + + //planelineint(startpt, endpt, apexpt, cross_e1, cross_e2, ip, &u); + + point fpt[3], ept[2]; + sort_3pts(startpt, endpt, apexpt, fpt); + sort_2pts(cross_e1, cross_e2, ept); + planelineint(fpt[0], fpt[1], fpt[2], ept[0], ept[1], ip, &u); + + makepoint(&steinerpt, FREEFACETVERTEX); + if ((u > 0.) && (u < 1.)) { + for (j = 0; j < 3; j++) steinerpt[j] = ip[j]; + // Make sure that this Steiner point is inside the subface. + if (is_collinear_at(steinerpt, startpt, endpt) || + is_collinear_at(steinerpt, endpt, apexpt) || + is_collinear_at(steinerpt, apexpt, startpt)) { + // Add the barycenter of this missing subface. + for (j = 0; j < 3; j++) { + steinerpt[j] = (startpt[j] + endpt[j] + apexpt[j]) / 3.0; + } + // Avoid creating a very skinny triangle + if (is_collinear_at(steinerpt, startpt, endpt) || + is_collinear_at(steinerpt, endpt, apexpt) || + is_collinear_at(steinerpt, apexpt, startpt)) { + terminatetetgen(this, 2); + } + } + } else { + // Add the barycenter of this missing subface. + for (j = 0; j < 3; j++) { + steinerpt[j] = (startpt[j] + endpt[j] + apexpt[j]) / 3.0; + } + // Avoid creating a very skinny triangle + if (is_collinear_at(steinerpt, startpt, endpt) || + is_collinear_at(steinerpt, endpt, apexpt) || + is_collinear_at(steinerpt, apexpt, startpt)) { + //assert(0); // to debug... + terminatetetgen(this, 2); + } + } + + // for create_a_shorter_edge(). + setpoint2sh(steinerpt, sencode(searchsh)); + + ivf.init(); + point2tetorg(startpt, searchtet); // Start from 'searchtet'. + ivf.iloc = (int) OUTSIDE; // Need point location. + ivf.bowywat = 1; + ivf.lawson = 2; // do recover delaunay. + ivf.rejflag = 0; + ivf.chkencflag = 0; + ivf.sloc = (int) ONFACE; // "searchsh" must be the subface. + ivf.sbowywat = 1; // split subface mesh separately, new subfaces + // are pushed into "subfacestack". + ivf.splitbdflag = 0; + ivf.validflag = 1; + ivf.respectbdflag = 1; + ivf.assignmeshsize = b->metric; + + if (insertpoint(steinerpt, &searchtet, &searchsh, NULL, &ivf)) { + if (flipstack != NULL) { + recoverdelaunay(); + } + + // Save this Steiner point (for removal). + // Re-use the array 'subvertstack'. + subvertstack->newindex((void **) &parypt); + *parypt = steinerpt; + + st_facref_count++; + if (steinerleft > 0) steinerleft--; + + success = 1; // This subface has been split. + } else { + // Failed to insert this point. + if (ivf.iloc == NEARVERTEX) { + // Check if this subface is nearly "touched" by an existing + // vertex. If so, report an event. + point chkpt = org(searchtet); + REAL dist = distance(steinerpt, chkpt); + if (dist < minedgelength) { + if (!issteinerpoint(chkpt)) { + if (!b->quiet && !b->nowarning) { // -no -Q -W + printf("Warning: A facet (%d,%d,%d) and a vertex %d are very close.\n", + pointmark(sorg(searchsh)), pointmark(sdest(searchsh)), + pointmark(sapex(searchsh)), pointmark(chkpt)); + double dd = dist; // distance(steinerpt, nearpt); + //assert(dd > 0.); + //minedgelength = longest * b->epsilon; + //assert(dd < minedgelength); + double new_dd = minedgelength - dd / longest; + double new_eps = new_dd / longest; + printf("You can ignore this warning by using -T%e (default is %e) option.\n", + new_eps, b->epsilon); + printf(" This will allow a short edge (len = %.17g) (default limit is %g).\n", + dd, minedgelength); + } + dir = SELF_INTERSECT; + } + } else { + // Report other types of possible (nearly) self-intersection. + terminatetetgen(this, 2); + dir = SELF_INTERSECT; + } + } + + if ((dir != SELF_INTERSECT) && (steinerflag >= 2)) { + if (ivf.iloc == NULLCAVITY) { + // Collect a list of bad quality tets which prevent the + // insertion of this Steiner point. + terminatetetgen(this, 2); + point2tetorg(startpt, searchtet); + ivf.iloc = (int) OUTSIDE; // re-do point location. + ivf.collect_inial_cavity_flag = 1; + insertpoint(steinerpt, &searchtet, &searchsh, NULL, &ivf); + } else { + terminatetetgen(this, 2); // report a bug. + } + } // if (steinerflag >= 2) + + pointdealloc(steinerpt); + steinerpt = NULL; + success = 0; // queue this subface. + } + } // if (steinerflag) + } + } else { // when i < 3 + // An edge (startpt, endpt) of this subface is missing. + if ((dir != (int) SELF_INTERSECT) && (steinerflag > 0)) { + // Split this edge by adding a Steiner point. + // Find the first face/edge crossed by the edge (startpt, endpt). + point2tetorg(startpt, searchtet); + dir = finddirection(&searchtet, endpt); + + + if (dir != (int) SELF_INTERSECT) { + // Insert a Steiner point. + REAL ip[3], u; + + enextself(searchtet); + point pa = org(searchtet); + point pb = dest(searchtet); + point pd = oppo(searchtet); + + //planelineint(pa, pb, pd, startpt, endpt, ip, &u); + + point fpt[3], ept[2]; + sort_3pts(pa, pb, pd, fpt); + sort_2pts(startpt, endpt, ept); + planelineint(fpt[0], fpt[1], fpt[2], ept[0], ept[1], ip, &u); + + makepoint(&steinerpt, FREEFACETVERTEX); + for (j = 0; j < 3; j++) steinerpt[j] = ip[j]; + + ivf.init(); + + ivf.refinetet = searchtet; // bakup the crossing face/edge. + + triface tmptet = searchtet; + ivf.iloc = locate(steinerpt, &tmptet); + + if (ivf.iloc == ONVERTEX) { + // the origin of tmptet is co-incident with this Steiner point. + searchtet = tmptet; + } + //else if (ivf.iloc == ONFACE) { + // searchtet = tmptet; + //} else if (ivf.iloc == ONEDGE) { + // searchtet = tmptet; + //} + else { + //assert(0); // to debug... + // Make sure that we can split the crossing edge/face (a,b,d). + if (dir == ACROSSFACE) { + ivf.iloc = (int) ONFACE; + //ivf.refineflag = 4; // Check if the crossing face is removed. + } else if (dir == ACROSSEDGE) { + ivf.iloc = (int) ONEDGE; + //ivf.refineflag = 8; // Check if the crossing edge is removed. + } else { + terminatetetgen(this, 2); + } + //ivf.iloc = (int) OUTSIDE; // do point location. + //ivf.refinetet = searchtet; // The crossing face/edge. + } + + ivf.bowywat = 1; + ivf.lawson = 2; // do recover delaunay. + ivf.rejflag = 0; + ivf.chkencflag = 0; + ivf.sloc = (int) ONEDGE; // "searchsh" must be the subedge. + ivf.sbowywat = 1; // split subface mesh separately, new subfaces + // are pushed into "subfacestack". + ivf.splitbdflag = 0; + ivf.validflag = 1; + ivf.respectbdflag = 1; + ivf.assignmeshsize = b->metric; + + //if (steinerflag >= 2) { + // Skip NEARVERTEX. This may create a very short edge. + //ivf.ignore_near_vertex = 1; + //} + + // searchsh may contain a missing segment. + // After splitting this subface, this segment must also be split. + // the two missing subsegments are stored in "subsegstack". + face misseg, *splitseg = NULL; + sspivot(searchsh, misseg); + if (misseg.sh != NULL) { + splitseg = &misseg; + setpointtype(steinerpt, FREESEGVERTEX); // default is FREEFACETVERTEX. + // for create_a_shorter_edge() + setpoint2sh(steinerpt, sencode(misseg)); + } else { + // for create_a_shorter_edge() + setpoint2sh(steinerpt, sencode(searchsh)); + } + + bool splitseg_flag = (splitseg != NULL); + int bak_iloc = ivf.iloc; // for collect_initial_cavity + + if (insertpoint(steinerpt, &searchtet, &searchsh, splitseg, &ivf)) { + if (flipstack != NULL) { + recoverdelaunay(); + } + + // Save this Steiner point (for removal). + // Re-use the array 'subvertstack'. + subvertstack->newindex((void **) &parypt); + *parypt = steinerpt; + + if (splitseg_flag) { + st_segref_count++; + } else { + st_facref_count++; + } + if (steinerleft > 0) steinerleft--; + + success = 1; // This subface has been split. + } else { + // Failed to insert this point. + if (ivf.iloc == NEARVERTEX) { + // Check if this subface is nearly "touched" by an existing + // vertex. If so, report an event. + point chkpt = org(searchtet); + REAL dist = distance(steinerpt, chkpt); // for reporting. + if (!issteinerpoint(chkpt)) { + if (!b->quiet && !b->nowarning) { + if (splitseg_flag) { + printf("Warning: A segment (%d,%d) and a vertex %d are very close.\n", + pointmark(sorg(searchsh)), pointmark(sdest(searchsh)), + pointmark(chkpt)); + } else { + printf("Warning: A facet (%d,%d,%d) and a vertex %d are very close.\n", + pointmark(sorg(searchsh)), pointmark(sdest(searchsh)), + pointmark(sapex(searchsh)), pointmark(chkpt)); + } + //printf(" Will result a vert short edge (len=%.17g) (< %.17g)\n", + // dist, minedgelength); + double dd = dist; // distance(steinerpt, nearpt); + //assert(dd > 0.); + //minedgelength = longest * b->epsilon; + //assert(dd < minedgelength); + double new_dd = minedgelength - dd / longest; + double new_eps = new_dd / longest; + printf("You can ignore this warning by using -T%e (default is %e) option.\n", + new_eps, b->epsilon); + printf(" This will allow a short edge (len = %.17g) (default limit is %g).\n", + dd, minedgelength); + } + dir = SELF_INTERSECT; + } + } + + if ((dir != SELF_INTERSECT) && (steinerflag >= 2)) { + success = 0; // queue this subface. + // Failed to split a crossing edge/face. + if ((ivf.iloc == ONVERTEX) || (ivf.iloc == NEARVERTEX)) { + // Get the existing vertex (must be a Steiner point). + if (dir == ACROSSEDGE) { + int idir; + if (add_steinerpt_to_recover_edge(startpt, endpt, NULL, 0, 1, idir)) { + // A Steiner point is inserted. + // Push this subface back to stack, to recover it again. + subfacstack->newindex((void **) &parysh); + *parysh = searchsh; + success = 1; + } + } else if (dir == ACROSSFACE) { + // to do... + terminatetetgen(this, 2); + } else { + terminatetetgen(this, 2); // not possible. + } + } else if (ivf.iloc == NULLCAVITY) { + // Collect a list of bad quality tets which prevent the + // insertion of this Steiner point. + terminatetetgen(this, 2); + } else { + terminatetetgen(this, 2); // report a bug. + } + } // if (steinerflag >= 2) + + pointdealloc(steinerpt); + steinerpt = NULL; + //success = 0; // queue this subface. + } + } // if (dir != SELF_INTERSECT) + } // if ((dir != SELF_INTERSECT) && steinerflag > 0) + } // if (i == 2) else + + if (success) continue; // recover the next subface. + + if (dir == (int) SELF_INTERSECT) { + // Found a self-intersection. This subface cannot be recovered. + // Save it in a separate list, and remove it from the subface pool. + if (skipped_facet_list == NULL) { + skipped_facet_list = new arraypool(sizeof(badface), 10); + } + badface *bf; + skipped_facet_list->newindex((void **) &bf); + bf->init(); + bf->ss = searchsh; + bf->forg = (point) searchsh.sh[3]; + bf->fdest = (point) searchsh.sh[4]; + bf->fapex = (point) searchsh.sh[5]; + bf->key = (double) shellmark(searchsh); + smarktest3(searchsh); // do not recover it later. + continue; // recover the next subface. + } + + // This subface is missing. + if (steinerflag >= 2) { + terminatetetgen(this, 2); + } // if (steinerflag >= 2) + + // Save this subface to recover it later. + misshlist->newindex((void **) &parysh); + *parysh = searchsh; + } // while (subfacstack->objects > 0l) + + return 0; +} + +//============================================================================// +// // +// getvertexstar() Return the star of a vertex. // +// // +// If the flag 'fullstar' is set, return the complete star of this vertex. // +// Otherwise, only a part of the star which is bounded by facets is returned. // +// // +// 'tetlist' returns the list of tets in the star of the vertex 'searchpt'. // +// Every tet in 'tetlist' is at the face opposing to 'searchpt'. // +// // +// 'vertlist' returns the list of vertices in the star (exclude 'searchpt'). // +// // +// 'shlist' returns the list of subfaces in the star. Each subface must face // +// to the interior of this star. // +// // +//============================================================================// + +int tetgenmesh::getvertexstar(int fullstar, point searchpt, arraypool* tetlist, + arraypool* vertlist, arraypool* shlist) +{ + triface searchtet, neightet, *parytet; + face checksh, *parysh; + point pt, *parypt; + int collectflag; + int t1ver; + int i, j; + + point2tetorg(searchpt, searchtet); + + // Go to the opposite face (the link face) of the vertex. + enextesymself(searchtet); + //assert(oppo(searchtet) == searchpt); + infect(searchtet); // Collect this tet (link face). + tetlist->newindex((void **) &parytet); + *parytet = searchtet; + if (vertlist != NULL) { + // Collect three (link) vertices. + j = (searchtet.ver & 3); // The current vertex index. + for (i = 1; i < 4; i++) { + pt = (point) searchtet.tet[4 + ((j + i) % 4)]; + pinfect(pt); + vertlist->newindex((void **) &parypt); + *parypt = pt; + } + } + + collectflag = 1; + esym(searchtet, neightet); + if (issubface(neightet)) { + if (shlist != NULL) { + tspivot(neightet, checksh); + if (!sinfected(checksh)) { + // Collect this subface (link edge). + sinfect(checksh); + shlist->newindex((void **) &parysh); + *parysh = checksh; + } + } + if (!fullstar) { + collectflag = 0; + } + } + if (collectflag) { + fsymself(neightet); // Goto the adj tet of this face. + esymself(neightet); // Goto the oppo face of this vertex. + // assert(oppo(neightet) == searchpt); + infect(neightet); // Collect this tet (link face). + tetlist->newindex((void **) &parytet); + *parytet = neightet; + if (vertlist != NULL) { + // Collect its apex. + pt = apex(neightet); + pinfect(pt); + vertlist->newindex((void **) &parypt); + *parypt = pt; + } + } // if (collectflag) + + // Continue to collect all tets in the star. + for (i = 0; i < tetlist->objects; i++) { + searchtet = * (triface *) fastlookup(tetlist, i); + // Note that 'searchtet' is a face opposite to 'searchpt', and the neighbor + // tet at the current edge is already collected. + // Check the neighbors at the other two edges of this face. + for (j = 0; j < 2; j++) { + collectflag = 1; + enextself(searchtet); + esym(searchtet, neightet); + if (issubface(neightet)) { + if (shlist != NULL) { + tspivot(neightet, checksh); + if (!sinfected(checksh)) { + // Collect this subface (link edge). + sinfect(checksh); + shlist->newindex((void **) &parysh); + *parysh = checksh; + } + } + if (!fullstar) { + collectflag = 0; + } + } + if (collectflag) { + fsymself(neightet); + if (!infected(neightet)) { + esymself(neightet); // Go to the face opposite to 'searchpt'. + infect(neightet); + tetlist->newindex((void **) &parytet); + *parytet = neightet; + if (vertlist != NULL) { + // Check if a vertex is collected. + pt = apex(neightet); + if (!pinfected(pt)) { + pinfect(pt); + vertlist->newindex((void **) &parypt); + *parypt = pt; + } + } + } // if (!infected(neightet)) + } // if (collectflag) + } // j + } // i + + + // Uninfect the list of tets and vertices. + for (i = 0; i < tetlist->objects; i++) { + parytet = (triface *) fastlookup(tetlist, i); + uninfect(*parytet); + } + + if (vertlist != NULL) { + for (i = 0; i < vertlist->objects; i++) { + parypt = (point *) fastlookup(vertlist, i); + puninfect(*parypt); + } + } + + if (shlist != NULL) { + for (i = 0; i < shlist->objects; i++) { + parysh = (face *) fastlookup(shlist, i); + suninfect(*parysh); + } + } + + return (int) tetlist->objects; +} + +//============================================================================// +// // +// getedge() Get a tetrahedron having the two endpoints. // +// // +// The method here is to search the second vertex in the link faces of the // +// first vertex. The global array 'cavetetlist' is re-used for searching. // +// // +// This function is used for the case when the mesh is non-convex. Otherwise, // +// the function finddirection() should be faster than this. // +// // +//============================================================================// + +int tetgenmesh::getedge(point e1, point e2, triface *tedge) +{ + triface searchtet, neightet, *parytet; + point pt; + int done; + int i, j; + + if (e1 == NULL || e2 == NULL) { + return 0; + } + if ((pointtype(e1) == UNUSEDVERTEX) || + (pointtype(e2) == UNUSEDVERTEX)) { + return 0; + } + + // Quickly check if 'tedge' is just this edge. + if (!isdeadtet(*tedge)) { + if (org(*tedge) == e1) { + if (dest(*tedge) == e2) { + return 1; + } + } else if (org(*tedge) == e2) { + if (dest(*tedge) == e1) { + esymself(*tedge); + return 1; + } + } + } + + // Search for the edge [e1, e2]. + point2tetorg(e1, *tedge); + finddirection(tedge, e2); + if (dest(*tedge) == e2) { + return 1; + } else { + // Search for the edge [e2, e1]. + point2tetorg(e2, *tedge); + finddirection(tedge, e1); + if (dest(*tedge) == e1) { + esymself(*tedge); + return 1; + } + } + + + // Go to the link face of e1. + point2tetorg(e1, searchtet); + enextesymself(searchtet); + arraypool *tetlist = cavebdrylist; + + // Search e2. + for (i = 0; i < 3; i++) { + pt = apex(searchtet); + if (pt == e2) { + // Found. 'searchtet' is [#,#,e2,e1]. + eorgoppo(searchtet, *tedge); // [e1,e2,#,#]. + return 1; + } + enextself(searchtet); + } + + // Get the adjacent link face at 'searchtet'. + fnext(searchtet, neightet); + esymself(neightet); + // assert(oppo(neightet) == e1); + pt = apex(neightet); + if (pt == e2) { + // Found. 'neightet' is [#,#,e2,e1]. + eorgoppo(neightet, *tedge); // [e1,e2,#,#]. + return 1; + } + + // Continue searching in the link face of e1. + infect(searchtet); + tetlist->newindex((void **) &parytet); + *parytet = searchtet; + infect(neightet); + tetlist->newindex((void **) &parytet); + *parytet = neightet; + + done = 0; + + for (i = 0; (i < tetlist->objects) && !done; i++) { + parytet = (triface *) fastlookup(tetlist, i); + searchtet = *parytet; + for (j = 0; (j < 2) && !done; j++) { + enextself(searchtet); + fnext(searchtet, neightet); + if (!infected(neightet)) { + esymself(neightet); + pt = apex(neightet); + if (pt == e2) { + // Found. 'neightet' is [#,#,e2,e1]. + eorgoppo(neightet, *tedge); + done = 1; + } else { + infect(neightet); + tetlist->newindex((void **) &parytet); + *parytet = neightet; + } + } + } // j + } // i + + // Uninfect the list of visited tets. + for (i = 0; i < tetlist->objects; i++) { + parytet = (triface *) fastlookup(tetlist, i); + uninfect(*parytet); + } + tetlist->restart(); + + return done; +} + +//============================================================================// +// // +// reduceedgesatvertex() Reduce the number of edges at a given vertex. // +// // +// 'endptlist' contains the endpoints of edges connecting at the vertex. // +// // +//============================================================================// + +int tetgenmesh::reduceedgesatvertex(point startpt, arraypool* endptlist) +{ + triface searchtet; + point *pendpt, *parypt; + enum interresult dir; + flipconstraints fc; + int reduceflag; + int count; + int n, i, j; + + + fc.remvert = startpt; + fc.checkflipeligibility = 1; + + while (1) { + + count = 0; + + for (i = 0; i < endptlist->objects; i++) { + pendpt = (point *) fastlookup(endptlist, i); + if (*pendpt == dummypoint) { + continue; // Do not reduce a virtual edge. + } + reduceflag = 0; + // Find the edge. + if (nonconvex) { + if (getedge(startpt, *pendpt, &searchtet)) { + dir = ACROSSVERT; + } else { + // The edge does not exist (was flipped). + dir = INTERSECT; + } + } else { + point2tetorg(startpt, searchtet); + dir = finddirection(&searchtet, *pendpt); + } + if (dir == ACROSSVERT) { + if (dest(searchtet) == *pendpt) { + // Do not flip a segment. + if (!issubseg(searchtet)) { + n = removeedgebyflips(&searchtet, &fc); + if (n == 2) { + reduceflag = 1; + } + } + } + else { + terminatetetgen(this, 2); + } + } else { + // The edge has been flipped. + reduceflag = 1; + } + if (reduceflag) { + count++; + // Move the last vertex into this slot. + j = endptlist->objects - 1; + parypt = (point *) fastlookup(endptlist, j); + *pendpt = *parypt; + endptlist->objects--; + i--; + } + } // i + + if (count == 0) { + // No edge is reduced. + break; + } + + } // while (1) + + return (int) endptlist->objects; +} + +//============================================================================// +// // +// removevertexbyflips() Remove a vertex by flips. // +// // +// This routine attempts to remove the given vertex 'rempt' (p) from the // +// tetrahedralization (T) by a sequence of flips. // +// // +// The algorithm used here is a simple edge reduce method. Suppose there are // +// n edges connected at p. We try to reduce the number of edges by flipping // +// any edge (not a segment) that is connecting at p. // +// // +// Unless T is a Delaunay tetrahedralization, there is no guarantee that 'p' // +// can be successfully removed. // +// // +//============================================================================// + +int tetgenmesh::removevertexbyflips(point steinerpt) +{ + triface *fliptets = NULL, wrktets[4]; + triface searchtet, spintet, neightet; + face parentsh, spinsh, checksh; + face leftseg, rightseg, checkseg; + point lpt = NULL, rpt = NULL, apexpt; //, *parypt; + flipconstraints fc; + enum verttype vt; + enum locateresult loc; + int valence, removeflag; + int slawson; + int t1ver; + int n, i; + + vt = pointtype(steinerpt); + + + if (vt == FREESEGVERTEX) { + sdecode(point2sh(steinerpt), leftseg); + leftseg.shver = 0; + if (sdest(leftseg) == steinerpt) { + senext(leftseg, rightseg); + spivotself(rightseg); + rightseg.shver = 0; + } else { + rightseg = leftseg; + senext2(rightseg, leftseg); + spivotself(leftseg); + leftseg.shver = 0; + } + lpt = sorg(leftseg); + rpt = sdest(rightseg); + + // Check if both leftseg and rightseg are recovered in tet mesh. + sstpivot1(leftseg, neightet); + if (neightet.tet == NULL) { + return 0; // Do not remove this Steiner point. + } + sstpivot1(rightseg, neightet); + if (neightet.tet == NULL) { + return 0; // Do not remove this Steiner point. + } + + if (b->verbose > 2) { + printf(" Removing Steiner point %d in segment (%d, %d).\n", + pointmark(steinerpt), pointmark(lpt), pointmark(rpt)); + + } + } else if (vt == FREEFACETVERTEX) { + if (b->verbose > 2) { + printf(" Removing Steiner point %d in facet.\n", + pointmark(steinerpt)); + } + } else if (vt == FREEVOLVERTEX) { + if (b->verbose > 2) { + printf(" Removing Steiner point %d in volume.\n", + pointmark(steinerpt)); + } + } else if (vt == VOLVERTEX) { + if (b->verbose > 2) { + printf(" Removing a point %d in volume.\n", + pointmark(steinerpt)); + } + } else { + // It is not a Steiner point. + return 0; + } + + // Try to reduce the number of edges at 'p' by flips. + getvertexstar(1, steinerpt, cavetetlist, cavetetvertlist, NULL); + cavetetlist->restart(); // This list may be re-used. + if (cavetetvertlist->objects > 3l) { + valence = reduceedgesatvertex(steinerpt, cavetetvertlist); + } else { + valence = cavetetvertlist->objects; + } + cavetetvertlist->restart(); + + removeflag = 0; + + if (valence == 4) { + // Only 4 vertices (4 tets) left! 'p' is inside the convex hull of the 4 + // vertices. This case is due to that 'p' is not exactly on the segment. + point2tetorg(steinerpt, searchtet); + loc = INTETRAHEDRON; + removeflag = 1; + } else if (valence == 5) { + // There are 5 edges. + if (vt == FREESEGVERTEX) { + sstpivot1(leftseg, searchtet); + if (org(searchtet) != steinerpt) { + esymself(searchtet); + } + i = 0; // Count the numbe of tet at the edge [p,lpt]. + neightet.tet = NULL; // Init the face. + spintet = searchtet; + while (1) { + i++; + if (apex(spintet) == rpt) { + // Remember the face containing the edge [lpt, rpt]. + neightet = spintet; + } + fnextself(spintet); + if (spintet.tet == searchtet.tet) break; + } + if (i == 3) { + // This case has been checked below. + } else if (i == 4) { + // There are 4 tets sharing at [p,lpt]. There must be 4 tets sharing + // at [p,rpt]. There must be a face [p, lpt, rpt]. + if (apex(neightet) == rpt) { + // The edge (segment) has been already recovered! + // Check if a 6-to-2 flip is possible (to remove 'p'). + // Let 'searchtet' be [p,d,a,b] + esym(neightet, searchtet); + enextself(searchtet); + // Check if there are exactly three tets at edge [p,d]. + wrktets[0] = searchtet; // [p,d,a,b] + for (i = 0; i < 2; i++) { + fnext(wrktets[i], wrktets[i+1]); // [p,d,b,c], [p,d,c,a] + } + if (apex(wrktets[0]) == oppo(wrktets[2])) { + loc = ONFACE; + removeflag = 1; + } + } + } + } else if (vt == FREEFACETVERTEX) { + // It is possible to do a 6-to-2 flip to remove the vertex. + point2tetorg(steinerpt, searchtet); + // Get the three faces of 'searchtet' which share at p. + // All faces has p as origin. + wrktets[0] = searchtet; + wrktets[1] = searchtet; + esymself(wrktets[1]); + enextself(wrktets[1]); + wrktets[2] = searchtet; + eprevself(wrktets[2]); + esymself(wrktets[2]); + // All internal edges of the six tets have valance either 3 or 4. + // Get one edge which has valance 3. + searchtet.tet = NULL; + for (i = 0; i < 3; i++) { + spintet = wrktets[i]; + valence = 0; + while (1) { + valence++; + fnextself(spintet); + if (spintet.tet == wrktets[i].tet) break; + } + if (valence == 3) { + // Found the edge. + searchtet = wrktets[i]; + break; + } + } + // Note, we do not detach the three subfaces at p. + // They will be removed within a 4-to-1 flip. + loc = ONFACE; + removeflag = 1; + } + //removeflag = 1; + } + + if (!removeflag) { + if (vt == FREESEGVERTEX) { + // Check is it possible to recover the edge [lpt,rpt]. + // The condition to check is: Whether each tet containing 'leftseg' is + // adjacent to a tet containing 'rightseg'. + sstpivot1(leftseg, searchtet); + if (org(searchtet) != steinerpt) { + esymself(searchtet); + } + spintet = searchtet; + while (1) { + // Go to the bottom face of this tet. + eprev(spintet, neightet); + esymself(neightet); // [steinerpt, p1, p2, lpt] + // Get the adjacent tet. + fsymself(neightet); // [p1, steinerpt, p2, rpt] + if (oppo(neightet) != rpt) { + // Found a non-matching adjacent tet. + break; + } + { + // [2017-10-15] Check if the tet is inverted? + point chkp1 = org(neightet); + point chkp2 = apex(neightet); + REAL chkori = orient3d(rpt, lpt, chkp1, chkp2); + if (chkori >= 0.0) { + // Either inverted or degenerated. + break; + } + } + fnextself(spintet); + if (spintet.tet == searchtet.tet) { + // 'searchtet' is [p,d,p1,p2]. + loc = ONEDGE; + removeflag = 1; + break; + } + } + } // if (vt == FREESEGVERTEX) + } + + if (!removeflag) { + if (vt == FREESEGVERTEX) { + // Check if the edge [lpt, rpt] exists. + if (getedge(lpt, rpt, &searchtet)) { + // We have recovered this edge. Shift the vertex into the volume. + // We can recover this edge if the subfaces are not recovered yet. + if (!checksubfaceflag) { + // Remove the vertex from the surface mesh. + // This will re-create the segment [lpt, rpt] and re-triangulate + // all the facets at the segment. + // Detach the subsegments from their surrounding tets. + for (i = 0; i < 2; i++) { + checkseg = (i == 0) ? leftseg : rightseg; + sstpivot1(checkseg, neightet); + spintet = neightet; + while (1) { + tssdissolve1(spintet); + fnextself(spintet); + if (spintet.tet == neightet.tet) break; + } + sstdissolve1(checkseg); + } // i + slawson = 1; // Do lawson flip after removal. + spivot(rightseg, parentsh); // 'rightseg' has p as its origin. + sremovevertex(steinerpt, &parentsh, &rightseg, slawson); + // Clear the list for new subfaces. + caveshbdlist->restart(); + // Insert the new segment. + sstbond1(rightseg, searchtet); + spintet = searchtet; + while (1) { + tssbond1(spintet, rightseg); + fnextself(spintet); + if (spintet.tet == searchtet.tet) break; + } + // The Steiner point has been shifted into the volume. + setpointtype(steinerpt, FREEVOLVERTEX); + st_segref_count--; + st_volref_count++; + return 1; + } // if (!checksubfaceflag) + } // if (getedge(...)) + } // if (vt == FREESEGVERTEX) + } // if (!removeflag) + + if (!removeflag) { + return 0; + } + + if (vt == FREESEGVERTEX) { + // Detach the subsegments from their surronding tets. + for (i = 0; i < 2; i++) { + checkseg = (i == 0) ? leftseg : rightseg; + sstpivot1(checkseg, neightet); + spintet = neightet; + while (1) { + tssdissolve1(spintet); + fnextself(spintet); + if (spintet.tet == neightet.tet) break; + } + sstdissolve1(checkseg); + } // i + if (checksubfaceflag) { + // Detach the subfaces at the subsegments from their attached tets. + for (i = 0; i < 2; i++) { + checkseg = (i == 0) ? leftseg : rightseg; + spivot(checkseg, parentsh); + if (parentsh.sh != NULL) { + spinsh = parentsh; + while (1) { + stpivot(spinsh, neightet); + if (neightet.tet != NULL) { + tsdissolve(neightet); + } + sesymself(spinsh); + stpivot(spinsh, neightet); + if (neightet.tet != NULL) { + tsdissolve(neightet); + } + stdissolve(spinsh); + spivotself(spinsh); // Go to the next subface. + if (spinsh.sh == parentsh.sh) break; + } + } + } // i + } // if (checksubfaceflag) + } + + if (loc == INTETRAHEDRON) { + // Collect the four tets containing 'p'. + fliptets = new triface[4]; + fliptets[0] = searchtet; // [p,d,a,b] + for (i = 0; i < 2; i++) { + fnext(fliptets[i], fliptets[i+1]); // [p,d,b,c], [p,d,c,a] + } + eprev(fliptets[0], fliptets[3]); + fnextself(fliptets[3]); // it is [a,p,b,c] + eprevself(fliptets[3]); + esymself(fliptets[3]); // [a,b,c,p]. + if (vt == FREEFACETVERTEX) { + // [2018-03-08] Check if the last 4-to-1 flip is valid. + // fliptets[0],[1],[2] are [p,d,a,b],[p,d,b,c],[p,d,c,a] + triface checktet, chkface; + for (i = 0; i < 3; i++) { + enext(fliptets[i], checktet); + esymself(checktet); // [a,d,b,p],[b,d,c,p],[c,d,a,p] + int scount = 0; int k; + for (k = 0; k < 3; k++) { + esym(checktet, chkface); + if (issubface(chkface)) scount++; + enextself(checktet); + } + if (scount == 3) { + break; // Found a tet which support a 3-to-1 flip. + } else if (scount == 2) { + // This is a strange configuration. Debug it. + // Do not do this flip. + delete [] fliptets; + return 0; + } + } + if (i == 3) { + // No tet in [p,d,a,b],[p,d,b,c],[p,d,c,a] support it. + int scount = 0; + for (i = 0; i < 3; i++) { + eprev(fliptets[i], checktet); + esymself(checktet); // [p,a,b,d],[p,b,c,d],[p,c,a,d] + if (issubface(chkface)) scount++; + } + if (scount != 3) { + // Do not do this flip. + delete [] fliptets; + return 0; + } + } + } // if (vt == FREEFACETVERTEX) + flip41(fliptets, 1, &fc); + //recenttet = fliptets[0]; + } else if (loc == ONFACE) { + // Let the original two tets be [a,b,c,d] and [b,a,c,e]. And p is in + // face [a,b,c]. Let 'searchtet' be the tet [p,d,a,b]. + // Collect the six tets containing 'p'. + fliptets = new triface[6]; + fliptets[0] = searchtet; // [p,d,a,b] + for (i = 0; i < 2; i++) { + fnext(fliptets[i], fliptets[i+1]); // [p,d,b,c], [p,d,c,a] + } + eprev(fliptets[0], fliptets[3]); + fnextself(fliptets[3]); // [a,p,b,e] + esymself(fliptets[3]); // [p,a,e,b] + eprevself(fliptets[3]); // [e,p,a,b] + for (i = 3; i < 5; i++) { + fnext(fliptets[i], fliptets[i+1]); // [e,p,b,c], [e,p,c,a] + } + if (vt == FREEFACETVERTEX) { + // We need to determine the location of three subfaces at p. + valence = 0; // Re-use it. + for (i = 3; i < 6; i++) { + if (issubface(fliptets[i])) valence++; + } + if (valence > 0) { + // We must do 3-to-2 flip in the upper part. We simply re-arrange + // the six tets. + for (i = 0; i < 3; i++) { + esym(fliptets[i+3], wrktets[i]); + esym(fliptets[i], fliptets[i+3]); + fliptets[i] = wrktets[i]; + } + // Swap the last two pairs, i.e., [1]<->[[2], and [4]<->[5] + wrktets[1] = fliptets[1]; + fliptets[1] = fliptets[2]; + fliptets[2] = wrktets[1]; + wrktets[1] = fliptets[4]; + fliptets[4] = fliptets[5]; + fliptets[5] = wrktets[1]; + } + // [2018-03-08] Check if the last 4-to-1 flip is valid. + // fliptets[0],[1],[2] are [p,d,a,b],[p,d,b,c],[p,d,c,a] + triface checktet, chkface; + for (i = 0; i < 3; i++) { + enext(fliptets[i], checktet); + esymself(checktet); // [a,d,b,p],[b,d,c,p],[c,d,a,p] + int scount = 0; int k; + for (k = 0; k < 3; k++) { + esym(checktet, chkface); + if (issubface(chkface)) scount++; + enextself(checktet); + } + if (scount == 3) { + break; // Found a tet which support a 3-to-1 flip. + } else if (scount == 2) { + // This is a strange configuration. Debug it. + // Do not do this flip. + delete [] fliptets; + return 0; + } + } + if (i == 3) { + // No tet in [p,d,a,b],[p,d,b,c],[p,d,c,a] support it. + int scount = 0; + for (i = 0; i < 3; i++) { + eprev(fliptets[i], checktet); + esymself(checktet); // [p,a,b,d],[p,b,c,d],[p,c,a,d] + if (issubface(chkface)) scount++; + } + if (scount != 3) { + // Do not do this flip. + delete [] fliptets; + return 0; + } + } + } // vt == FREEFACETVERTEX + // Remove p by a 6-to-2 flip, which is a combination of two flips: + // a 3-to-2 (deletes the edge [e,p]), and + // a 4-to-1 (deletes the vertex p). + // First do a 3-to-2 flip on [e,p,a,b],[e,p,b,c],[e,p,c,a]. It creates + // two new tets: [a,b,c,p] and [b,a,c,e]. The new tet [a,b,c,p] is + // degenerate (has zero volume). It will be deleted in the followed + // 4-to-1 flip. + //flip32(&(fliptets[3]), 1, 0, 0); + flip32(&(fliptets[3]), 1, &fc); + // Second do a 4-to-1 flip on [p,d,a,b],[p,d,b,c],[p,d,c,a],[a,b,c,p]. + // This creates a new tet [a,b,c,d]. + //flip41(fliptets, 1, 0, 0); + flip41(fliptets, 1, &fc); + //recenttet = fliptets[0]; + } else if (loc == ONEDGE) { + // Let the original edge be [e,d] and p is in [e,d]. Assume there are n + // tets sharing at edge [e,d] originally. We number the link vertices + // of [e,d]: p_0, p_1, ..., p_n-1. 'searchtet' is [p,d,p_0,p_1]. + // Count the number of tets at edge [e,p] and [p,d] (this is n). + n = 0; + spintet = searchtet; + while (1) { + n++; + fnextself(spintet); + if (spintet.tet == searchtet.tet) break; + } + // Collect the 2n tets containing 'p'. + fliptets = new triface[2 * n]; + fliptets[0] = searchtet; // [p,b,p_0,p_1] + for (i = 0; i < (n - 1); i++) { + fnext(fliptets[i], fliptets[i+1]); // [p,d,p_i,p_i+1]. + } + eprev(fliptets[0], fliptets[n]); + fnextself(fliptets[n]); // [p_0,p,p_1,e] + esymself(fliptets[n]); // [p,p_0,e,p_1] + eprevself(fliptets[n]); // [e,p,p_0,p_1] + for (i = n; i < (2 * n - 1); i++) { + fnext(fliptets[i], fliptets[i+1]); // [e,p,p_i,p_i+1]. + } + // Remove p by a 2n-to-n flip, it is a sequence of n flips: + // - Do a 2-to-3 flip on + // [p_0,p_1,p,d] and + // [p,p_1,p_0,e]. + // This produces: + // [e,d,p_0,p_1], + // [e,d,p_1,p] (degenerated), and + // [e,d,p,p_0] (degenerated). + wrktets[0] = fliptets[0]; // [p,d,p_0,p_1] + eprevself(wrktets[0]); // [p_0,p,d,p_1] + esymself(wrktets[0]); // [p,p_0,p_1,d] + enextself(wrktets[0]); // [p_0,p_1,p,d] [0] + wrktets[1] = fliptets[n]; // [e,p,p_0,p_1] + enextself(wrktets[1]); // [p,p_0,e,p_1] + esymself(wrktets[1]); // [p_0,p,p_1,e] + eprevself(wrktets[1]); // [p_1,p_0,p,e] [1] + //flip23(wrktets, 1, 0, 0); + flip23(wrktets, 1, &fc); + // Save the new tet [e,d,p,p_0] (degenerated). + fliptets[n] = wrktets[2]; + // Save the new tet [e,d,p_0,p_1]. + fliptets[0] = wrktets[0]; + // - Repeat from i = 1 to n-2: (n - 2) flips + // - Do a 3-to-2 flip on + // [p,p_i,d,e], + // [p,p_i,e,p_i+1], and + // [p,p_i,p_i+1,d]. + // This produces: + // [d,e,p_i+1,p_i], and + // [e,d,p_i+1,p] (degenerated). + for (i = 1; i < (n - 1); i++) { + wrktets[0] = wrktets[1]; // [e,d,p_i,p] (degenerated). + enextself(wrktets[0]); // [d,p_i,e,p] (...) + esymself(wrktets[0]); // [p_i,d,p,e] (...) + eprevself(wrktets[0]); // [p,p_i,d,e] (degenerated) [0]. + wrktets[1] = fliptets[n+i]; // [e,p,p_i,p_i+1] + enextself(wrktets[1]); // [p,p_i,e,p_i+1] [1] + wrktets[2] = fliptets[i]; // [p,d,p_i,p_i+1] + eprevself(wrktets[2]); // [p_i,p,d,p_i+1] + esymself(wrktets[2]); // [p,p_i,p_i+1,d] [2] + //flip32(wrktets, 1, 0, 0); + flip32(wrktets, 1, &fc); + // Save the new tet [e,d,p_i,p_i+1]. // FOR DEBUG ONLY + fliptets[i] = wrktets[0]; // [d,e,p_i+1,p_i] // FOR DEBUG ONLY + esymself(fliptets[i]); // [e,d,p_i,p_i+1] // FOR DEBUG ONLY + } + // - Do a 4-to-1 flip on + // [p,p_0,e,d], [d,e,p_0,p], + // [p,p_0,d,p_n-1], [e,p_n-1,p_0,p], + // [p,p_0,p_n-1,e], [p_0,p_n-1,d,p], and + // [e,d,p_n-1,p]. + // This produces + // [e,d,p_n-1,p_0] and + // deletes p. + wrktets[3] = wrktets[1]; // [e,d,p_n-1,p] (degenerated) [3] + wrktets[0] = fliptets[n]; // [e,d,p,p_0] (degenerated) + eprevself(wrktets[0]); // [p,e,d,p_0] (...) + esymself(wrktets[0]); // [e,p,p_0,d] (...) + enextself(wrktets[0]); // [p,p_0,e,d] (degenerated) [0] + wrktets[1] = fliptets[n-1]; // [p,d,p_n-1,p_0] + esymself(wrktets[1]); // [d,p,p_0,p_n-1] + enextself(wrktets[1]); // [p,p_0,d,p_n-1] [1] + wrktets[2] = fliptets[2*n-1]; // [e,p,p_n-1,p_0] + enextself(wrktets[2]); // [p_p_n-1,e,p_0] + esymself(wrktets[2]); // [p_n-1,p,p_0,e] + enextself(wrktets[2]); // [p,p_0,p_n-1,e] [2] + //flip41(wrktets, 1, 0, 0); + flip41(wrktets, 1, &fc); + // Save the new tet [e,d,p_n-1,p_0] // FOR DEBUG ONLY + fliptets[n-1] = wrktets[0]; // [e,d,p_n-1,p_0] // FOR DEBUG ONLY + //recenttet = fliptets[0]; + } + + delete [] fliptets; + + if (vt == FREESEGVERTEX) { + // Remove the vertex from the surface mesh. + // This will re-create the segment [lpt, rpt] and re-triangulate + // all the facets at the segment. + // Only do lawson flip when subfaces are not recovery yet. + slawson = (checksubfaceflag ? 0 : 1); + spivot(rightseg, parentsh); // 'rightseg' has p as its origin. + sremovevertex(steinerpt, &parentsh, &rightseg, slawson); + + // The original segment is returned in 'rightseg'. + rightseg.shver = 0; + // Insert the new segment. + point2tetorg(lpt, searchtet); + finddirection(&searchtet, rpt); + if (dest(searchtet) != rpt) { + terminatetetgen(this, 2); + } + sstbond1(rightseg, searchtet); + spintet = searchtet; + while (1) { + tssbond1(spintet, rightseg); + fnextself(spintet); + if (spintet.tet == searchtet.tet) break; + } + + if (checksubfaceflag) { + // Insert subfaces at segment [lpt,rpt] into the tetrahedralization. + spivot(rightseg, parentsh); + if (parentsh.sh != NULL) { + spinsh = parentsh; + while (1) { + if (sorg(spinsh) != lpt) { + sesymself(spinsh); + } + apexpt = sapex(spinsh); + // Find the adjacent tet of [lpt,rpt,apexpt]; + spintet = searchtet; + while (1) { + if (apex(spintet) == apexpt) { + tsbond(spintet, spinsh); + sesymself(spinsh); // Get to another side of this face. + fsym(spintet, neightet); + tsbond(neightet, spinsh); + sesymself(spinsh); // Get back to the original side. + break; + } + fnextself(spintet); + } + spivotself(spinsh); + if (spinsh.sh == parentsh.sh) break; + } + } + } // if (checksubfaceflag) + + // Clear the set of new subfaces. + caveshbdlist->restart(); + } // if (vt == FREESEGVERTEX) + + // The point has been removed. + if (pointtype(steinerpt) != UNUSEDVERTEX) { + setpointtype(steinerpt, UNUSEDVERTEX); + unuverts++; + } + if (vt != VOLVERTEX) { + // Update the correspinding counters. + if (vt == FREESEGVERTEX) { + st_segref_count--; + } else if (vt == FREEFACETVERTEX) { + st_facref_count--; + } else if (vt == FREEVOLVERTEX) { + st_volref_count--; + } + if (steinerleft > 0) steinerleft++; + } + + return 1; +} + +//============================================================================// +// // +// smoothpoint() Moving a vertex to improve the mesh quality. // +// // +// 'smtpt' (p) is a point to be smoothed. Generally, it is a Steiner point. // +// It may be not a vertex of the mesh. // +// // +// This routine tries to move 'p' inside its star until a selected objective // +// function over all tetrahedra in the star is improved. The function may be // +// the some quality measures, i.e., aspect ratio, maximum dihedral angel, or // +// simply the volume of the tetrahedra. // +// // +// 'linkfacelist' contains the list of link faces of 'p'. Since a link face // +// has two orientations, ccw or cw, with respect to 'p'. 'ccw' indicates // +// the orientation is ccw (1) or not (0). // +// // +// 'opm' is a structure contains the parameters of the objective function. // +// It is needed by the evaluation of the function value. // +// // +// The return value indicates weather the point is smoothed or not. // +// // +// ASSUMPTION: This routine assumes that all link faces are true faces, i.e, // +// no face has 'dummypoint' as its vertex. // +// // +//============================================================================// + +int tetgenmesh::smoothpoint(point smtpt, arraypool *linkfacelist, int ccw, + optparameters *opm) +{ + triface *parytet, *parytet1, swaptet; + badface bf; + point pa, pb, pc; + REAL fcent[3], startpt[3], nextpt[3], bestpt[3]; + REAL oldval, minval = 0.0, val; + REAL maxcosd; // oldang, newang; + REAL ori, diff; + int numdirs, iter; + int i, j, k; + + // Decide the number of moving directions. + numdirs = (int) linkfacelist->objects; + if (numdirs > opm->numofsearchdirs) { + numdirs = opm->numofsearchdirs; // Maximum search directions. + } + + // Set the initial value. + opm->imprval = opm->initval; + iter = 0; + + for (i = 0; i < 3; i++) { + bestpt[i] = startpt[i] = smtpt[i]; + } + + // Iterate until the obj function is not improved. + while (1) { + + // Find the best next location. + oldval = opm->imprval; + + for (i = 0; i < numdirs; i++) { + // Randomly pick a link face (0 <= k <= objects - i - 1). + k = (int) randomnation(linkfacelist->objects - i); + parytet = (triface *) fastlookup(linkfacelist, k); + // Calculate a new position from 'p' to the center of this face. + pa = org(*parytet); + pb = dest(*parytet); + pc = apex(*parytet); + for (j = 0; j < 3; j++) { + fcent[j] = (pa[j] + pb[j] + pc[j]) / 3.0; + } + for (j = 0; j < 3; j++) { + nextpt[j] = startpt[j] + opm->searchstep * (fcent[j] - startpt[j]); + } + // Calculate the largest minimum function value for the new location. + for (j = 0; j < linkfacelist->objects; j++) { + parytet = (triface *) fastlookup(linkfacelist, j); + if (ccw) { + pa = org(*parytet); + pb = dest(*parytet); + } else { + pb = org(*parytet); + pa = dest(*parytet); + } + pc = apex(*parytet); + ori = orient3d(pa, pb, pc, nextpt); + if (ori < 0.0) { + // Calcuate the objective function value. + if (opm->max_min_volume) { + //val = -ori; + val = - orient3dfast(pa, pb, pc, nextpt); + } else if (opm->min_max_aspectratio) { + get_tetqual(pa, pb, pc, nextpt, &bf); + val = 1.0 / bf.key; + } else if (opm->min_max_dihedangle) { + get_tetqual(pa, pb, pc, nextpt, &bf); + maxcosd = bf.cent[0]; + if (maxcosd < -1) maxcosd = -1.0; // Rounding. + val = maxcosd + 1.0; // Make it be positive. + } else { + // Unknown objective function. + val = 0.0; + } + } else { // ori >= 0.0; + // An invalid new tet. + // This may happen if the mesh contains inverted elements. + if (opm->max_min_volume) { + //val = -ori; + val = - orient3dfast(pa, pb, pc, nextpt); + } else { + // Discard this point. + break; // j + } + } // if (ori >= 0.0) + // Stop looping when the object value is not improved. + if (val <= opm->imprval) { + break; // j + } else { + // Remember the smallest improved value. + if (j == 0) { + minval = val; + } else { + minval = (val < minval) ? val : minval; + } + } + } // j + if (j == linkfacelist->objects) { + // The function value has been improved. + opm->imprval = minval; + // Save the new location of the point. + for (j = 0; j < 3; j++) bestpt[j] = nextpt[j]; + } + // Swap k-th and (object-i-1)-th entries. + j = linkfacelist->objects - i - 1; + parytet = (triface *) fastlookup(linkfacelist, k); + parytet1 = (triface *) fastlookup(linkfacelist, j); + swaptet = *parytet1; + *parytet1 = *parytet; + *parytet = swaptet; + } // i + + diff = opm->imprval - oldval; + if (diff > 0.0) { + // Is the function value improved effectively? + if (opm->max_min_volume) { + //if ((diff / oldval) < b->epsilon) diff = 0.0; + } else if (opm->min_max_aspectratio) { + if ((diff / oldval) < 1e-3) diff = 0.0; + } else if (opm->min_max_dihedangle) { + //oldang = acos(oldval - 1.0); + //newang = acos(opm->imprval - 1.0); + //if ((oldang - newang) < 0.00174) diff = 0.0; // about 0.1 degree. + } else { + // Unknown objective function. + terminatetetgen(this, 2); + } + } + + if (diff > 0.0) { + // Yes, move p to the new location and continue. + for (j = 0; j < 3; j++) startpt[j] = bestpt[j]; + iter++; + if ((opm->maxiter > 0) && (iter >= opm->maxiter)) { + // Maximum smoothing iterations reached. + break; + } + } else { + break; + } + + } // while (1) + + if (iter > 0) { + // The point has been smoothed. + opm->smthiter = iter; // Remember the number of iterations. + // The point has been smoothed. Update it to its new position. + for (i = 0; i < 3; i++) smtpt[i] = startpt[i]; + } + + return iter; +} + +//============================================================================// +// // +// suppressbdrysteinerpoint() Suppress a boundary Steiner point // +// // +//============================================================================// + +int tetgenmesh::suppressbdrysteinerpoint(point steinerpt) +{ + face parentsh, spinsh, *parysh; + face leftseg, rightseg; + point lpt = NULL, rpt = NULL; + int i; + + verttype vt = pointtype(steinerpt); + + if (vt == FREESEGVERTEX) { + sdecode(point2sh(steinerpt), leftseg); + leftseg.shver = 0; + if (sdest(leftseg) == steinerpt) { + senext(leftseg, rightseg); + spivotself(rightseg); + rightseg.shver = 0; + } else { + rightseg = leftseg; + senext2(rightseg, leftseg); + spivotself(leftseg); + leftseg.shver = 0; + } + lpt = sorg(leftseg); + rpt = sdest(rightseg); + if (b->verbose > 2) { + printf(" Suppressing Steiner point %d in segment (%d, %d).\n", + pointmark(steinerpt), pointmark(lpt), pointmark(rpt)); + } + // Get all subfaces at the left segment [lpt, steinerpt]. + spivot(leftseg, parentsh); + if (parentsh.sh != NULL) { + // It is not a dangling segment. + spinsh = parentsh; + while (1) { + cavesegshlist->newindex((void **) &parysh); + *parysh = spinsh; + // Orient the face consistently. + if (sorg(*parysh)!= sorg(parentsh)) sesymself(*parysh); + spivotself(spinsh); + if (spinsh.sh == NULL) break; + if (spinsh.sh == parentsh.sh) break; + } + } + if (cavesegshlist->objects < 2) { + // It is a single segment. Not handle it yet. + cavesegshlist->restart(); + return 0; + } + } else if (vt == FREEFACETVERTEX) { + if (b->verbose > 2) { + printf(" Suppressing Steiner point %d from facet.\n", + pointmark(steinerpt)); + } + sdecode(point2sh(steinerpt), parentsh); + // A facet Steiner point. There are exactly two sectors. + for (i = 0; i < 2; i++) { + cavesegshlist->newindex((void **) &parysh); + *parysh = parentsh; + sesymself(parentsh); + } + } else { + return 0; // no need to suppress it. + } + + triface searchtet, neightet, *parytet; + point pa, pb, pc, pd; + REAL v1[3], v2[3], len, u; + + REAL startpt[3] = {0,}, samplept[3] = {0,}, candpt[3] = {0,}; + REAL ori, minvol, smallvol; + int samplesize; + int it, j, k; + + int n = (int) cavesegshlist->objects; + point *newsteiners = new point[n]; + for (i = 0; i < n; i++) newsteiners[i] = NULL; + + // Search for each sector an interior vertex. + for (i = 0; i < cavesegshlist->objects; i++) { + parysh = (face *) fastlookup(cavesegshlist, i); + stpivot(*parysh, searchtet); + // Skip it if it is outside. + if (ishulltet(searchtet)) continue; + // Get the "half-ball". Tets in 'cavetetlist' all contain 'steinerpt' as + // opposite. Subfaces in 'caveshlist' all contain 'steinerpt' as apex. + // Moreover, subfaces are oriented towards the interior of the ball. + setpoint2tet(steinerpt, encode(searchtet)); + getvertexstar(0, steinerpt, cavetetlist, NULL, caveshlist); + // Calculate the searching vector. + pa = sorg(*parysh); + pb = sdest(*parysh); + pc = sapex(*parysh); + facenormal(pa, pb, pc, v1, 1, NULL); + len = sqrt(dot(v1, v1)); + v1[0] /= len; + v1[1] /= len; + v1[2] /= len; + if (vt == FREESEGVERTEX) { + parysh = (face *) fastlookup(cavesegshlist, (i + 1) % n); + pd = sapex(*parysh); + facenormal(pb, pa, pd, v2, 1, NULL); + len = sqrt(dot(v2, v2)); + v2[0] /= len; + v2[1] /= len; + v2[2] /= len; + // Average the two vectors. + v1[0] = 0.5 * (v1[0] + v2[0]); + v1[1] = 0.5 * (v1[1] + v2[1]); + v1[2] = 0.5 * (v1[2] + v2[2]); + } + // Search the intersection of the ray starting from 'steinerpt' to + // the search direction 'v1' and the shell of the half-ball. + // - Construct an endpoint. + len = distance(pa, pb); + v2[0] = steinerpt[0] + len * v1[0]; + v2[1] = steinerpt[1] + len * v1[1]; + v2[2] = steinerpt[2] + len * v1[2]; + for (j = 0; j < cavetetlist->objects; j++) { + parytet = (triface *) fastlookup(cavetetlist, j); + pa = org(*parytet); + pb = dest(*parytet); + pc = apex(*parytet); + // Test if the ray startpt->v2 lies in the cone: where 'steinerpt' + // is the apex, and three sides are defined by the triangle + // [pa, pb, pc]. + ori = orient3d(steinerpt, pa, pb, v2); + if (ori >= 0) { + ori = orient3d(steinerpt, pb, pc, v2); + if (ori >= 0) { + ori = orient3d(steinerpt, pc, pa, v2); + if (ori >= 0) { + // Found! Calculate the intersection. + planelineint(pa, pb, pc, steinerpt, v2, startpt, &u); + break; + } + } + } + } // j + if (j == cavetetlist->objects) { + break; // There is no intersection!! Debug is needed. + } + // Close the ball by adding the subfaces. + for (j = 0; j < caveshlist->objects; j++) { + parysh = (face *) fastlookup(caveshlist, j); + stpivot(*parysh, neightet); + cavetetlist->newindex((void **) &parytet); + *parytet = neightet; + } + // Search a best point inside the segment [startpt, steinerpt]. + it = 0; + samplesize = 100; + v1[0] = steinerpt[0] - startpt[0]; + v1[1] = steinerpt[1] - startpt[1]; + v1[2] = steinerpt[2] - startpt[2]; + minvol = -1.0; + while (it < 3) { + for (j = 1; j < samplesize - 1; j++) { + samplept[0] = startpt[0] + ((REAL) j / (REAL) samplesize) * v1[0]; + samplept[1] = startpt[1] + ((REAL) j / (REAL) samplesize) * v1[1]; + samplept[2] = startpt[2] + ((REAL) j / (REAL) samplesize) * v1[2]; + // Find the minimum volume for 'samplept'. + smallvol = -1; + for (k = 0; k < cavetetlist->objects; k++) { + parytet = (triface *) fastlookup(cavetetlist, k); + pa = org(*parytet); + pb = dest(*parytet); + pc = apex(*parytet); + ori = orient3d(pb, pa, pc, samplept); + { + // [2017-10-15] Rounding + REAL lab = distance(pa, pb); + REAL lbc = distance(pb, pc); + REAL lca = distance(pc, pa); + REAL lv = (lab + lbc + lca) / 3.0; + REAL l3 = lv*lv*lv; + if (fabs(ori) / l3 < 1e-8) ori = 0.0; + } + if (ori <= 0) { + break; // An invalid tet. + } + if (smallvol == -1) { + smallvol = ori; + } else { + if (ori < smallvol) smallvol = ori; + } + } // k + if (k == cavetetlist->objects) { + // Found a valid point. Remember it. + if (minvol == -1.0) { + candpt[0] = samplept[0]; + candpt[1] = samplept[1]; + candpt[2] = samplept[2]; + minvol = smallvol; + } else { + if (minvol < smallvol) { + // It is a better location. Remember it. + candpt[0] = samplept[0]; + candpt[1] = samplept[1]; + candpt[2] = samplept[2]; + minvol = smallvol; + } else { + // No improvement of smallest volume. + // Since we are searching along the line [startpt, steinerpy], + // The smallest volume can only be decreased later. + break; + } + } + } + } // j + if (minvol > 0) break; + samplesize *= 10; + it++; + } // while (it < 3) + if (minvol == -1.0) { + // Failed to find a valid point. + cavetetlist->restart(); + caveshlist->restart(); + break; + } + // Create a new Steiner point inside this section. + makepoint(&(newsteiners[i]), FREEVOLVERTEX); + newsteiners[i][0] = candpt[0]; + newsteiners[i][1] = candpt[1]; + newsteiners[i][2] = candpt[2]; + cavetetlist->restart(); + caveshlist->restart(); + } // i + + if (i < cavesegshlist->objects) { + // Failed to suppress the vertex. + for (; i > 0; i--) { + if (newsteiners[i - 1] != NULL) { + pointdealloc(newsteiners[i - 1]); + } + } + delete [] newsteiners; + cavesegshlist->restart(); + return 0; + } + + // First insert Steiner points into the mesh. + // 'cavesegshlist' will be used by insertpoint(). + //int nfaces = cavesegshlist->objects; + face *segshlist = new face[n]; + for (i = 0; i < cavesegshlist->objects; i++) { + segshlist[i] = * (face *) fastlookup(cavesegshlist, i); + } + cavesegshlist->restart(); + + for (i = 0; i < n; i++) { + //assert(caveoldtetlist->objects == 0); + //assert(cavetetlist->objects == 0); + parysh = &(segshlist[i]); + // 'parysh' is the face [lpt, steinerpt, #]. + stpivot(*parysh, searchtet); + // Skip it if it is outside. + if (ishulltet(searchtet)) continue; + + // Get the "half-ball". Tets in 'cavetetlist' all contain 'steinerpt' as + // opposite. Subfaces in 'caveshlist' all contain 'steinerpt' as apex. + // Moreover, subfaces are oriented towards the interior of the ball. + setpoint2tet(steinerpt, encode(searchtet)); + getvertexstar(0, steinerpt, cavetetlist, NULL, caveshlist); + + // Get all tets in this sector. + for (int j = 0; j < cavetetlist->objects; j++) { + neightet = * (triface *) fastlookup(cavetetlist, j); + infect(neightet); + caveoldtetlist->newindex((void **) &parytet); + *parytet = neightet; + } + cavetetlist->restart(); + caveshlist->restart(); + + insertvertexflags ivf; + searchtet = neightet; // No need point location. + ivf.iloc = (int) INSTAR; // No need point location. + // The following are default options. + //ivf.bowywat = 0; + //ivf.lawson = 0; + //ivf.validflag = 0; // no need to validate cavity. + //ivf.chkencflag = 0; //chkencflag; + ivf.assignmeshsize = b->metric; + if (ivf.assignmeshsize) { + // Search the tet containing 'steinerpt' for size interpolation. + locate(newsteiners[i], &searchtet); + } + + // Insert the new point into the tetrahedralization T. + // Note that T is convex (nonconvex = 0). + if (insertpoint(newsteiners[i], &searchtet, NULL, NULL, &ivf)) { + // The vertex has been inserted. + st_volref_count++; + if (steinerleft > 0) steinerleft--; + //return 1; + } else { + // Not inserted. + //assert(0); + pointdealloc(newsteiners[i]); + newsteiners[i] = NULL; + break; //return 0; + } + } // i + + delete [] segshlist; + + if (i < n) { + //assert(0); + delete [] newsteiners; + return 0; + } + + // Now remove the Steiner point from the segment. + if (!removevertexbyflips(steinerpt)) { + //assert(0); + delete [] newsteiners; + return 0; + } + + // We've removed a Steiner points. + setpointtype(steinerpt, UNUSEDVERTEX); + unuverts++; + + int steinercount = 0; + + int bak_fliplinklevel = b->fliplinklevel; + b->fliplinklevel = 100000; // Unlimited flip level. + + // Try to remove newly added Steiner points. + for (i = 0; i < n; i++) { + if (newsteiners[i] != NULL) { + if (!removevertexbyflips(newsteiners[i])) { + if (b->supsteiner_level > 0) { // Not -Y/0 + // Save it in subvertstack for removal. + point *parypt; + subvertstack->newindex((void **) &parypt); + *parypt = newsteiners[i]; + } + steinercount++; + } + } + } + + b->fliplinklevel = bak_fliplinklevel; + + if (steinercount > 0) { + if (b->verbose > 3) { + printf(" Added %d interior Steiner points.\n", steinercount); + } + } + + delete [] newsteiners; + + return 1; +} + + +//============================================================================// +// // +// suppresssteinerpoints() Suppress Steiner points. // +// // +// All Steiner points have been saved in 'subvertstack' in the routines // +// carveholes() and suppresssteinerpoint(). // +// Each Steiner point is either removed or shifted into the interior. // +// // +//============================================================================// + +int tetgenmesh::suppresssteinerpoints() +{ + + if (!b->quiet) { + printf("Suppressing Steiner points ...\n"); + } + + point rempt, *parypt; + + int bak_fliplinklevel = b->fliplinklevel; + b->fliplinklevel = 100000; // Unlimited flip level. + int suppcount = 0, remcount = 0; + int i; + + // Try to suppress boundary Steiner points. + for (i = 0; i < subvertstack->objects; i++) { + parypt = (point *) fastlookup(subvertstack, i); + rempt = *parypt; + if (pointtype(rempt) != UNUSEDVERTEX) { + if ((pointtype(rempt) == FREESEGVERTEX) || + (pointtype(rempt) == FREEFACETVERTEX)) { + if (suppressbdrysteinerpoint(rempt)) { + suppcount++; + } + } + } + } // i + + if (suppcount > 0) { + if (b->verbose) { + printf(" Suppressed %d boundary Steiner points.\n", suppcount); + } + } + + if (b->supsteiner_level > 0) { // -Y/1 + for (i = 0; i < subvertstack->objects; i++) { + parypt = (point *) fastlookup(subvertstack, i); + rempt = *parypt; + if (pointtype(rempt) != UNUSEDVERTEX) { + if (pointtype(rempt) == FREEVOLVERTEX) { + if (removevertexbyflips(rempt)) { + remcount++; + } + } + } + } + } + + if (remcount > 0) { + if (b->verbose) { + printf(" Removed %d interior Steiner points.\n", remcount); + } + } + + b->fliplinklevel = bak_fliplinklevel; + + if (b->supsteiner_level > 1) { // -Y/2 + // Smooth interior Steiner points. + optparameters opm; + triface *parytet; + point *ppt; + REAL ori; + int smtcount, count, ivcount; + int nt, j; + + // Point smooth options. + opm.max_min_volume = 1; + opm.numofsearchdirs = 20; + opm.searchstep = 0.001; + opm.maxiter = 30; // Limit the maximum iterations. + + smtcount = 0; + + do { + + nt = 0; + + while (1) { + count = 0; + ivcount = 0; // Clear the inverted count. + + for (i = 0; i < subvertstack->objects; i++) { + parypt = (point *) fastlookup(subvertstack, i); + rempt = *parypt; + if (pointtype(rempt) == FREEVOLVERTEX) { + getvertexstar(1, rempt, cavetetlist, NULL, NULL); + // Calculate the initial smallest volume (maybe zero or negative). + for (j = 0; j < cavetetlist->objects; j++) { + parytet = (triface *) fastlookup(cavetetlist, j); + ppt = (point *) &(parytet->tet[4]); + ori = orient3dfast(ppt[1], ppt[0], ppt[2], ppt[3]); + if (j == 0) { + opm.initval = ori; + } else { + if (opm.initval > ori) opm.initval = ori; + } + } + if (smoothpoint(rempt, cavetetlist, 1, &opm)) { + count++; + } + if (opm.imprval <= 0.0) { + ivcount++; // The mesh contains inverted elements. + } + cavetetlist->restart(); + } + } // i + + smtcount += count; + + if (count == 0) { + // No point has been smoothed. + break; + } + + nt++; + if (nt > 2) { + break; // Already three iterations. + } + } // while + + if (ivcount > 0) { + // There are inverted elements! + if (opm.maxiter > 0) { + // Set unlimited smoothing steps. Try again. + opm.numofsearchdirs = 30; + opm.searchstep = 0.0001; + opm.maxiter = -1; + continue; + } + } + + break; + } while (1); // Additional loop for (ivcount > 0) + + if (ivcount > 0) { + printf("BUG Report! The mesh contain inverted elements.\n"); + } + + if (b->verbose) { + if (smtcount > 0) { + printf(" Smoothed %d Steiner points.\n", smtcount); + } + } + } // -Y2 + + subvertstack->restart(); + + return 1; +} + +//============================================================================// +// // +// recoverboundary() Recover segments and facets. // +// // +//============================================================================// + +void tetgenmesh::recoverboundary(clock_t& tv) +{ + arraypool *misseglist, *misshlist; + arraypool *bdrysteinerptlist; + face searchsh, *parysh; + face searchseg, *paryseg; + point rempt, *parypt; + long ms; // The number of missing segments/subfaces. + int nit; // The number of iterations. + int s, i; + + // Counters. + long bak_segref_count, bak_facref_count, bak_volref_count; + + if (!b->quiet) { + printf("Recovering boundaries...\n"); + } + + boundary_recovery_flag = 1; + cos_collinear_ang_tol = cos(b->collinear_ang_tol / 180. * PI); + + if (segmentendpointslist == NULL) { + // We need segment adjacent information during flips. + makesegmentendpointsmap(); + } + + + if (b->verbose) { + printf(" Recovering segments.\n"); + } + + // Segments will be introduced. + checksubsegflag = 1; + + misseglist = new arraypool(sizeof(face), 8); + bdrysteinerptlist = new arraypool(sizeof(point), 8); + + // In random order. + subsegs->traversalinit(); + for (i = 0; i < subsegs->items; i++) { + s = randomnation(i + 1); + // Move the s-th seg to the i-th. + subsegstack->newindex((void **) &paryseg); + *paryseg = * (face *) fastlookup(subsegstack, s); + // Put i-th seg to be the s-th. + searchseg.sh = shellfacetraverse(subsegs); + paryseg = (face *) fastlookup(subsegstack, s); + *paryseg = searchseg; + } + + // The init number of missing segments. + ms = subsegs->items; + nit = 0; + if (b->fliplinklevel < 0) { + autofliplinklevel = 1; // Init value. + } + + // First, trying to recover segments by only doing flips. + while (1) { + recoversegments(misseglist, 0, 0); + + if (misseglist->objects > 0) { + if (b->fliplinklevel >= 0) { + break; + } else { + if (misseglist->objects >= ms) { + nit++; + if (nit >= 3) { + //break; + // Do the last round with unbounded flip link level. + b->fliplinklevel = 100000; + } + } else { + ms = misseglist->objects; + if (nit > 0) { + nit--; + } + } + for (i = 0; i < misseglist->objects; i++) { + subsegstack->newindex((void **) &paryseg); + *paryseg = * (face *) fastlookup(misseglist, i); + } + misseglist->restart(); + autofliplinklevel+=b->fliplinklevelinc; + } + } else { + // All segments are recovered. + break; + } + } // while (1) + + if (b->verbose) { + printf(" %ld (%ld) segments are recovered (missing).\n", + subsegs->items - misseglist->objects, misseglist->objects); + } + + if (misseglist->objects > 0) { + // Second, trying to recover segments by doing more flips (fullsearch). + while (misseglist->objects > 0) { + ms = misseglist->objects; + for (i = 0; i < misseglist->objects; i++) { + subsegstack->newindex((void **) &paryseg); + *paryseg = * (face *) fastlookup(misseglist, i); + } + misseglist->restart(); + + recoversegments(misseglist, 1, 0); + + if (misseglist->objects < ms) { + // The number of missing segments is reduced. + continue; + } else { + break; + } + } + if (b->verbose) { + printf(" %ld (%ld) segments are recovered (missing).\n", + subsegs->items - misseglist->objects, misseglist->objects); + } + } + + //int bak_verbose = b->verbose; + //if (b->verbose < 3) { + // b->verbose = 3; // debug... + //} + + if (misseglist->objects > 0) { + // Third, trying to recover segments by doing more flips (fullsearch) + // and adding Steiner points in the volume. + + if (b->verbose) { + printf(" Recovering Delaunay.\n"); + } + + recoverdelaunay(); + + if (b->verbose) { + printf(" Recovering segments with Steiner points.\n"); + } + + while (misseglist->objects > 0) { + ms = misseglist->objects; + for (i = 0; i < misseglist->objects; i++) { + subsegstack->newindex((void **) &paryseg); + *paryseg = * (face *) fastlookup(misseglist, i); + } + misseglist->restart(); + + //recoversegments(misseglist, 1, 1); + recoversegments(misseglist, 0, 1); // no full search + + if (misseglist->objects < ms) { + // The number of missing segments is reduced. + continue; + } else { + break; + } + } + if (b->verbose) { + printf(" Added %ld Steiner points in volume.\n", st_volref_count); + } + } + + if (misseglist->objects > 0) { + // Last, trying to recover segments by doing more flips (fullsearch), + // and adding Steiner points in the volume, and splitting segments. + long bak_inpoly_count = st_volref_count; //st_inpoly_count; + + if (b->verbose) { + printf(" Recovering Delaunay.\n"); + } + + recoverdelaunay(); + + if (b->verbose) { + printf(" Recovering segments with Steiner points.\n"); + } + + while (misseglist->objects > 0) { + ms = misseglist->objects; + for (i = 0; i < misseglist->objects; i++) { + subsegstack->newindex((void **) &paryseg); + *paryseg = * (face *) fastlookup(misseglist, i); + } + misseglist->restart(); + + //recoversegments(misseglist, 1, 2); + recoversegments(misseglist, 0, 2); // no full search + + if (misseglist->objects < ms) { + // The number of missing segments is reduced. + continue; + } else { + break; + } + } // while (misseglist->objects > 0) + + if (b->verbose) { + printf(" Added %ld Steiner points in segments.\n", st_segref_count); + if (st_volref_count > bak_inpoly_count) { + printf(" Added another %ld Steiner points in volume.\n", + st_volref_count - bak_inpoly_count); + } + } + + // There may be un-recovered subsegments. + if (misseglist->objects > 0l) { + if (b->verbose) { + printf(" !! %ld subsegments are missing.\n", misseglist->objects); + } + } + } + + if (skipped_segment_list != NULL) { + if (!b->quiet) { + printf(" Skipped %ld segments due to intersections.\n", + skipped_segment_list->objects); + } + delete skipped_segment_list; + } + + + //b->verbose = bak_verbose; // debug... + + if (st_segref_count > 0) { + // Try to remove the Steiner points added in segments. + if (b->verbose) { + printf(" Suppressing %ld Steiner points in segments.\n", st_segref_count); + } + int bak_fliplinklevel = b->fliplinklevel; + b->fliplinklevel = 20; // limit this value + + bak_segref_count = st_segref_count; + bak_volref_count = st_volref_count; + for (i = 0; i < subvertstack->objects; i++) { + // Get the Steiner point. + parypt = (point *) fastlookup(subvertstack, i); + rempt = *parypt; + if (!removevertexbyflips(rempt)) { + // Save it in list. + bdrysteinerptlist->newindex((void **) &parypt); + *parypt = rempt; + } + } + if (b->verbose) { + if (st_segref_count < bak_segref_count) { + if (bak_volref_count < st_volref_count) { + printf(" Suppressed %ld Steiner points in segments.\n", + st_volref_count - bak_volref_count); + } + if ((st_segref_count + (st_volref_count - bak_volref_count)) < + bak_segref_count) { + printf(" Removed %ld Steiner points in segments.\n", + bak_segref_count - + (st_segref_count + (st_volref_count - bak_volref_count))); + } + } + } + + b->fliplinklevel = bak_fliplinklevel; // restore it. + subvertstack->restart(); + } + + + tv = clock(); + + if (b->verbose) { + printf(" Recovering facets.\n"); + } + + // Subfaces will be introduced. + checksubfaceflag = 1; + + misshlist = new arraypool(sizeof(face), 8); + + // Randomly order the subfaces. + subfaces->traversalinit(); + for (i = 0; i < subfaces->items; i++) { + s = randomnation(i + 1); + // Move the s-th subface to the i-th. + subfacstack->newindex((void **) &parysh); + *parysh = * (face *) fastlookup(subfacstack, s); + // Put i-th subface to be the s-th. + searchsh.sh = shellfacetraverse(subfaces); + parysh = (face *) fastlookup(subfacstack, s); + *parysh = searchsh; + } + + ms = subfaces->items; + nit = 0; + b->fliplinklevel = -1; // Init. + if (b->fliplinklevel < 0) { + autofliplinklevel = 1; // Init value. + } + + while (1) { + recoversubfaces(misshlist, 0); + + if (misshlist->objects > 0) { + if (b->fliplinklevel >= 0) { + break; + } else { + if (misshlist->objects >= ms) { + nit++; + if (nit >= 3) { + //break; + // Do the last round with unbounded flip link level. + //b->fliplinklevel = 100000; // this can be very slow. + if (autofliplinklevel < 30) { + b->fliplinklevel = 30; + } else { + b->fliplinklevel = autofliplinklevel + 30; + } + } + } else { + ms = misshlist->objects; + if (nit > 0) { + nit--; + } + } + for (i = 0; i < misshlist->objects; i++) { + subfacstack->newindex((void **) &parysh); + *parysh = * (face *) fastlookup(misshlist, i); + } + misshlist->restart(); + autofliplinklevel+=b->fliplinklevelinc; + } + } else { + // All subfaces are recovered. + break; + } + } // while (1) + + if (b->verbose) { + printf(" %ld (%ld) subfaces are recovered (missing).\n", + subfaces->items - misshlist->objects, misshlist->objects); + } + + if (misshlist->objects > 0) { + // There are missing subfaces. Add Steiner points. + + if (b->verbose) { + printf(" Recovering Delaunay.\n"); + } + + recoverdelaunay(); + + if (b->verbose) { + printf(" Recovering facets with Steiner points.\n"); + } + + while (misshlist->objects > 0) { + ms = misshlist->objects; + for (i = 0; i < misshlist->objects; i++) { + subfacstack->newindex((void **) &parysh); + *parysh = * (face *) fastlookup(misshlist, i); + } + misshlist->restart(); + + recoversubfaces(misshlist, 1); + + if (misshlist->objects < ms) { + continue; + } else { + break; + } + } + + if (b->verbose) { + printf(" %ld (%ld) subfaces are recovered (missing).\n", + subfaces->items - misshlist->objects, misshlist->objects); + printf(" Added %ld Steiner points in facets.\n", st_facref_count); + } + } + + if (misshlist->objects > 0) { + long bak_steiner = st_facref_count; + + if (b->verbose) { + printf(" Recovering Delaunay.\n"); + } + + recoverdelaunay(); + + if (b->verbose) { + printf(" Recovering facets with Steiner points.\n"); + } + + while (misshlist->objects > 0) { + ms = misshlist->objects; + for (i = 0; i < misshlist->objects; i++) { + subfacstack->newindex((void **) &parysh); + *parysh = * (face *) fastlookup(misshlist, i); + } + misshlist->restart(); + + recoversubfaces(misshlist, 2); // steinerflag = 2; + + if (misshlist->objects < ms) { + continue; + } else { + break; + } + } + + if (subsegstack->objects > 0) { + // Save unrecovered subsegments. + triface neightet; + face checkseg; + for (i = 0; i < subsegstack->objects; i++) { + checkseg = * (face *) fastlookup(subsegstack, i); + if ((checkseg.sh == NULL) || + (checkseg.sh[3] == NULL)) continue; + // Check if this subsegment is missing. + sstpivot1(checkseg, neightet); + if (neightet.tet != NULL) continue; + // Save a missing subsegment. + misseglist->newindex((void **) &paryseg); + *paryseg = checkseg; + } + subsegstack->restart(); + } // if (subsegstack->objects > 0) + + if (b->verbose) { + printf(" %ld (%ld) subfaces are recovered (missing).\n", + subfaces->items - misshlist->objects, misshlist->objects); + printf(" Added %ld Steiner points in facets.\n", + st_facref_count - bak_steiner); + } + } + + // There may be un-recovered subsegments. + if (misshlist->objects > 0l) { + if (b->verbose) { + printf(" !! %ld subfaces are missing.\n", misshlist->objects); + } + terminatetetgen(this, 2); + // Save the list of missing subface. + //missing_tri_list = new arraypool(sizeof(face), 8); + //for (i = 0; i < misshlist->objects; i++) { + // missing_tri_list->newindex((void **) &parysh); + // *parysh = * (face *) fastlookup(misshlist, i); + //} + //misshlist->restart(); + } + + if (duplicated_facets_count > 0l) { + if (b->verbose) { + printf(" Deleting %ld duplicated facets.\n", duplicated_facets_count); + } + triface neightet, spintet; + face faceloop, sfaces[256]; // *tmp_sfaces = NULL; + face sseg; + int snum, snum_limit = 256; + int t1ver; + subfaces->traversalinit(); + faceloop.sh = shellfacetraverse(subfaces); + while (faceloop.sh != NULL) { + if (sinfected(faceloop)) { + // Delete an ignored duplicated subface. + shellfacedealloc(subfaces, faceloop.sh); + } + if (!smarktest3ed(faceloop)) { + faceloop.shver = 0; + stpivot(faceloop, neightet); + if (neightet.tet == NULL) { + terminatetetgen(this, 2); + } + // Update the subface connections at its three edges. + for (int k= 0; k < 3; k++) { + sspivot(faceloop, sseg); + if (sseg.sh != NULL) { + ssbond(faceloop, sseg); // Update segment connection. + } + // Get all subfaces at this edge. + snum = 0; + spintet = neightet; + do { + if (issubface(spintet)) { + tspivot(spintet, sfaces[snum++]); + if (snum > snum_limit) { + // Unlikely to happen. + terminatetetgen(this, 2); + //tmp_sfaces = new face[snum_limit * 2]; + } + } + fnextself(spintet); + } while (spintet.tet != neightet.tet); + // Re-create the face ring. + for (int j = 0; j < snum - 1; j++) { + sbond1(sfaces[j], sfaces[j+1]); + } + sbond1(sfaces[snum - 1], sfaces[0]); + enextself(neightet); + senextself(faceloop); + } // k + } + faceloop.sh = shellfacetraverse(subfaces); + } + } // if (duplicated_facets_count > 0l) + + + if (st_facref_count > 0) { + // Try to remove the Steiner points added in facets. + if (b->verbose) { + printf(" Suppressing %ld Steiner points in facets.\n", st_facref_count); + } + int bak_fliplinklevel = b->fliplinklevel; + b->fliplinklevel = 30; // limit this value + + bak_facref_count = st_facref_count; + for (i = 0; i < subvertstack->objects; i++) { + // Get the Steiner point. + parypt = (point *) fastlookup(subvertstack, i); + rempt = *parypt; + if (!removevertexbyflips(*parypt)) { + // Save it in list. + bdrysteinerptlist->newindex((void **) &parypt); + *parypt = rempt; + } + } + if (b->verbose) { + if (st_facref_count < bak_facref_count) { + printf(" Removed %ld Steiner points in facets.\n", + bak_facref_count - st_facref_count); + } + } + + b->fliplinklevel = bak_fliplinklevel; + subvertstack->restart(); + } + + + // There may be missing segments and subfaces. + if (misseglist->objects > 0) { + triface adjtet; + face checkseg; + for (i = 0; i < misseglist->objects; i++) { + checkseg = * (face *) fastlookup(misseglist, i); + // A saved missing segment might be split or recovered. + if ((checkseg.sh == NULL) || (checkseg.sh[3] == NULL)) { + continue; // it is split. + } + sstpivot1(checkseg, adjtet); + if (adjtet.tet != NULL) { + continue; // it is recovered. + } + // This is a missing segmemt. + subsegstack->newindex((void **) &paryseg); + *paryseg = checkseg; + } + if (subsegstack->objects > 0) { + if (!b->quiet && !b->nowarning) { + printf("Warning: %ld segments are not recovered.\n", subsegstack->objects); + } + //assert(0); // to do... + subsegstack->restart(); + } + } + + + if (bdrysteinerptlist->objects > 0) { + if (b->verbose) { + printf(" %ld Steiner points remained in boundary.\n", + bdrysteinerptlist->objects); + } + } // if + + + boundary_recovery_flag = 0; + + // Accumulate the dynamic memory. + totalworkmemory += (misseglist->totalmemory + misshlist->totalmemory + + bdrysteinerptlist->totalmemory); + + delete bdrysteinerptlist; + delete misseglist; + delete misshlist; +} + +// // +// // +//== steiner_cxx =============================================================// + + +//== reconstruct_cxx =========================================================// +// // +// // + +//============================================================================// +// // +// carveholes() Remove tetrahedra not in the mesh domain. // +// // +//============================================================================// + + +void tetgenmesh::carveholes() +{ + arraypool *tetarray, *hullarray; + triface tetloop, neightet, *parytet, *parytet1; + triface *regiontets = NULL; + face checksh, *parysh; + face checkseg; + point ptloop, *parypt; + int t1ver; + int i, j, k; + + if (!b->quiet) { + if (b->convex) { + printf("Marking exterior tetrahedra ...\n"); + } else { + printf("Removing exterior tetrahedra ...\n"); + } + } + + // Initialize the pool of exterior tets. + tetarray = new arraypool(sizeof(triface), 10); + hullarray = new arraypool(sizeof(triface), 10); + + // Collect unprotected tets and hull tets. + tetrahedrons->traversalinit(); + tetloop.ver = 11; // The face opposite to dummypoint. + tetloop.tet = alltetrahedrontraverse(); + while (tetloop.tet != (tetrahedron *) NULL) { + if (ishulltet(tetloop)) { + // Is this side protected by a subface? + if (!issubface(tetloop)) { + // Collect an unprotected hull tet and tet. + infect(tetloop); + hullarray->newindex((void **) &parytet); + *parytet = tetloop; + // tetloop's face number is 11 & 3 = 3. + decode(tetloop.tet[3], neightet); + if (!infected(neightet)) { + infect(neightet); + tetarray->newindex((void **) &parytet); + *parytet = neightet; + } + } + } + tetloop.tet = alltetrahedrontraverse(); + } + + if (in->numberofholes > 0) { + // Mark as infected any tets inside volume holes. + for (i = 0; i < 3 * in->numberofholes; i += 3) { + // Search a tet containing the i-th hole point. + neightet.tet = NULL; + randomsample(&(in->holelist[i]), &neightet); + if (locate(&(in->holelist[i]), &neightet) != OUTSIDE) { + // The tet 'neightet' contain this point. + if (!infected(neightet)) { + infect(neightet); + tetarray->newindex((void **) &parytet); + *parytet = neightet; + // Add its adjacent tet if it is not protected. + if (!issubface(neightet)) { + decode(neightet.tet[neightet.ver & 3], tetloop); + if (!infected(tetloop)) { + infect(tetloop); + if (ishulltet(tetloop)) { + hullarray->newindex((void **) &parytet); + } else { + tetarray->newindex((void **) &parytet); + } + *parytet = tetloop; + } + } + else { + // It is protected. Check if its adjacent tet is a hull tet. + decode(neightet.tet[neightet.ver & 3], tetloop); + if (ishulltet(tetloop)) { + // It is hull tet, add it into the list. Moreover, the subface + // is dead, i.e., both sides are in exterior. + if (!infected(tetloop)) { + infect(tetloop); + hullarray->newindex((void **) &parytet); + *parytet = tetloop; + } + } + if (infected(tetloop)) { + // Both sides of this subface are in exterior. + tspivot(neightet, checksh); + sinfect(checksh); // Only queue it once. + subfacstack->newindex((void **) &parysh); + *parysh = checksh; + } + } + } // if (!infected(neightet)) + } else { + // A hole point locates outside of the convex hull. + if (!b->quiet) { + printf("Warning: The %d-th hole point ", i/3 + 1); + printf("lies outside the convex hull.\n"); + } + } + } // i + } // if (in->numberofholes > 0) + + if (b->hole_mesh && (b->hole_mesh_filename[0] != 0)) { + // A hole mesh (***.ele) is given. + //enum tetgenbehavior::objecttype object; + char filebasename[256]; + strcpy(filebasename, b->hole_mesh_filename); + //object = tetgenbehavior::MESH; + if (!strcmp(&filebasename[strlen(filebasename) - 4], ".ele")) { + filebasename[strlen(filebasename) - 4] = '\0'; + //object = tetgenbehavior::MESH; + } + bool hole_mesh_loaded = false; + tetgenio io; + if (io.load_node(filebasename)) { + if (io.load_tet(filebasename)) { + hole_mesh_loaded = true; + } + } + if (hole_mesh_loaded) { + if (b->verbose) { + printf(" Adding hole tets from the mesh %s\n", b->hole_mesh_filename); + } + int count = 0, hcount = 0, scount = 0; + int shift = io.firstnumber > 0 ? -1 : 0; + double *p1, *p2, *p3, *p4; + double searchpt[3]; + // Randomly select a tet. + i = randomnation(io.numberoftetrahedra); + //for (i = 0; i < io.numberoftetrahedra; i++) { + int *idx = &(io.tetrahedronlist[i * 4]); + p1 = &(io.pointlist[(idx[0]+shift)*3]); + p2 = &(io.pointlist[(idx[1]+shift)*3]); + p3 = &(io.pointlist[(idx[2]+shift)*3]); + p4 = &(io.pointlist[(idx[3]+shift)*3]); + for (j = 0; j < 3; j++) { + searchpt[j] = (p1[j]+p2[j]+p3[j]+p4[j])/4.; + } + // Search the point. + neightet.tet = NULL; + if (locate(searchpt, &neightet) != OUTSIDE) { + // The tet 'neightet' contain this point. + if (!infected(neightet)) { + infect(neightet); + tetarray->newindex((void **) &parytet); + *parytet = neightet; + count++; + // Add its adjacent tet if it is not protected. + if (!issubface(neightet)) { + decode(neightet.tet[neightet.ver & 3], tetloop); + if (!infected(tetloop)) { + infect(tetloop); + if (ishulltet(tetloop)) { + hullarray->newindex((void **) &parytet); + hcount++; + } else { + tetarray->newindex((void **) &parytet); + count++; + } + *parytet = tetloop; + } + } + else { + // It is protected. Check if its adjacent tet is a hull tet. + decode(neightet.tet[neightet.ver & 3], tetloop); + if (ishulltet(tetloop)) { + // It is hull tet, add it into the list. Moreover, the subface + // is dead, i.e., both sides are in exterior. + if (!infected(tetloop)) { + infect(tetloop); + hullarray->newindex((void **) &parytet); + *parytet = tetloop; + hcount++; + } + } + if (infected(tetloop)) { + // Both sides of this subface are in exterior. + tspivot(neightet, checksh); + sinfect(checksh); // Only queue it once. + subfacstack->newindex((void **) &parysh); + *parysh = checksh; + scount++; + } + } + } + } + //} // i + if (b->verbose) { + printf(" Added %d hole tets, %d hull tet, %d hole subfaces\n", + count, hcount, scount); + } + } // if (hole_mesh_loaded) + } + + if (b->regionattrib && (in->numberofregions > 0)) { // -A option. + // Record the tetrahedra that contains the region points for assigning + // region attributes after the holes have been carved. + regiontets = new triface[in->numberofregions]; + // Mark as marktested any tetrahedra inside volume regions. + for (i = 0; i < 5 * in->numberofregions; i += 5) { + // Search a tet containing the i-th region point. + neightet.tet = NULL; + randomsample(&(in->regionlist[i]), &neightet); + if (locate(&(in->regionlist[i]), &neightet) != OUTSIDE) { + regiontets[i/5] = neightet; + } else { + if (!b->quiet) { + printf("Warning: The %d-th region point ", i/5+1); + printf("lies outside the convex hull.\n"); + } + regiontets[i/5].tet = NULL; + } + } + } + + // Collect all exterior tets (in concave place and in holes). + for (i = 0; i < tetarray->objects; i++) { + parytet = (triface *) fastlookup(tetarray, i); + j = (parytet->ver & 3); // j is the current face number. + // Check the other three adjacent tets. + for (k = 1; k < 4; k++) { + decode(parytet->tet[(j + k) % 4], neightet); + // neightet may be a hull tet. + if (!infected(neightet)) { + // Is neightet protected by a subface. + if (!issubface(neightet)) { + // Not proected. Collect it. (It must not be a hull tet). + infect(neightet); + tetarray->newindex((void **) &parytet1); + *parytet1 = neightet; + } else { + // Protected. Check if it is a hull tet. + if (ishulltet(neightet)) { + // A hull tet. Collect it. + infect(neightet); + hullarray->newindex((void **) &parytet1); + *parytet1 = neightet; + // Both sides of this subface are exterior. + tspivot(neightet, checksh); + // Queue this subface (to be deleted later). + sinfect(checksh); // Only queue it once. + subfacstack->newindex((void **) &parysh); + *parysh = checksh; + } + } + } else { + // Both sides of this face are in exterior. + // If there is a subface. It should be collected. + if (issubface(neightet)) { + tspivot(neightet, checksh); + if (!sinfected(checksh)) { + sinfect(checksh); + subfacstack->newindex((void **) &parysh); + *parysh = checksh; + } + } + } + } // j, k + } // i + + if (b->regionattrib && (in->numberofregions > 0)) { + // Re-check saved region tets to see if they lie outside. + for (i = 0; i < in->numberofregions; i++) { + if ((regiontets[i].tet != NULL) && infected(regiontets[i])) { + if (b->verbose) { + printf("Warning: The %d-th region point ", i+1); + printf("lies in the exterior of the domain.\n"); + } + regiontets[i].tet = NULL; + } + } + } + + // Collect vertices which point to infected tets. These vertices + // may get deleted after the removal of exterior tets. + // If -Y1 option is used, collect all Steiner points for removal. + // The lists 'cavetetvertlist' and 'subvertstack' are re-used. + points->traversalinit(); + ptloop = pointtraverse(); + while (ptloop != NULL) { + if ((pointtype(ptloop) != UNUSEDVERTEX) && + (pointtype(ptloop) != DUPLICATEDVERTEX)) { + decode(point2tet(ptloop), neightet); + if (infected(neightet)) { + cavetetvertlist->newindex((void **) &parypt); + *parypt = ptloop; + } + if ((!b->cdt || b->nobisect) && (b->supsteiner_level > 0)) { // -Y/1 + // Queue it if it is a Steiner point. + if (pointmark(ptloop) > + (in->numberofpoints - (in->firstnumber ? 0 : 1))) { + subvertstack->newindex((void **) &parypt); + *parypt = ptloop; + } + } + } + ptloop = pointtraverse(); + } + + if (!b->convex && (tetarray->objects > 0l)) { // No -c option. + // Remove exterior tets. Hull tets are updated. + arraypool *newhullfacearray; + triface hulltet, casface; + face segloop, *paryseg; + point pa, pb, pc; + long delsegcount = 0l; + + // Collect segments which point to infected tets. Some segments + // may get deleted after the removal of exterior tets. + subsegs->traversalinit(); + segloop.sh = shellfacetraverse(subsegs); + while (segloop.sh != NULL) { + sstpivot1(segloop, neightet); + if (infected(neightet)) { + subsegstack->newindex((void **) &paryseg); + *paryseg = segloop; + } + segloop.sh = shellfacetraverse(subsegs); + } + + newhullfacearray = new arraypool(sizeof(triface), 10); + + // Create and save new hull tets. + for (i = 0; i < tetarray->objects; i++) { + parytet = (triface *) fastlookup(tetarray, i); + for (j = 0; j < 4; j++) { + decode(parytet->tet[j], tetloop); + if (!infected(tetloop)) { + // Found a new hull face (must be a subface). + tspivot(tetloop, checksh); + maketetrahedron(&hulltet); + pa = org(tetloop); + pb = dest(tetloop); + pc = apex(tetloop); + setvertices(hulltet, pb, pa, pc, dummypoint); + bond(tetloop, hulltet); + // Update the subface-to-tet map. + sesymself(checksh); + tsbond(hulltet, checksh); + // Update the segment-to-tet map. + for (k = 0; k < 3; k++) { + if (issubseg(tetloop)) { + tsspivot1(tetloop, checkseg); + tssbond1(hulltet, checkseg); + sstbond1(checkseg, hulltet); + } + enextself(tetloop); + eprevself(hulltet); + } + // Update the point-to-tet map. + setpoint2tet(pa, (tetrahedron) tetloop.tet); + setpoint2tet(pb, (tetrahedron) tetloop.tet); + setpoint2tet(pc, (tetrahedron) tetloop.tet); + // Save the exterior tet at this hull face. It still holds pointer + // to the adjacent interior tet. Use it to connect new hull tets. + newhullfacearray->newindex((void **) &parytet1); + parytet1->tet = parytet->tet; + parytet1->ver = j; + } // if (!infected(tetloop)) + } // j + } // i + + // Connect new hull tets. + for (i = 0; i < newhullfacearray->objects; i++) { + parytet = (triface *) fastlookup(newhullfacearray, i); + fsym(*parytet, neightet); + // Get the new hull tet. + fsym(neightet, hulltet); + for (j = 0; j < 3; j++) { + esym(hulltet, casface); + if (casface.tet[casface.ver & 3] == NULL) { + // Since the boundary of the domain may not be a manifold, we + // find the adjacent hull face by traversing the tets in the + // exterior (which are all infected tets). + neightet = *parytet; + while (1) { + fnextself(neightet); + if (!infected(neightet)) break; + } + if (!ishulltet(neightet)) { + // An interior tet. Get the new hull tet. + fsymself(neightet); + esymself(neightet); + } + // Bond them together. + bond(casface, neightet); + } + enextself(hulltet); + enextself(*parytet); + } // j + } // i + + if (subfacstack->objects > 0l) { + // Remove all subfaces which do not attach to any tetrahedron. + // Segments which are not attached to any subfaces and tets + // are deleted too. + face casingout, casingin; + + for (i = 0; i < subfacstack->objects; i++) { + parysh = (face *) fastlookup(subfacstack, i); + if (i == 0) { + if (b->verbose) { + printf("Warning: Removed an exterior face (%d, %d, %d) #%d\n", + pointmark(sorg(*parysh)), pointmark(sdest(*parysh)), + pointmark(sapex(*parysh)), shellmark(*parysh)); + } + } + // Dissolve this subface from face links. + for (j = 0; j < 3; j++) { + spivot(*parysh, casingout); + sspivot(*parysh, checkseg); + if (casingout.sh != NULL) { + casingin = casingout; + while (1) { + spivot(casingin, checksh); + if (checksh.sh == parysh->sh) break; + casingin = checksh; + } + if (casingin.sh != casingout.sh) { + // Update the link: ... -> casingin -> casingout ->... + sbond1(casingin, casingout); + } else { + // Only one subface at this edge is left. + sdissolve(casingout); + } + if (checkseg.sh != NULL) { + // Make sure the segment does not connect to a dead one. + ssbond(casingout, checkseg); + } + } else { + if (checkseg.sh != NULL) { + //if (checkseg.sh[3] != NULL) { + if (delsegcount == 0) { + if (b->verbose) { + printf("Warning: Removed an exterior segment (%d, %d) #%d\n", + pointmark(sorg(checkseg)), pointmark(sdest(checkseg)), + shellmark(checkseg)); + } + } + shellfacedealloc(subsegs, checkseg.sh); + delsegcount++; + } + } + senextself(*parysh); + } // j + // Delete this subface. + shellfacedealloc(subfaces, parysh->sh); + } // i + if (b->verbose) { + printf(" Deleted %ld subfaces.\n", subfacstack->objects); + } + subfacstack->restart(); + } // if (subfacstack->objects > 0l) + + if (subsegstack->objects > 0l) { + for (i = 0; i < subsegstack->objects; i++) { + paryseg = (face *) fastlookup(subsegstack, i); + if (paryseg->sh && (paryseg->sh[3] != NULL)) { + sstpivot1(*paryseg, neightet); + if (infected(neightet)) { + if (b->verbose) { + printf("Warning: Removed an exterior segment (%d, %d) #%d\n", + pointmark(sorg(*paryseg)), pointmark(sdest(*paryseg)), + shellmark(*paryseg)); + } + shellfacedealloc(subsegs, paryseg->sh); + delsegcount++; + } + } + } + subsegstack->restart(); + } // if (subsegstack->objects > 0l) + + if (delsegcount > 0) { + if (b->verbose) { + printf(" Deleted %ld segments.\n", delsegcount); + } + } + + if (cavetetvertlist->objects > 0l) { + // Some vertices may lie in exterior. Marke them as UNUSEDVERTEX. + long delvertcount = unuverts; + long delsteinercount = 0l; + + for (i = 0; i < cavetetvertlist->objects; i++) { + parypt = (point *) fastlookup(cavetetvertlist, i); + decode(point2tet(*parypt), neightet); + if (infected(neightet)) { + // Found an exterior vertex. + if (pointmark(*parypt) > + (in->numberofpoints - (in->firstnumber ? 0 : 1))) { + // A Steiner point. + if (pointtype(*parypt) == FREESEGVERTEX) { + st_segref_count--; + } else if (pointtype(*parypt) == FREEFACETVERTEX) { + st_facref_count--; + } else { + st_volref_count--; + } + delsteinercount++; + if (steinerleft > 0) steinerleft++; + } + setpointtype(*parypt, UNUSEDVERTEX); + unuverts++; + } + } + + if (b->verbose) { + if (unuverts > delvertcount) { + if (delsteinercount > 0l) { + if (unuverts > (delvertcount + delsteinercount)) { + printf(" Removed %ld exterior input vertices.\n", + unuverts - delvertcount - delsteinercount); + } + printf(" Removed %ld exterior Steiner vertices.\n", + delsteinercount); + } else { + printf(" Removed %ld exterior input vertices.\n", + unuverts - delvertcount); + } + } + } + cavetetvertlist->restart(); + // Comment: 'subvertstack' will be cleaned in routine + // suppresssteinerpoints(). + } // if (cavetetvertlist->objects > 0l) + + // Update the hull size. + hullsize += (newhullfacearray->objects - hullarray->objects); + + // Delete all exterior tets and old hull tets. + for (i = 0; i < tetarray->objects; i++) { + parytet = (triface *) fastlookup(tetarray, i); + tetrahedrondealloc(parytet->tet); + } + tetarray->restart(); + + for (i = 0; i < hullarray->objects; i++) { + parytet = (triface *) fastlookup(hullarray, i); + tetrahedrondealloc(parytet->tet); + } + hullarray->restart(); + + delete newhullfacearray; + } // if (!b->convex && (tetarray->objects > 0l)) + + if (b->convex && (tetarray->objects > 0l)) { // With -c option + // In this case, all exterior tets get a region marker '-1'. + int attrnum = numelemattrib - 1; + + for (i = 0; i < tetarray->objects; i++) { + parytet = (triface *) fastlookup(tetarray, i); + setelemattribute(parytet->tet, attrnum, -1); + } + tetarray->restart(); + + for (i = 0; i < hullarray->objects; i++) { + parytet = (triface *) fastlookup(hullarray, i); + uninfect(*parytet); + } + hullarray->restart(); + + if (subfacstack->objects > 0l) { + for (i = 0; i < subfacstack->objects; i++) { + parysh = (face *) fastlookup(subfacstack, i); + suninfect(*parysh); + } + subfacstack->restart(); + } + + if (cavetetvertlist->objects > 0l) { + cavetetvertlist->restart(); + } + } // if (b->convex && (tetarray->objects > 0l)) + + if (b->regionattrib) { // With -A option. + if (!b->quiet) { + printf("Spreading region attributes.\n"); + } + REAL volume; + int attr, maxattr = 0; // Choose a small number here. + int attrnum = numelemattrib - 1; + // Comment: The element region marker is at the end of the list of + // the element attributes. + int regioncount = 0; + + arraypool *tmpary = new arraypool(sizeof(int), 8); + int *paryint; + + // If has user-defined region attributes. + if (in->numberofregions > 0) { + // Spread region attributes. + for (i = 0; i < 5 * in->numberofregions; i += 5) { + if (regiontets[i/5].tet != NULL) { + attr = (int) in->regionlist[i + 3]; + if (attr > maxattr) { + maxattr = attr; + } + volume = in->regionlist[i + 4]; + tetarray->restart(); // Re-use this array. + infect(regiontets[i/5]); + tetarray->newindex((void **) &parytet); + *parytet = regiontets[i/5]; + // Collect and set attrs for all tets of this region. + for (j = 0; j < tetarray->objects; j++) { + parytet = (triface *) fastlookup(tetarray, j); + tetloop = *parytet; + setelemattribute(tetloop.tet, attrnum, attr); + if (b->varvolume) { // If has -a option. + setvolumebound(tetloop.tet, volume); + } + for (k = 0; k < 4; k++) { + decode(tetloop.tet[k], neightet); + // Is the adjacent already checked? + if (!infected(neightet)) { + // Is this side protected by a subface? + if (!issubface(neightet)) { + infect(neightet); + tetarray->newindex((void **) &parytet); + *parytet = neightet; + } + } + } // k + } // j + tmpary->newindex((void **) &paryint); + *paryint = attr; + regioncount++; + } // if (regiontets[i/5].tet != NULL) + } // i + } + + // Set attributes for all tetrahedra. + attr = maxattr + 1; + tetrahedrons->traversalinit(); + tetloop.tet = tetrahedrontraverse(); + while (tetloop.tet != (tetrahedron *) NULL) { + if (!infected(tetloop)) { + // An unmarked region. + tetarray->restart(); // Re-use this array. + infect(tetloop); + tetarray->newindex((void **) &parytet); + *parytet = tetloop; + // Find and mark all tets. + for (j = 0; j < tetarray->objects; j++) { + parytet = (triface *) fastlookup(tetarray, j); + tetloop = *parytet; + setelemattribute(tetloop.tet, attrnum, attr); + for (k = 0; k < 4; k++) { + decode(tetloop.tet[k], neightet); + // Is the adjacent tet already checked? + if (!infected(neightet)) { + // Is this side protected by a subface? + if (!issubface(neightet)) { + infect(neightet); + tetarray->newindex((void **) &parytet); + *parytet = neightet; + } + } + } // k + } // j + tmpary->newindex((void **) &paryint); + *paryint = attr; + attr++; // Increase the attribute. + regioncount++; + } + tetloop.tet = tetrahedrontraverse(); + } + + // Uninfect processed tets. + tetrahedrons->traversalinit(); + tetloop.tet = tetrahedrontraverse(); + while (tetloop.tet != (tetrahedron *) NULL) { + uninfect(tetloop); + tetloop.tet = tetrahedrontraverse(); + } + + // Until here, every tet has a region attribute. + subdomains = regioncount; // Remember it for output. + subdomain_markers = new int[subdomains]; + for (i = 0; i < subdomains; i++) { + paryint = (int *) fastlookup(tmpary, i); + subdomain_markers[i] = *paryint; + } + delete tmpary; + + if (b->verbose) { + //assert(regioncount > 0); + if (regioncount > 1) { + printf(" Found %d subdomains.\n", regioncount); + } else { + printf(" Found %d domain.\n", regioncount); + } + } + } // if (b->regionattrib) + + if (regiontets != NULL) { + delete [] regiontets; + } + delete tetarray; + delete hullarray; + + if (!b->convex) { // No -c option + // The mesh is non-convex now. + nonconvex = 1; + + // Push all hull tets into 'flipstack'. + tetrahedrons->traversalinit(); + tetloop.ver = 11; // The face opposite to dummypoint. + tetloop.tet = alltetrahedrontraverse(); + while (tetloop.tet != (tetrahedron *) NULL) { + if ((point) tetloop.tet[7] == dummypoint) { + fsym(tetloop, neightet); + flippush(flipstack, &neightet); + } + tetloop.tet = alltetrahedrontraverse(); + } + + flipconstraints fc; + fc.enqflag = 2; + long sliver_peel_count = lawsonflip3d(&fc); + + if (sliver_peel_count > 0l) { + if (b->verbose) { + printf(" Removed %ld hull slivers.\n", sliver_peel_count); + } + } + unflipqueue->restart(); + } // if (!b->convex) +} + +// [2018-07-30] +// Search a face with given indices (i,j,k). +// This function is only called when the default fast search fails. +// It is possible when there are non-manifold edges on the hull. +// On finish, tetloop return this face if it exists, otherwise, return 0. +int tetgenmesh::search_face(point pi, point pj, point pk, triface &tetloop) +{ + pinfect(pi); + pinfect(pj); + pinfect(pk); + + int t1ver; + triface t, t1; + point *pts, toppo; + int pcount = 0; + + t.ver = t1.ver = 0; + tetrahedrons->traversalinit(); + t.tet = tetrahedrontraverse(); + while (t.tet != NULL) { + pts = (point *) t.tet; + pcount = 0; + if (pinfected(pts[4])) pcount++; + if (pinfected(pts[5])) pcount++; + if (pinfected(pts[6])) pcount++; + if (pinfected(pts[7])) pcount++; + + if (pcount == 3) { + // Found a tet containing this face. + for (t.ver = 0; t.ver < 4; t.ver++) { + toppo = oppo(t); + if (!pinfected(toppo)) break; + } + int ii; + for (ii = 0; ii < 3; ii++) { + if (org(t) == pi) break; + enextself(t); + } + if (dest(t) == pj) { + } else { + eprevself(t); + fsymself(t); + } + break; + } + t.tet = tetrahedrontraverse(); + } + + puninfect(pi); + puninfect(pj); + puninfect(pk); + + if (t.tet != NULL) { + tetloop = t; + return 1; + } else { + return 0; + } +} + +int tetgenmesh::search_edge(point p0, point p1, triface &tetloop) +{ + triface t; + int ii; + + tetrahedrons->traversalinit(); + t.tet = tetrahedrontraverse(); + while (t.tet != NULL) { + for (ii = 0; ii < 6; ii++) { + t.ver = edge2ver[ii]; + if (((org(t) == p0) && (dest(t) == p1)) || + ((org(t) == p1) && (dest(t) == p0))) { + // Found the tet. + tetloop = t; + return 1; + } + } + t.tet = tetrahedrontraverse(); + } + + tetloop.tet = NULL; + return 0; +} + +//============================================================================// +// // +// reconstructmesh() Reconstruct a tetrahedral mesh. // +// // +//============================================================================// + +void tetgenmesh::reconstructmesh() +{ + tetrahedron *ver2tetarray; + point *idx2verlist; + triface tetloop, checktet, prevchktet; + triface hulltet, face1, face2; + tetrahedron tptr; + face subloop, neighsh, nextsh; + face segloop; + shellface sptr; + point p[4], q[3]; + REAL ori, attrib, volume; + REAL cosang_tol, cosang; + REAL n1[3], n2[3]; + int eextras, marker = 0; + int bondflag; + int t1ver; + int idx, i, j, k; + + if (!b->quiet) { + printf("Reconstructing mesh ...\n"); + } + + if (b->convex) { // -c option. + // Assume the mesh is convex. Exterior tets have region attribute -1. + if (!(in->numberoftetrahedronattributes > 0)) { + terminatetetgen(this, 2); + } + } else { + // Assume the mesh is non-convex. + nonconvex = 1; + } + + // Create a map from indices to vertices. + makeindex2pointmap(idx2verlist); + // 'idx2verlist' has length 'in->numberofpoints + 1'. + if (in->firstnumber == 1) { + idx2verlist[0] = dummypoint; // Let 0th-entry be dummypoint. + } + + // Allocate an array that maps each vertex to its adjacent tets. + ver2tetarray = new tetrahedron[in->numberofpoints + 1]; + unuverts = in->numberofpoints; // All vertices are unused yet. + //for (i = 0; i < in->numberofpoints + 1; i++) { + for (i = in->firstnumber; i < in->numberofpoints + in->firstnumber; i++) { + ver2tetarray[i] = NULL; + } + + // Create the tetrahedra and connect those that share a common face. + for (i = 0; i < in->numberoftetrahedra; i++) { + // Get the four vertices. + idx = i * in->numberofcorners; + for (j = 0; j < 4; j++) { + p[j] = idx2verlist[in->tetrahedronlist[idx++]]; + if (pointtype(p[j]) == UNUSEDVERTEX) { + setpointtype(p[j], VOLVERTEX); // initial type. + unuverts--; + } + } + // Check the orientation. + ori = orient3d(p[0], p[1], p[2], p[3]); + if (ori > 0.0) { + // Swap the first two vertices. + q[0] = p[0]; p[0] = p[1]; p[1] = q[0]; + } else if (ori == 0.0) { + if (!b->quiet) { + printf("Warning: Tet #%d is degenerate.\n", i + in->firstnumber); + } + } + // Create a new tetrahedron. + maketetrahedron(&tetloop); // tetloop.ver = 11. + setvertices(tetloop, p[0], p[1], p[2], p[3]); + // Set element attributes if they exist. + for (j = 0; j < in->numberoftetrahedronattributes; j++) { + idx = i * in->numberoftetrahedronattributes; + attrib = in->tetrahedronattributelist[idx + j]; + setelemattribute(tetloop.tet, j, attrib); + } + // If -a switch is used (with no number follows) Set a volume + // constraint if it exists. + if (b->varvolume) { + if (in->tetrahedronvolumelist != (REAL *) NULL) { + volume = in->tetrahedronvolumelist[i]; + } else { + volume = -1.0; + } + setvolumebound(tetloop.tet, volume); + } + // Try connecting this tet to others that share the common faces. + for (tetloop.ver = 0; tetloop.ver < 4; tetloop.ver++) { + p[3] = oppo(tetloop); + // Look for other tets having this vertex. + idx = pointmark(p[3]); + tptr = ver2tetarray[idx]; + // Link the current tet to the next one in the stack. + tetloop.tet[8 + tetloop.ver] = tptr; + // Push the current tet onto the stack. + ver2tetarray[idx] = encode(tetloop); + decode(tptr, checktet); + if (checktet.tet != NULL) { + p[0] = org(tetloop); // a + p[1] = dest(tetloop); // b + p[2] = apex(tetloop); // c + prevchktet = tetloop; + do { + q[0] = org(checktet); // a' + q[1] = dest(checktet); // b' + q[2] = apex(checktet); // c' + // Check the three faces at 'd' in 'checktet'. + bondflag = 0; + for (j = 0; j < 3; j++) { + // Go to the face [b',a',d], or [c',b',d], or [a',c',d]. + esym(checktet, face2); + if (face2.tet[face2.ver & 3] == NULL) { + k = ((j + 1) % 3); + if (q[k] == p[0]) { // b', c', a' = a + if (q[j] == p[1]) { // a', b', c' = b + // [#,#,d] is matched to [b,a,d]. + esym(tetloop, face1); + bond(face1, face2); + bondflag++; + } + } + if (q[k] == p[1]) { // b',c',a' = b + if (q[j] == p[2]) { // a',b',c' = c + // [#,#,d] is matched to [c,b,d]. + enext(tetloop, face1); + esymself(face1); + bond(face1, face2); + bondflag++; + } + } + if (q[k] == p[2]) { // b',c',a' = c + if (q[j] == p[0]) { // a',b',c' = a + // [#,#,d] is matched to [a,c,d]. + eprev(tetloop, face1); + esymself(face1); + bond(face1, face2); + bondflag++; + } + } + } else { + bondflag++; + } + enextself(checktet); + } // j + // Go to the next tet in the link. + tptr = checktet.tet[8 + checktet.ver]; + if (bondflag == 3) { + // All three faces at d in 'checktet' have been connected. + // It can be removed from the link. + prevchktet.tet[8 + prevchktet.ver] = tptr; + } else { + // Bakup the previous tet in the link. + prevchktet = checktet; + } + decode(tptr, checktet); + } while (checktet.tet != NULL); + } // if (checktet.tet != NULL) + } // for (tetloop.ver = 0; ... + } // i + + // Remember a tet of the mesh. + recenttet = tetloop; + + // Create hull tets, create the point-to-tet map, and clean up the + // temporary spaces used in each tet. + hullsize = tetrahedrons->items; + + tetrahedrons->traversalinit(); + tetloop.tet = tetrahedrontraverse(); + while (tetloop.tet != (tetrahedron *) NULL) { + tptr = encode(tetloop); + for (tetloop.ver = 0; tetloop.ver < 4; tetloop.ver++) { + if (tetloop.tet[tetloop.ver] == NULL) { + // Create a hull tet. + maketetrahedron(&hulltet); + p[0] = org(tetloop); + p[1] = dest(tetloop); + p[2] = apex(tetloop); + setvertices(hulltet, p[1], p[0], p[2], dummypoint); + bond(tetloop, hulltet); + // Try connecting this to others that share common hull edges. + for (j = 0; j < 3; j++) { + fsym(hulltet, face2); + while (1) { + if (face2.tet == NULL) break; + esymself(face2); + if (apex(face2) == dummypoint) break; + fsymself(face2); + } + if (face2.tet != NULL) { + // Found an adjacent hull tet. + esym(hulltet, face1); + bond(face1, face2); + } + enextself(hulltet); + } + } + // Create the point-to-tet map. + setpoint2tet((point) (tetloop.tet[4 + tetloop.ver]), tptr); + // Clean the temporary used space. + tetloop.tet[8 + tetloop.ver] = NULL; + } + tetloop.tet = tetrahedrontraverse(); + } + + hullsize = tetrahedrons->items - hullsize; + + // Subfaces will be inserted into the mesh. + if (in->trifacelist != NULL) { + // A .face file is given. It may contain boundary faces. Insert them. + for (i = 0; i < in->numberoftrifaces; i++) { + // Is it a subface? + if (in->trifacemarkerlist != NULL) { + marker = in->trifacemarkerlist[i]; + } else { + // Face markers are not available. Assume all of them are subfaces. + marker = -1; // The default marker. + } + if (marker != 0) { + idx = i * 3; + for (j = 0; j < 3; j++) { + p[j] = idx2verlist[in->trifacelist[idx++]]; + } + // Search the subface. + bondflag = 0; + neighsh.sh = NULL; + // Make sure all vertices are in the mesh. Avoid crash. + for (j = 0; j < 3; j++) { + decode(point2tet(p[j]), checktet); + if (checktet.tet == NULL) break; + } + if ((j == 3) && getedge(p[0], p[1], &checktet)) { + tetloop = checktet; + q[2] = apex(checktet); + while (1) { + if (apex(tetloop) == p[2]) { + // Found the face. + // Check if there exist a subface already? + tspivot(tetloop, neighsh); + if (neighsh.sh != NULL) { + // Found a duplicated subface. + // This happens when the mesh was generated by other mesher. + bondflag = 0; + } else { + bondflag = 1; + } + break; + } + fnextself(tetloop); + if (apex(tetloop) == q[2]) break; + } + } + if (!bondflag) { + if (neighsh.sh == NULL) { + if (b->verbose > 1) { + printf("Warning: Searching subface #%d [%d,%d,%d] mark=%d.\n", + i + in->firstnumber, pointmark(p[0]), pointmark(p[1]), + pointmark(p[2]), marker); + } + // Search it globally. + if (search_face(p[0], p[1], p[2], tetloop)) { + bondflag = 1; + } + } + } + if (bondflag) { + // Create a new subface. + makeshellface(subfaces, &subloop); + setshvertices(subloop, p[0], p[1], p[2]); + // Create the point-to-subface map. + sptr = sencode(subloop); + for (j = 0; j < 3; j++) { + setpointtype(p[j], FACETVERTEX); // initial type. + setpoint2sh(p[j], sptr); + } + setshellmark(subloop, marker); + // Insert the subface into the mesh. + tsbond(tetloop, subloop); + fsymself(tetloop); + sesymself(subloop); + tsbond(tetloop, subloop); + } else { + if (neighsh.sh != NULL) { + // The subface already exists. Only set its mark. + setshellmark(neighsh, marker); + } else { + if (!b->quiet) { + printf("Warning: Subface #%d [%d,%d,%d] mark=%d is not found.\n", + i + in->firstnumber, pointmark(p[0]), pointmark(p[1]), + pointmark(p[2]), marker); + } + } + } // if (bondflag) + } // if (marker != 0) + } // i + } // if (in->trifacelist) + + // Indentify subfaces from the mesh. + // Create subfaces for hull faces (if they're not subface yet) and + // interior faces which separate two different materials. + eextras = in->numberoftetrahedronattributes; + tetrahedrons->traversalinit(); + tetloop.tet = tetrahedrontraverse(); + while (tetloop.tet != (tetrahedron *) NULL) { + for (tetloop.ver = 0; tetloop.ver < 4; tetloop.ver++) { + tspivot(tetloop, neighsh); + if (neighsh.sh == NULL) { + bondflag = 0; + fsym(tetloop, checktet); + if (ishulltet(checktet)) { + // A hull face. + if (!b->convex) { + bondflag = 1; // Insert a hull subface. + } + } else { + if (eextras > 0) { + if (elemattribute(tetloop.tet, eextras - 1) != + elemattribute(checktet.tet, eextras - 1)) { + bondflag = 1; // Insert an interior interface. + } + } + } + if (bondflag) { + // Create a new subface. + makeshellface(subfaces, &subloop); + p[0] = org(tetloop); + p[1] = dest(tetloop); + p[2] = apex(tetloop); + setshvertices(subloop, p[0], p[1], p[2]); + // Create the point-to-subface map. + sptr = sencode(subloop); + for (j = 0; j < 3; j++) { + setpointtype(p[j], FACETVERTEX); // initial type. + setpoint2sh(p[j], sptr); + } + setshellmark(subloop, -1); // Default marker. + // Insert the subface into the mesh. + tsbond(tetloop, subloop); + sesymself(subloop); + tsbond(checktet, subloop); + } // if (bondflag) + } // if (neighsh.sh == NULL) + } + tetloop.tet = tetrahedrontraverse(); + } + + // Connect subfaces together. + subfaces->traversalinit(); + subloop.shver = 0; + subloop.sh = shellfacetraverse(subfaces); + while (subloop.sh != (shellface *) NULL) { + for (i = 0; i < 3; i++) { + spivot(subloop, neighsh); + if (neighsh.sh == NULL) { + // Form a subface ring by linking all subfaces at this edge. + // Traversing all faces of the tets at this edge. + stpivot(subloop, tetloop); + q[2] = apex(tetloop); + neighsh = subloop; + while (1) { + fnextself(tetloop); + tspivot(tetloop, nextsh); + if (nextsh.sh != NULL) { + // Do not connect itself. + if (nextsh.sh != neighsh.sh) { + // Link neighsh <= nextsh. + sbond1(neighsh, nextsh); + neighsh = nextsh; + } + } + if (apex(tetloop) == q[2]) { + break; + } + } // while (1) + } // if (neighsh.sh == NULL) + senextself(subloop); + } + subloop.sh = shellfacetraverse(subfaces); + } + + + // Segments will be introduced. + if (in->edgelist != NULL) { + // A .edge file is given. It may contain boundary edges. Insert them. + for (i = 0; i < in->numberofedges; i++) { + // Is it a segment? + if (in->edgemarkerlist != NULL) { + marker = in->edgemarkerlist[i]; + } else { + // Edge markers are not available. Assume all of them are segments. + marker = -1; // Default marker. + } + if (marker != 0) { + // Insert a segment. + idx = i * 2; + for (j = 0; j < 2; j++) { + p[j] = idx2verlist[in->edgelist[idx++]]; + } + // Make sure all vertices are in the mesh. Avoid crash. + for (j = 0; j < 2; j++) { + decode(point2tet(p[j]), checktet); + if (checktet.tet == NULL) break; + } + // Search the segment. + bondflag = 0; + if (j == 2) { + if (getedge(p[0], p[1], &checktet)) { + bondflag = 1; + } else { + if (b->verbose > 1) { + printf("Warning: Searching segment #%d [%d,%d] mark=%d.\n", + i + in->firstnumber, pointmark(p[0]), pointmark(p[1]), marker); + } + // Search it globally. + if (search_edge(p[0], p[1], checktet)) { + bondflag = 1; + } + } + } + if (bondflag > 0) { + // Create a new segment. + makeshellface(subsegs, &segloop); + setshvertices(segloop, p[0], p[1], NULL); + // Create the point-to-segment map. + sptr = sencode(segloop); + for (j = 0; j < 2; j++) { + setpointtype(p[j], RIDGEVERTEX); // initial type. + setpoint2sh(p[j], sptr); + } + setshellmark(segloop, marker); + // Insert the segment into the mesh. + tetloop = checktet; + q[2] = apex(checktet); + subloop.sh = NULL; + while (1) { + tssbond1(tetloop, segloop); + tspivot(tetloop, subloop); + if (subloop.sh != NULL) { + ssbond1(subloop, segloop); + sbond1(segloop, subloop); + } + fnextself(tetloop); + if (apex(tetloop) == q[2]) break; + } // while (1) + // Remember an adjacent tet for this segment. + sstbond1(segloop, tetloop); + } else { + if (!b->quiet) { + printf("Warning: Segment #%d [%d,%d] is missing.\n", + i + in->firstnumber, pointmark(p[0]), pointmark(p[1])); + } + } + } // if (marker != 0) + } // i + } // if (in->edgelist) + + // Identify segments from the mesh. + // Create segments for non-manifold edges (which are shared by more + // than two subfaces), and for non-coplanar edges, i.e., two subfaces + // form an dihedral angle > 'b->facet_separate_ang_tol' (degree). + cosang_tol = cos(b->facet_separate_ang_tol / 180.0 * PI); + subfaces->traversalinit(); + subloop.shver = 0; + subloop.sh = shellfacetraverse(subfaces); + while (subloop.sh != (shellface *) NULL) { + for (i = 0; i < 3; i++) { + sspivot(subloop, segloop); + if (segloop.sh == NULL) { + // Check if this edge is a segment. + bondflag = 0; + // Counter the number of subfaces at this edge. + idx = 0; + spivot(subloop, nextsh); + if (nextsh.sh != NULL) { + nextsh = subloop; + while (1) { + idx++; + spivotself(nextsh); + if (nextsh.sh == subloop.sh) break; + } + } else { + // There is only one subface at this edge. + idx = 1; + } + if (idx != 2) { + // It's a non-manifold edge. Insert a segment. + p[0] = sorg(subloop); + p[1] = sdest(subloop); + bondflag = 1; + } else { + // There are two subfaces at this edge. + spivot(subloop, neighsh); + if (shellmark(subloop) != shellmark(neighsh)) { + // It's an interior interface. Insert a segment. + p[0] = sorg(subloop); + p[1] = sdest(subloop); + bondflag = 1; + } else { + if (!b->convex) { + // Check the dihedral angle formed by the two subfaces. + p[0] = sorg(subloop); + p[1] = sdest(subloop); + p[2] = sapex(subloop); + p[3] = sapex(neighsh); + facenormal(p[0], p[1], p[2], n1, 1, NULL); + facenormal(p[0], p[1], p[3], n2, 1, NULL); + cosang = dot(n1, n2) / (sqrt(dot(n1, n1)) * sqrt(dot(n2, n2))); + // Rounding. + if (cosang > 1.0) cosang = 1.0; + else if (cosang < -1.0) cosang = -1.0; + if (cosang > cosang_tol) { + bondflag = 1; + } + } + } + } + if (bondflag) { + // Create a new segment. + makeshellface(subsegs, &segloop); + setshvertices(segloop, p[0], p[1], NULL); + // Create the point-to-segment map. + sptr = sencode(segloop); + for (j = 0; j < 2; j++) { + setpointtype(p[j], RIDGEVERTEX); // initial type. + setpoint2sh(p[j], sptr); + } + setshellmark(segloop, -1); // Default marker. + // Insert the subface into the mesh. + stpivot(subloop, tetloop); + q[2] = apex(tetloop); + while (1) { + tssbond1(tetloop, segloop); + tspivot(tetloop, neighsh); + if (neighsh.sh != NULL) { + ssbond1(neighsh, segloop); + } + fnextself(tetloop); + if (apex(tetloop) == q[2]) break; + } // while (1) + // Remember an adjacent tet for this segment. + sstbond1(segloop, tetloop); + sbond1(segloop, subloop); + } // if (bondflag) + } // if (neighsh.sh == NULL) + senextself(subloop); + } // i + subloop.sh = shellfacetraverse(subfaces); + } + + // Remember the number of input segments. + insegments = subsegs->items; + + if (!b->nobisect || checkconstraints) { + // Mark Steiner points on segments and facets. + // - all vertices which remaining type FEACTVERTEX become + // Steiner points in facets (= FREEFACERVERTEX). + // - vertices on segment need to be checked. + face* segperverlist; + int* idx2seglist; + face parentseg, nextseg; + verttype vt; + REAL area, len; // l1, l2; + int fmarker; + + makepoint2submap(subsegs, idx2seglist, segperverlist); + + points->traversalinit(); + point ptloop = pointtraverse(); + while (ptloop != NULL) { + vt = pointtype(ptloop); + if (vt == VOLVERTEX) { + setpointtype(ptloop, FREEVOLVERTEX); + st_volref_count++; + } else if (vt == FACETVERTEX) { + setpointtype(ptloop, FREEFACETVERTEX); + st_facref_count++; + } else if (vt == RIDGEVERTEX) { + idx = pointmark(ptloop) - in->firstnumber; + if ((idx2seglist[idx + 1] - idx2seglist[idx]) == 2) { + i = idx2seglist[idx]; + parentseg = segperverlist[i]; + nextseg = segperverlist[i + 1]; + sesymself(nextseg); + p[0] = sorg(nextseg); + p[1] = sdest(parentseg); + // Check if three points p[0], ptloop, p[2] are (nearly) collinear. + if (is_collinear_at(ptloop, p[0], p[1])) { + // They are (nearly) collinear. + setpointtype(ptloop, FREESEGVERTEX); + // Connect nextseg and parentseg together at ptloop. + senextself(nextseg); + senext2self(parentseg); + sbond(nextseg, parentseg); + st_segref_count++; + } + } + } + ptloop = pointtraverse(); + } + + // Are there area constraints? + if (b->quality && (in->facetconstraintlist != (REAL *) NULL)) { + // Set maximum area constraints on facets. + for (i = 0; i < in->numberoffacetconstraints; i++) { + fmarker = (int) in->facetconstraintlist[i * 2]; + area = in->facetconstraintlist[i * 2 + 1]; + subfaces->traversalinit(); + subloop.sh = shellfacetraverse(subfaces); + while (subloop.sh != NULL) { + if (shellmark(subloop) == fmarker) { + setareabound(subloop, area); + } + subloop.sh = shellfacetraverse(subfaces); + } + } + } + + // Are there length constraints? + if (b->quality && (in->segmentconstraintlist != (REAL *) NULL)) { + // Set maximum length constraints on segments. + int e1, e2; + for (i = 0; i < in->numberofsegmentconstraints; i++) { + e1 = (int) in->segmentconstraintlist[i * 3]; + e2 = (int) in->segmentconstraintlist[i * 3 + 1]; + len = in->segmentconstraintlist[i * 3 + 2]; + // Search for edge [e1, e2]. + idx = e1 - in->firstnumber; + for (j = idx2seglist[idx]; j < idx2seglist[idx + 1]; j++) { + parentseg = segperverlist[j]; + if (pointmark(sdest(parentseg)) == e2) { + setareabound(parentseg, len); + break; + } + } + } + } + + delete [] idx2seglist; + delete [] segperverlist; + } + + + // Set global flags. + checksubsegflag = 1; + checksubfaceflag = 1; + + delete [] idx2verlist; + delete [] ver2tetarray; +} + +//============================================================================// +// // +// scoutpoint() Search a point in mesh. // +// // +// This function searches the point in a mesh whose domain may be not convex. // +// In case of a convex domain, the locate() function is sufficient. // +// // +// If 'randflag' is used, randomly select a start searching tet. Otherwise, // +// start searching directly from 'searchtet'. // +// // +//============================================================================// + +int tetgenmesh::scout_point(point searchpt, triface *searchtet, int randflag) +{ + if (b->verbose > 3) { + printf(" Scout point %d.\n", pointmark(searchpt)); + } + // randflag is not used. + enum locateresult loc = OUTSIDE; + int maxiter = 100, iter = 0; + + do { + // 'searchtet' must be a valid tetrahedron. + if (searchtet->tet == NULL) { + // Randomly select a good starting tet. + randomsample(searchpt, searchtet); + } + + if (ishulltet(*searchtet)) { + if ((recenttet.tet != NULL) && !ishulltet(recenttet)) { + *searchtet = recenttet; + } + } + + if (ishulltet(*searchtet)) { + int t1ver; + searchtet->ver = 11; + fsymself(*searchtet); + } + + loc = locate_point_walk(searchpt, searchtet, 0); // encflg = 0. + + if (loc == OUTSIDE) { + //randomsample(searchpt, searchtet); + searchtet->tet = NULL; + } + + iter++; + if (iter < maxiter) break; + } while (loc != OUTSIDE); + + if (loc == INTETRAHEDRON) { + // Check if this vertex is nearly on subfacet. + triface chktet = *searchtet; + for (chktet.ver = 0; chktet.ver < 4; chktet.ver++) { + if (issubface(chktet)) { + point pa = org(chktet); + point pb = org(chktet); + point pc = org(chktet); + REAL ori = orient3d(pa, pb, pc, searchpt); + REAL averlen = (distance(pa, pb) + + distance(pb, pc) + + distance(pc, pa)) / 3.; + REAL len3 = averlen * averlen * averlen; + REAL ratio = (-ori) / len3; + if (ratio < b->epsilon) { + *searchtet = chktet; + loc = ONFACE; + break; + } + } + } + } // if (loc == INTETRAHEDRON) + + if (loc == ONFACE) { + // Check if this vertex is nearly on a subsegment. + triface chkface = *searchtet; + for (int i = 0; i < 3; i++) { + if (issubseg(chkface)) { + REAL cosang = cos_interiorangle(searchpt, org(chkface), dest(chkface)); + if (cosang < cos_collinear_ang_tol) { // -p////179.9 + *searchtet = chkface; + loc = ONEDGE; + break; + } + } + enextself(chkface); + } + } // if (loc == ONFACE) + + if (loc == ONEDGE) { + // Check if this vertex is nearly on a vertex. + triface chkedge = *searchtet; + for (int i = 0; i < 2; i++) { + REAL dd = distance(searchpt, org(chkedge)); + if (dd < minedgelength) { + *searchtet = chkedge; + loc = ONVERTEX; + break; + } + esymself(chkedge); + } + } + + return (int) loc; +} + +//============================================================================// +// // +// getpointmeshsize() Interpolate the mesh size at given point. // +// // +// 'iloc' indicates the location of the point w.r.t. 'searchtet'. The size // +// is obtained by linear interpolation on the vertices of the tet. // +// // +//============================================================================// + +REAL tetgenmesh::getpointmeshsize(point searchpt, triface *searchtet, int iloc) +{ + point *pts, pa, pb, pc; + REAL volume, vol[4], wei[4]; + REAL size; + int i; + + size = 0; + + if (iloc == (int) INTETRAHEDRON) { + pts = (point *) &(searchtet->tet[4]); + // Only do interpolation if all vertices have non-zero sizes. + if ((pts[0][pointmtrindex] > 0) && (pts[1][pointmtrindex] > 0) && + (pts[2][pointmtrindex] > 0) && (pts[3][pointmtrindex] > 0)) { + // P1 interpolation. + volume = orient3dfast(pts[0], pts[1], pts[2], pts[3]); + vol[0] = orient3dfast(searchpt, pts[1], pts[2], pts[3]); + vol[1] = orient3dfast(pts[0], searchpt, pts[2], pts[3]); + vol[2] = orient3dfast(pts[0], pts[1], searchpt, pts[3]); + vol[3] = orient3dfast(pts[0], pts[1], pts[2], searchpt); + for (i = 0; i < 4; i++) { + wei[i] = fabs(vol[i] / volume); + size += (wei[i] * pts[i][pointmtrindex]); + } + } + } else if (iloc == (int) ONFACE) { + pa = org(*searchtet); + pb = dest(*searchtet); + pc = apex(*searchtet); + if ((pa[pointmtrindex] > 0) && (pb[pointmtrindex] > 0) && + (pc[pointmtrindex] > 0)) { + volume = triarea(pa, pb, pc); + vol[0] = triarea(searchpt, pb, pc); + vol[1] = triarea(pa, searchpt, pc); + vol[2] = triarea(pa, pb, searchpt); + size = (vol[0] / volume) * pa[pointmtrindex] + + (vol[1] / volume) * pb[pointmtrindex] + + (vol[2] / volume) * pc[pointmtrindex]; + } + } else if (iloc == (int) ONEDGE) { + pa = org(*searchtet); + pb = dest(*searchtet); + if ((pa[pointmtrindex] > 0) && (pb[pointmtrindex] > 0)) { + volume = distance(pa, pb); + vol[0] = distance(searchpt, pb); + vol[1] = distance(pa, searchpt); + size = (vol[0] / volume) * pa[pointmtrindex] + + (vol[1] / volume) * pb[pointmtrindex]; + } + } else if (iloc == (int) ONVERTEX) { + pa = org(*searchtet); + if (pa[pointmtrindex] > 0) { + size = pa[pointmtrindex]; + } + } + + return size; +} + +//============================================================================// +// // +// interpolatemeshsize() Interpolate the mesh size from a background mesh // +// (source) to the current mesh (destination). // +// // +//============================================================================// + +void tetgenmesh::interpolatemeshsize() +{ + triface searchtet; + point ploop; + REAL minval = 0.0, maxval = 0.0; + int iloc; + int count; + + if (!b->quiet) { + printf("Interpolating mesh size ...\n"); + } + + long bak_nonregularcount = nonregularcount; + nonregularcount = 0l; // Count the number of (slow) global searches. + long baksmaples = bgm->samples; + bgm->samples = 3l; + count = 0; // Count the number of interpolated points. + + // Interpolate sizes for all points in the current mesh. + points->traversalinit(); + ploop = pointtraverse(); + while (ploop != NULL) { + // Search a tet in bgm which containing this point. + searchtet.tet = NULL; + iloc = bgm->scout_point(ploop, &searchtet, 1); // randflag = 1 + if (iloc != (int) OUTSIDE) { + // Interpolate the mesh size. + ploop[pointmtrindex] = bgm->getpointmeshsize(ploop, &searchtet, iloc); + setpoint2bgmtet(ploop, bgm->encode(searchtet)); + if (count == 0) { + // This is the first interpolated point. + minval = maxval = ploop[pointmtrindex]; + } else { + if (ploop[pointmtrindex] < minval) { + minval = ploop[pointmtrindex]; + } + if (ploop[pointmtrindex] > maxval) { + maxval = ploop[pointmtrindex]; + } + } + count++; + } else { + if (!b->quiet) { + printf("Warnning: Failed to locate point %d in source mesh.\n", + pointmark(ploop)); + } + } + ploop = pointtraverse(); + } + + if (b->verbose) { + printf(" Interoplated %d points.\n", count); + if (nonregularcount > 0l) { + printf(" Performed %ld brute-force searches.\n", nonregularcount); + } + printf(" Size rangle [%.17g, %.17g].\n", minval, maxval); + } + + bgm->samples = baksmaples; + nonregularcount = bak_nonregularcount; +} + +//============================================================================// +// // +// insertconstrainedpoints() Insert a list of points into the mesh. // +// // +// Assumption: The bounding box of the insert point set should be no larger // +// than the bounding box of the mesh. (Required by point sorting). // +// // +//============================================================================// + +void tetgenmesh::insertconstrainedpoints(point *insertarray, int arylen, + int rejflag) +{ + triface searchtet, spintet; + face splitsh; + face splitseg; + insertvertexflags ivf; + //flipconstraints fc; + int randflag = 0; + int t1ver; + int i; + + if (b->verbose) { + printf(" Inserting %d constrained points\n", arylen); + } + + if (b->no_sort) { // -b/1 option. + if (b->verbose) { + printf(" Using the input order.\n"); + } + } else { + if (b->verbose) { + printf(" Permuting vertices.\n"); + } + point swappoint; + int randindex; + srand(arylen); + for (i = 0; i < arylen; i++) { + randindex = rand() % (i + 1); + swappoint = insertarray[i]; + insertarray[i] = insertarray[randindex]; + insertarray[randindex] = swappoint; + } + if (b->brio_hilbert) { // -b1 option + if (b->verbose) { + printf(" Sorting vertices.\n"); + } + hilbert_init(in->mesh_dim); + int ngroup = 0; + brio_multiscale_sort(insertarray, arylen, b->brio_threshold, + b->brio_ratio, &ngroup); + } else { // -b0 option. + randflag = 1; + } // if (!b->brio_hilbert) + } // if (!b->no_sort) + + long bak_nonregularcount = nonregularcount; + nonregularcount = 0l; + long baksmaples = samples; + samples = 3l; // Use at least 3 samples. Updated in randomsample(). + + long bak_seg_count = st_segref_count; + long bak_fac_count = st_facref_count; + long bak_vol_count = st_volref_count; + + // Initialize the insertion parameters. + // Use Bowyer-Watson algorithm. + ivf.bowywat = 1; + ivf.lawson = 2; // do flip to recover Delaunay + ivf.validflag = 1; // Validate the B-W cavity. + ivf.rejflag = rejflag; + ivf.chkencflag = 0; + ivf.sloc = (int) INSTAR; + ivf.sbowywat = 3; + ivf.splitbdflag = 1; + ivf.respectbdflag = 1; + ivf.assignmeshsize = b->metric; + + encseglist = new arraypool(sizeof(face), 8); + encshlist = new arraypool(sizeof(badface), 8); + searchtet.tet = NULL; + + // Insert the points. + for (i = 0; i < arylen; i++) { + // Find the location of the inserted point. + // Do not use 'recenttet', since the mesh may be non-convex. + ivf.iloc = scout_point(insertarray[i], &searchtet, randflag); + + // Decide the right type for this point. + setpointtype(insertarray[i], FREEVOLVERTEX); // Default. + splitsh.sh = NULL; + splitseg.sh = NULL; + if (ivf.iloc == (int) ONEDGE) { + if (issubseg(searchtet)) { + tsspivot1(searchtet, splitseg); + setpointtype(insertarray[i], FREESEGVERTEX); + //ivf.rejflag = 0; + } else { + // Check if it is a subface edge. + spintet = searchtet; + while (1) { + if (issubface(spintet)) { + tspivot(spintet, splitsh); + setpointtype(insertarray[i], FREEFACETVERTEX); + //ivf.rejflag |= 1; + break; + } + fnextself(spintet); + if (spintet.tet == searchtet.tet) break; + } + } + } else if (ivf.iloc == (int) ONFACE) { + if (issubface(searchtet)) { + tspivot(searchtet, splitsh); + setpointtype(insertarray[i], FREEFACETVERTEX); + //ivf.rejflag |= 1; + } + } + + // Now insert the point. + if (insertpoint(insertarray[i], &searchtet, &splitsh, &splitseg, &ivf)) { + if (flipstack != NULL) { + flipconstraints fc; + //fc.chkencflag = chkencflag; + fc.enqflag = 2; + lawsonflip3d(&fc); + //unflipqueue->restart(); + } + + if (later_unflip_queue->objects > b->unflip_queue_limit) { + recoverdelaunay(); + } + + // Update the Steiner counters. + if (pointtype(insertarray[i]) == FREESEGVERTEX) { + st_segref_count++; + } else if (pointtype(insertarray[i]) == FREEFACETVERTEX) { + st_facref_count++; + } else { + st_volref_count++; + } + } else { + // Point is not inserted. + if (pointtype(insertarray[i]) != UNUSEDVERTEX) { + setpointtype(insertarray[i], UNUSEDVERTEX); + } + unuverts++; + + encseglist->restart(); + encshlist->restart(); + } + } // i + + if (later_unflip_queue->objects > 0) { + recoverdelaunay(); + } + + delete encseglist; + delete encshlist; + encseglist = NULL; + encshlist = NULL; + + if (b->verbose) { + printf(" Inserted %ld (%ld, %ld, %ld) vertices.\n", + st_segref_count + st_facref_count + st_volref_count - + (bak_seg_count + bak_fac_count + bak_vol_count), + st_segref_count - bak_seg_count, st_facref_count - bak_fac_count, + st_volref_count - bak_vol_count); + if (nonregularcount > 0l) { + printf(" Performed %ld brute-force searches.\n", nonregularcount); + } + } + + nonregularcount = bak_nonregularcount; + samples = baksmaples; +} + +void tetgenmesh::insertconstrainedpoints(tetgenio *addio) +{ + point *insertarray, newpt; + REAL x, y, z, w; + int index, attribindex, mtrindex; + int arylen, i, j; + + if (!b->quiet) { + printf("Inserting constrained points ...\n"); + } + + insertarray = new point[addio->numberofpoints]; + arylen = 0; + index = 0; + attribindex = 0; + mtrindex = 0; + + for (i = 0; i < addio->numberofpoints; i++) { + x = addio->pointlist[index++]; + y = addio->pointlist[index++]; + z = addio->pointlist[index++]; + // Test if this point lies inside the bounding box. + if ((x < xmin) || (x > xmax) || (y < ymin) || (y > ymax) || + (z < zmin) || (z > zmax)) { + if (b->verbose) { + printf("Warning: Point #%d lies outside the bounding box. Ignored\n", + i + in->firstnumber); + } + continue; + } + makepoint(&newpt, UNUSEDVERTEX); + newpt[0] = x; + newpt[1] = y; + newpt[2] = z; + // Read the point attributes. (Including point weights.) + for (j = 0; j < addio->numberofpointattributes; j++) { + newpt[3 + j] = addio->pointattributelist[attribindex++]; + } + // Read the point metric tensor. + for (j = 0; j < addio->numberofpointmtrs; j++) { + newpt[pointmtrindex + j] = addio->pointmtrlist[mtrindex++]; + } + if (b->weighted) { // -w option + if (addio->numberofpointattributes > 0) { + // The first point attribute is its weight. + w = newpt[3]; + } else { + // No given weight available. Default choose the maximum + // absolute value among its coordinates. + w = fabs(x); + if (w < fabs(y)) w = fabs(y); + if (w < fabs(z)) w = fabs(z); + } + if (b->weighted_param == 0) { + newpt[3] = x * x + y * y + z * z - w; // Weighted DT. + } else { // -w1 option + newpt[3] = w; // Regular tetrahedralization. + } + } + insertarray[arylen] = newpt; + arylen++; + } // i + + // Insert the points. + int rejflag = 0; // Do not check encroachment. + if (b->metric) { // -m option. + rejflag |= 4; // Reject it if it lies in some protecting balls. + } + + insertconstrainedpoints(insertarray, arylen, rejflag); + + delete [] insertarray; +} + +//============================================================================// +// // +// meshcoarsening() Deleting (selected) vertices. // +// // +//============================================================================// + +void tetgenmesh::collectremovepoints(arraypool *remptlist) +{ + point ptloop, *parypt; + verttype vt; + + // If a mesh sizing function is given. Collect vertices whose mesh size + // is greater than its smallest edge length. + if (b->metric) { // -m option + REAL len, smlen; + int i; + points->traversalinit(); + ptloop = pointtraverse(); + while (ptloop != NULL) { + // Do not remove a boundary vertex + vt = pointtype(ptloop); + if ((vt == RIDGEVERTEX) || /*(vt == ACUTEVERTEX) ||*/ (vt == FACETVERTEX) || + (vt == FREEFACETVERTEX) || (vt == FREESEGVERTEX) || (vt == UNUSEDVERTEX)) { + ptloop = pointtraverse(); + continue; + } + if (ptloop[pointmtrindex] > 0) { + // Get the smallest edge length at this vertex. + getvertexstar(1, ptloop, cavetetlist, cavetetvertlist, NULL); + parypt = (point *) fastlookup(cavetetvertlist, 0); + smlen = distance(ptloop, *parypt); + for (i = 1; i < cavetetvertlist->objects; i++) { + parypt = (point *) fastlookup(cavetetvertlist, i); + len = distance(ptloop, *parypt); + if (len < smlen) { + smlen = len; + } + } + cavetetvertlist->restart(); + cavetetlist->restart(); + if (smlen < ptloop[pointmtrindex]) { + pinfect(ptloop); + remptlist->newindex((void **) &parypt); + *parypt = ptloop; + } + } + ptloop = pointtraverse(); + } + if (b->verbose > 1) { + printf(" Coarsen %ld oversized points.\n", remptlist->objects); + } + } + + // If 'in->pointmarkerlist' exists, Collect vertices with markers '-1'. + if (in->pointmarkerlist != NULL) { + long bak_count = remptlist->objects; + points->traversalinit(); + ptloop = pointtraverse(); + int index = 0; + while (ptloop != NULL) { + if (index < in->numberofpoints) { + if (in->pointmarkerlist[index] == -1) { + pinfect(ptloop); + remptlist->newindex((void **) &parypt); + *parypt = ptloop; + } + } else { + // Remaining are not input points. Stop here. + break; + } + index++; + ptloop = pointtraverse(); + } + if (b->verbose > 1) { + printf(" Coarsen %ld marked points.\n", remptlist->objects - bak_count); + } + } // if (in->pointmarkerlist != NULL) + + if (b->coarsen_param > 0) { // -R1/# + // Remove a coarsen_percent number of interior points. + if (b->verbose > 1) { + printf(" Coarsen %g percent of interior points.\n", + b->coarsen_percent * 100.0); + } + arraypool *intptlist = new arraypool(sizeof(point *), 10); + // Count the total number of interior points. + points->traversalinit(); + ptloop = pointtraverse(); + while (ptloop != NULL) { + vt = pointtype(ptloop); + if ((vt == VOLVERTEX) || (vt == FREEVOLVERTEX) || + (vt == FREEFACETVERTEX) || (vt == FREESEGVERTEX)) { + intptlist->newindex((void **) &parypt); + *parypt = ptloop; + } + ptloop = pointtraverse(); + } + if (intptlist->objects > 0l) { + // Sort the list of points randomly. + point *parypt_i, swappt; + int randindex, i; + srand(intptlist->objects); + for (i = 0; i < intptlist->objects; i++) { + randindex = rand() % (i + 1); // randomnation(i + 1); + parypt_i = (point *) fastlookup(intptlist, i); + parypt = (point *) fastlookup(intptlist, randindex); + // Swap this two points. + swappt = *parypt_i; + *parypt_i = *parypt; + *parypt = swappt; + } + int remcount = (int) ((REAL) intptlist->objects * b->coarsen_percent); + // Return the first remcount points. + for (i = 0; i < remcount; i++) { + parypt_i = (point *) fastlookup(intptlist, i); + if (!pinfected(*parypt_i)) { + pinfected(*parypt_i); + remptlist->newindex((void **) &parypt); + *parypt = *parypt_i; + } + } + } + delete intptlist; + } + + // Unmark all collected vertices. + for (int i = 0; i < remptlist->objects; i++) { + parypt = (point *) fastlookup(remptlist, i); + puninfect(*parypt); + } +} + +void tetgenmesh::meshcoarsening() +{ + arraypool *remptlist; + + if (!b->quiet) { + printf("Mesh coarsening ...\n"); + } + + // Collect the set of points to be removed + remptlist = new arraypool(sizeof(point *), 10); + collectremovepoints(remptlist); + + if (remptlist->objects == 0l) { + delete remptlist; + return; + } + + if (b->verbose) { + if (remptlist->objects > 0l) { + printf(" Removing %ld points...\n", remptlist->objects); + } + } + + point *parypt, *plastpt; + long ms = remptlist->objects; + int nit = 0; + int bak_fliplinklevel = b->fliplinklevel; + b->fliplinklevel = -1; + autofliplinklevel = 1; // Init value. + int i; + + while (1) { + + if (b->verbose > 1) { + printf(" Removing points [%s level = %2d] #: %ld.\n", + (b->fliplinklevel > 0) ? "fixed" : "auto", + (b->fliplinklevel > 0) ? b->fliplinklevel : autofliplinklevel, + remptlist->objects); + } + + // Remove the list of points. + for (i = 0; i < remptlist->objects; i++) { + parypt = (point *) fastlookup(remptlist, i); + if (removevertexbyflips(*parypt)) { + // Move the last entry to the current place. + plastpt = (point *) fastlookup(remptlist, remptlist->objects - 1); + *parypt = *plastpt; + remptlist->objects--; + i--; + } + } + + if (remptlist->objects > 0l) { + if (b->fliplinklevel >= 0) { + break; // We have tried all levels. + } + if (remptlist->objects == ms) { + nit++; + if (nit >= 3) { + // Do the last round with unbounded flip link level. + b->fliplinklevel = 100000; + } + } else { + ms = remptlist->objects; + if (nit > 0) { + nit--; + } + } + autofliplinklevel+=b->fliplinklevelinc; + } else { + // All points are removed. + break; + } + } // while (1) + + if (remptlist->objects > 0l) { + if (b->verbose) { + printf(" %ld points are not removed !\n", remptlist->objects); + } + } + + b->fliplinklevel = bak_fliplinklevel; + delete remptlist; +} + +// // +// // +//== reconstruct_cxx =========================================================// + +//== refine_cxx ==============================================================// +// // +// // + +//============================================================================// +// // +// makesegmentendpointsmap() Create a map from a segment to its endpoints. // +// // +// The map is saved in the array 'segmentendpointslist'. The length of this // +// array is twice the number of segments. Each segment is assigned a unique // +// index (starting from 0). // +// // +//============================================================================// + +void tetgenmesh::makesegmentendpointsmap() +{ + arraypool *segptlist; + face segloop, prevseg, nextseg; + point eorg, edest, *parypt; + int segindex = 0, idx = 0; + int i; + + if (b->verbose > 0) { + printf(" Creating the segment-endpoints map.\n"); + } + segptlist = new arraypool(2 * sizeof(point), 10); + + // for creating ridge_vertex-to-segment map; + // The index might start from 0 or 1. + idx_segment_ridge_vertex_list = new int[points->items + 2]; + for (i = 0; i < points->items + 2; i++) { + idx_segment_ridge_vertex_list[i] = 0; + } + + // A segment s may have been split into many subsegments. Operate the one + // which contains the origin of s. Then mark the rest of subsegments. + subsegs->traversalinit(); + segloop.sh = shellfacetraverse(subsegs); + segloop.shver = 0; + while (segloop.sh != NULL) { + senext2(segloop, prevseg); + spivotself(prevseg); + if (prevseg.sh == NULL) { + eorg = sorg(segloop); + edest = sdest(segloop); + setfacetindex(segloop, segindex); + senext(segloop, nextseg); + spivotself(nextseg); + while (nextseg.sh != NULL) { + setfacetindex(nextseg, segindex); + nextseg.shver = 0; + if (sorg(nextseg) != edest) sesymself(nextseg); + edest = sdest(nextseg); + // Go the next connected subsegment at edest. + senextself(nextseg); + spivotself(nextseg); + } + segptlist->newindex((void **) &parypt); + parypt[0] = eorg; + parypt[1] = edest; + segindex++; + // for creating adj_ridge_vertex_list; + idx_segment_ridge_vertex_list[pointmark(eorg)]++; + idx_segment_ridge_vertex_list[pointmark(edest)]++; + } + segloop.sh = shellfacetraverse(subsegs); + } + + if (b->verbose) { + printf(" Found %ld segments.\n", segptlist->objects); + } + + segmentendpointslist_length = segptlist->objects; + segmentendpointslist = new point[segptlist->objects * 2]; + + totalworkmemory += (segptlist->objects * 2) * sizeof(point *); + + for (i = 0; i < segptlist->objects; i++) { + parypt = (point *) fastlookup(segptlist, i); + segmentendpointslist[idx++] = parypt[0]; + segmentendpointslist[idx++] = parypt[1]; + } + + // Create the adj_ridge_vertex_list. + int j = idx_segment_ridge_vertex_list[0], k; + idx_segment_ridge_vertex_list[0] = 0; + for (i = 0; i < points->items + 1; i++) { + k = idx_segment_ridge_vertex_list[i+1]; + idx_segment_ridge_vertex_list[i+1] = idx_segment_ridge_vertex_list[i] + j; + j = k; + } + + //assert(i == points->items+1); + int total_count = idx_segment_ridge_vertex_list[i] + 1; + segment_ridge_vertex_list = new point[total_count]; + for (i = 0; i < segptlist->objects; i++) { + eorg = segmentendpointslist[i*2]; + edest = segmentendpointslist[i*2+1]; + j = pointmark(eorg); + k = pointmark(edest); + segment_ridge_vertex_list[idx_segment_ridge_vertex_list[j]] = edest; //eorg; + segment_ridge_vertex_list[idx_segment_ridge_vertex_list[k]] = eorg; //edest; + idx_segment_ridge_vertex_list[j]++; + idx_segment_ridge_vertex_list[k]++; + } + + // Counters in idx_adj_ridge_vertex_list[] are shifted by 1. + for (i = points->items; i >= 0; i--) { + idx_segment_ridge_vertex_list[i+1] = idx_segment_ridge_vertex_list[i]; + } + idx_segment_ridge_vertex_list[0] = 0; + + + delete segptlist; +} + +//============================================================================// +// // +// set_ridge_vertex_protecting_ball() Calculate the protecting ball for a // +// given ridge vertex. // +// // +//============================================================================// + +REAL tetgenmesh::set_ridge_vertex_protecting_ball(point ridge_pt) +{ + REAL rv = getpointinsradius(ridge_pt); + if (rv == 0.) { + REAL mindist = 1.e+30, dist; + int idx = pointmark(ridge_pt); + for (int i = idx_segment_ridge_vertex_list[idx]; + i < idx_segment_ridge_vertex_list[idx+1]; i++) { + dist = distance(ridge_pt, segment_ridge_vertex_list[i]); + if (mindist > dist) mindist = dist; + } + rv = mindist * 0.95; // mindist / 3.0; // refer to J. Shewchuk + setpointinsradius(ridge_pt, rv); + } + return rv; +} + +//============================================================================// +// // +// get_min_diahedral_angle() Calculate the minimum (interior) dihedral // +// angle a given segment. // +// // +//============================================================================// + +REAL tetgenmesh::get_min_diahedral_angle(face* seg) +{ + triface adjtet, spintet; + face startsh, neighsh; + point pa, pb, pc1, pc2; + REAL n1[3], n2[3]; + REAL n1len, n2len; + REAL costheta; //, ori; + REAL theta, sum_theta, minang = 2.0 * PI; + int t1ver; + + pa = sorg(*seg); + pb = sdest(*seg); + spivot(*seg, startsh); + if (startsh.sh == NULL) { + // This segment is not connected by any facet. + sstpivot1(*seg, adjtet); + if (adjtet.tet != NULL) { + // This segment is completely inside the volume. + return 360.; // 2*pi. + } + } else { + if (sorg(startsh) != pa) sesymself(startsh); + stpivot(startsh, adjtet); + } + if (adjtet.tet == NULL) { + // This segment is not inserted (recovered) yet. + return 0.; + } + + + sum_theta = 0.; + spintet = adjtet; + while (true) { + if (!ishulltet(spintet)) { + // Increase the interior dihedral angle (sum_theta). + pc1 = apex(spintet); + pc2 = oppo(spintet); + facenormal(pa, pb, pc1, n1, 1, NULL); + facenormal(pa, pb, pc2, n2, 1, NULL); + n1len = sqrt(dot(n1, n1)); + n2len = sqrt(dot(n2, n2)); + costheta = dot(n1, n2) / (n1len * n2len); + // Be careful rounding error! + if (costheta > 1.0) { + costheta = 1.0; + } else if (costheta < -1.0) { + costheta = -1.0; + } + theta = acos(costheta); + sum_theta += theta; + } + // Go to the next adjacent tetrahedron at this segment. + fnextself(spintet); + // Check if we meet a subface. + tspivot(spintet, neighsh); + if ((neighsh.sh != NULL) && (sum_theta > 0.)) { + // Update the smallest dihedral angle. + if (sum_theta < minang) minang = sum_theta; + sum_theta = 0.; // clear it + } + if (spintet.tet == adjtet.tet) break; + } + + double mindihedang = minang / PI * 180.; + return mindihedang; +} + +//============================================================================// +// // +// get_min_angle_at_ridge_vertex() Calculate the minimum face angle at a // +// given ridge vertex. // +// // +//============================================================================// + +REAL tetgenmesh::get_min_angle_at_ridge_vertex(face* seg) +{ + face startsh, spinsh, neighsh; + point pa, pb, pc; + REAL theta, sum_theta, minang = 2.0 * PI; + + pa = sorg(*seg); + spivot(*seg, startsh); + if (startsh.sh == NULL) { + // This segment does not belong to any facet. + return 360.; // 2*pi. + } else { + if (sorg(startsh) != pa) sesymself(startsh); + } + + spinsh = startsh; + while (spinsh.sh != NULL) { + sum_theta = 0.; + neighsh = spinsh; + while (true) { + pb = sdest(neighsh); + pc = sapex(neighsh); + theta = interiorangle(pa, pb, pc, NULL); + sum_theta += theta; + senext2self(neighsh); + if (isshsubseg(neighsh)) break; + spivotself(neighsh); + if (sorg(neighsh) != pa) sesymself(neighsh); + } + if (sum_theta < minang) { + minang = sum_theta; + } + // Go to the next facet at this segment. + spivotself(spinsh); + if (spinsh.sh == startsh.sh) break; + if (spinsh.sh == NULL) break; // A single facet may happen. + if (sorg(spinsh) != pa) sesymself(spinsh); + } + + return minang / PI * 180.; +} + +//============================================================================// +// // +// create_segment_info_list() Calculate the minimum dihedral angle at a // +// a given segment. // +// // +// segment_info_list = new double[segmentendpointslist_length * 4]; // +// - [0] min_dihedral_angle (degree) at this segment, // +// - [1] min_protect_cylinder_radius at this segment (for bookkeeping only), // +// - [2] min_seg_seg_angle (degree) at its endpoint [0], // +// - [3] min_seg_seg_angle (degree) at its endpoint [1]. // +// // +// This function must be called after makesegmentendpointsmap(). The number // +// of unique segments (segmentendpointslist_length) is calculated. // +// // +//============================================================================// + +void tetgenmesh::create_segment_info_list() +{ + face min_dihedral_ang_seg; + point min_face_ang_vertex; + REAL min_dihedral_ang = 360.; + REAL min_face_ang = 360.; + + if (b->verbose > 0) { + printf(" Creating the segment_info_list.\n"); + } + if (segment_info_list != NULL) { + delete [] segment_info_list; + } + + if (subsegs->items == 0) { + return; // There is no segments. + } + + int count = (segmentendpointslist_length + 1) * 4; + segment_info_list = new double[count]; + for (int i = 0; i < count; i++) { + segment_info_list[i] = 0.; + } + + // Loop through the list of segments. + face segloop; + subsegs->traversalinit(); + segloop.sh = shellfacetraverse(subsegs); + while (segloop.sh != NULL) { + int segidx = getfacetindex(segloop); + // Check if this segment has been already calulcated. + double *values = &(segment_info_list[segidx * 4]); + + // The min_diahedral_angle at this segment is in (0, 2pi]. + if (values[0] == 0.) { + // Get the smallest dihedral angle at this segment. + values[0] = get_min_diahedral_angle(&segloop); + if (values[0] < min_dihedral_ang) { + min_dihedral_ang = values[0]; + min_dihedral_ang_seg = segloop; + } + } + + point *endpts = &(segmentendpointslist[segidx * 2]); + + for (int k = 0; k < 2; k++) { + segloop.shver = 0; + if (values[2+k] == 0.) { + if (sorg(segloop) != endpts[k]) { + sesymself(segloop); + } + if (sorg(segloop) == endpts[k]) { + // Get the min face angle at vertex endpts[0]. + values[2+k] = get_min_angle_at_ridge_vertex(&segloop); + if (values[2+k] < min_face_ang) { + min_face_ang = values[2+k]; + min_face_ang_vertex = endpts[k]; + } + } + } + } + + segloop.sh = shellfacetraverse(subsegs); + } + + if (b->verbose) { + printf(" min_dihedral angle = %g degree, at segment [%d,%d]\n", + min_dihedral_ang, pointmark(sorg(min_dihedral_ang_seg)), + pointmark(sdest(min_dihedral_ang_seg))); + printf(" min face angle = %g degree, at vertex %d\n", + min_face_ang, pointmark(min_face_ang_vertex)); + } + +} + +//============================================================================// +// // +// makefacetverticesmap() Create a map from facet to its vertices. // +// // +// All facets will be indexed (starting from 0). The map is saved in two // +// global arrays: 'idx2facetlist' and 'facetverticeslist'. // +// // +//============================================================================// + +void tetgenmesh::makefacetverticesmap() +{ + arraypool *facetvertexlist, *vertlist, **paryvertlist; + face subloop, neighsh, *parysh, *parysh1; + point pa, *ppt, *parypt; + verttype vt; + int facetindex, totalvertices; + unsigned long max_facet_size = 0l; + int max_facet_idx = 0; + int i, j, k; + + if (b->verbose) { + printf(" Creating the facet vertices map.\n"); + } + + facetvertexlist = new arraypool(sizeof(arraypool *), 10); + facetindex = totalvertices = 0; + + // The index might start from 0 or 1. + idx_ridge_vertex_facet_list = new int[points->items + 2]; + for (i = 0; i < points->items + 2; i++) { + idx_ridge_vertex_facet_list[i] = 0; + } + + subfaces->traversalinit(); + subloop.sh = shellfacetraverse(subfaces); + while (subloop.sh != NULL) { + if (!sinfected(subloop)) { + // A new facet. Create its vertices list. + vertlist = new arraypool(sizeof(point *), 8); + ppt = (point *) &(subloop.sh[3]); + for (k = 0; k < 3; k++) { + vt = pointtype(ppt[k]); + //if ((vt != FREESEGVERTEX) && (vt != FREEFACETVERTEX)) { + if (vt == RIDGEVERTEX) { + pinfect(ppt[k]); + vertlist->newindex((void **) &parypt); + *parypt = ppt[k]; + // for creating ridge_vertex-to-facet map. + idx_ridge_vertex_facet_list[pointmark(ppt[k])]++; + } + } + sinfect(subloop); + caveshlist->newindex((void **) &parysh); + *parysh = subloop; + for (i = 0; i < caveshlist->objects; i++) { + parysh = (face *) fastlookup(caveshlist, i); + setfacetindex(*parysh, facetindex); + for (j = 0; j < 3; j++) { + if (!isshsubseg(*parysh)) { + spivot(*parysh, neighsh); + if (!sinfected(neighsh)) { + pa = sapex(neighsh); + if (!pinfected(pa)) { + vt = pointtype(pa); + //if ((vt != FREESEGVERTEX) && (vt != FREEFACETVERTEX)) { + if (vt == RIDGEVERTEX) { + pinfect(pa); + vertlist->newindex((void **) &parypt); + *parypt = pa; + // for creating ridge_vertex-to-facet map. + idx_ridge_vertex_facet_list[pointmark(pa)]++; + } + } + sinfect(neighsh); + caveshlist->newindex((void **) &parysh1); + *parysh1 = neighsh; + } + } + senextself(*parysh); + } + } // i + totalvertices += (int) vertlist->objects; + if (max_facet_size < vertlist->objects) { + max_facet_size = vertlist->objects; + max_facet_idx = facetindex; + } + // Uninfect facet vertices. + for (k = 0; k < vertlist->objects; k++) { + parypt = (point *) fastlookup(vertlist, k); + puninfect(*parypt); + } + caveshlist->restart(); + // Save this vertex list. + facetvertexlist->newindex((void **) &paryvertlist); + *paryvertlist = vertlist; + facetindex++; + } + subloop.sh = shellfacetraverse(subfaces); + } + + // All subfaces are infected. Uninfect them. + subfaces->traversalinit(); + subloop.sh = shellfacetraverse(subfaces); + while (subloop.sh != NULL) { + suninfect(subloop); + subloop.sh = shellfacetraverse(subfaces); + } + + if (b->verbose) { + printf(" Found %ld facets. Max facet idx(%d), size(%ld)\n", + facetvertexlist->objects, max_facet_idx, max_facet_size); + } + + number_of_facets = facetindex; + idx2facetlist = new int[facetindex + 1]; + facetverticeslist = new point[totalvertices]; + + // create ridge_vertex-to-facet map. + j = idx_ridge_vertex_facet_list[0]; //k; + idx_ridge_vertex_facet_list[0] = 0; + for (i = 0; i < points->items + 1; i++) { + k = idx_ridge_vertex_facet_list[i+1]; + idx_ridge_vertex_facet_list[i+1] = idx_ridge_vertex_facet_list[i] + j; + j = k; + } + + int total_count = idx_ridge_vertex_facet_list[i] + 1; + ridge_vertex_facet_list = new int[total_count]; + + // Bookkeeping + totalworkmemory += ((facetindex + 1) * sizeof(int) + + totalvertices * sizeof(point *)); + + idx2facetlist[0] = 0; + for (i = 0, k = 0; i < facetindex; i++) { + paryvertlist = (arraypool **) fastlookup(facetvertexlist, i); + vertlist = *paryvertlist; + idx2facetlist[i + 1] = (idx2facetlist[i] + (int) vertlist->objects); + for (j = 0; j < vertlist->objects; j++) { + parypt = (point *) fastlookup(vertlist, j); + facetverticeslist[k] = *parypt; + k++; + // create ridge_vertex-to-facet map. + int ridge_idx = pointmark(*parypt); // index of this ridge vertex + // 'i' is the current facet index. + ridge_vertex_facet_list[idx_ridge_vertex_facet_list[ridge_idx]] = i; + // for the next facet index of this ridge vertex. + idx_ridge_vertex_facet_list[ridge_idx]++; + } + } + + // Counters in idx_ridge_vertex_facet_list[] are shifted by 1. + for (i = points->items; i >= 0; i--) { + idx_ridge_vertex_facet_list[i+1] = idx_ridge_vertex_facet_list[i]; + } + idx_ridge_vertex_facet_list[0] = 0; + + + // Free the lists. + for (i = 0; i < facetvertexlist->objects; i++) { + paryvertlist = (arraypool **) fastlookup(facetvertexlist, i); + vertlist = *paryvertlist; + delete vertlist; + } + delete facetvertexlist; +} + +//============================================================================// +// // +// create_segment_facet_map() Create the map from segments to adjacent // +// facets. // +// // +//============================================================================// + +void tetgenmesh::create_segment_facet_map() +{ + if (b->verbose > 0) { + printf(" Creating the segment-to-facets map.\n"); + } + if (idx_segment_facet_list != NULL) { + delete [] idx_segment_facet_list; + delete [] segment_facet_list; + } + + face startsh, spinsh; + face segloop; + int segindex, facetidx; + int totalcount = 0; + int i; + + // both segment-index and facet-index start from zero. + idx_segment_facet_list = new int[segmentendpointslist_length + 1]; + for (i = 0; i < segmentendpointslist_length + 1; i++) { + idx_segment_facet_list[i] = 0; + } + + subsegs->traversalinit(); + segloop.sh = shellfacetraverse(subsegs); + while (segloop.sh != NULL) { + segindex = getfacetindex(segloop); + if (idx_segment_facet_list[segindex] == 0) { + // Count the number of facets at this segment. + spivot(segloop, startsh); + spinsh = startsh; + while (spinsh.sh != NULL) { + idx_segment_facet_list[segindex]++; + spivotself(spinsh); + if (spinsh.sh == startsh.sh) break; + } + totalcount += idx_segment_facet_list[segindex]; + } + segloop.sh = shellfacetraverse(subsegs); + } + + // A working list. + bool *bflags = new bool[segmentendpointslist_length + 1]; + + // Have got the totalcount, fill the starting indices into the list. + int j = idx_segment_facet_list[0], k; + idx_segment_facet_list[0] = 0; + //for (i = 0; i < segmentendpointslist_length + 1; i++) { + for (i = 0; i < segmentendpointslist_length; i++) { + k = idx_segment_facet_list[i+1]; + idx_segment_facet_list[i+1] = idx_segment_facet_list[i] + j; + j = k; + bflags[i] = false; + } + + segment_facet_list = new int[totalcount + 1]; + subsegs->traversalinit(); + segloop.sh = shellfacetraverse(subsegs); + while (segloop.sh != NULL) { + segindex = getfacetindex(segloop); + if (!bflags[segindex]) { + spivot(segloop, startsh); + spinsh = startsh; + while (spinsh.sh != NULL) { + facetidx = getfacetindex(spinsh); + segment_facet_list[idx_segment_facet_list[segindex]] = facetidx; + idx_segment_facet_list[segindex]++; // for the next one + spivotself(spinsh); + if (spinsh.sh == startsh.sh) break; + } + bflags[segindex] = true; + } + segloop.sh = shellfacetraverse(subsegs); + } + + // Counters in idx_segment_facet_list[] are shifted by 1. + for (i = segmentendpointslist_length - 1; i >= 0; i--) { + idx_segment_facet_list[i+1] = idx_segment_facet_list[i]; + } + idx_segment_facet_list[0] = 0; + + + delete [] bflags; +} + +//============================================================================// +// // +// ridge_vertices_adjacent() Check if two ridge vertices are connected by // +// an input segment. // +// // +//============================================================================// + +int tetgenmesh::ridge_vertices_adjacent(point e1, point e2) +{ + int idx = pointmark(e1); + int acount = idx_segment_ridge_vertex_list[idx+1]-idx_segment_ridge_vertex_list[idx]; + for (int i = 0; i < acount; i++) { + if (segment_ridge_vertex_list[idx_segment_ridge_vertex_list[idx]+i] == e2) { + return 1; // adjacent. + } + } + return 0; // not adjacent. +} + +//============================================================================// +// // +// facet_ridge_vertex_adjacent() Check if a facet and a ridge vertex is // +// adjacent by an input segment. // +// // +//============================================================================// + +int tetgenmesh::facet_ridge_vertex_adjacent(face *chkfac, point chkpt) +{ + int ridge_idx = pointmark(chkpt); + int facet_idx = getfacetindex(*chkfac); + for (int i = idx_ridge_vertex_facet_list[ridge_idx]; + i < idx_ridge_vertex_facet_list[ridge_idx+1]; i++) { + if (ridge_vertex_facet_list[i] == facet_idx) { + return 1; // They are adjacent. + } + } + return 0; +} + +//============================================================================// +// // +// segsegadjacent() Check whether two segments, or a segment and a facet, // +// or two facets are adjacent to each other. // +// // +//============================================================================// + +int tetgenmesh::segsegadjacent(face *seg1, face *seg2) +{ + int segidx1 = getfacetindex(*seg1); + int segidx2 = getfacetindex(*seg2); + + if (segidx1 == segidx2) { + return 2; // Adjacent. They are the same segment. + } + + point pa1 = segmentendpointslist[segidx1 * 2]; + point pb1 = segmentendpointslist[segidx1 * 2 + 1]; + point pa2 = segmentendpointslist[segidx2 * 2]; + point pb2 = segmentendpointslist[segidx2 * 2 + 1]; + + if ((pa1 == pa2) || (pa1 == pb2) || (pb1 == pa2) || (pb1 == pb2)) { + return 1; // Adjacent. + } + return 0; // not adjacent +} + +//============================================================================// +// // +// segfacetadjacent() Check whether a segment and a facet are adjacent or // +// not. // +// // +//============================================================================// + +int tetgenmesh::segfacetadjacent(face *subseg, face *subsh) +{ + int seg_idx = getfacetindex(*subseg); + int facet_idx = getfacetindex(*subsh); + for (int i = idx_segment_facet_list[seg_idx]; + i < idx_segment_facet_list[seg_idx+1]; i++) { + if (segment_facet_list[i] == facet_idx) { + return 1; // They are adjacent. + } + } + return 0; +} + +//============================================================================// +// // +// facetfacetadjacent() Check whether two facets are adjacent or not. // +// // +//============================================================================// + +int tetgenmesh::facetfacetadjacent(face *subsh1, face *subsh2) +{ + int count = 0, i; + + int fidx1 = getfacetindex(*subsh1); + int fidx2 = getfacetindex(*subsh2); + + if (fidx1 == fidx2) { + return 2; // Adjacent. They are the same facet. + } + + for (i = idx2facetlist[fidx1]; i < idx2facetlist[fidx1+1]; i++) { + pinfect(facetverticeslist[i]); + } + + for (i = idx2facetlist[fidx2]; i < idx2facetlist[fidx2+1]; i++) { + if (pinfected(facetverticeslist[i])) count++; + } + + // Uninfect the vertices. + for (i = idx2facetlist[fidx1]; i < idx2facetlist[fidx1+1]; i++) { + puninfect(facetverticeslist[i]); + } + + if (count > 0) { + return 1; + } else { + return 0; + } +} + +//============================================================================// +// // +// is_sharp_segment() Check whether a given segment is sharp or not. // +// // +//============================================================================// + +bool tetgenmesh::is_sharp_segment(face *seg) +{ + int segidx = getfacetindex(*seg); + double mindihedang = segment_info_list[segidx*4]; + return mindihedang < 72.; // (in theory) < 72 degree is sufficient. +} + +//============================================================================// +// // +// does_seg_contain_acute_vertex() Check whether one of the endpoints of a // +// given segment is a sharp corner. // +// // +//============================================================================// + +bool tetgenmesh::does_seg_contain_acute_vertex(face* seg) +{ + int segidx = getfacetindex(*seg); + point *ppt = &(segmentendpointslist[segidx * 2]); + REAL ang = 180.; + // Get the smallest angle at its endpoints. + for (int i = 0; i < 2; i++) { + if ((ppt[i] == sorg(*seg)) || (ppt[i] == sdest(*seg))) { + if (segment_info_list[segidx * 4 + 2 + i] < ang) { + ang = segment_info_list[segidx * 4 + 2 + i]; + } + } + } + return ang < 60.; +} + +//============================================================================// +// // +// create_a_shorter_edge() Can we create an edge (which is shorter than // +// minedgelength) between the two given vertices? // +// // +//============================================================================// + +bool tetgenmesh::create_a_shorter_edge(point steinerpt, point nearpt) +{ + bool createflag = false; // default, do not create a shorter edge. + + enum verttype nearpt_type = pointtype(nearpt); + enum verttype steiner_type = pointtype(steinerpt); + + if (nearpt_type == RIDGEVERTEX) { + if (steiner_type == FREESEGVERTEX) { + // Create a shorter edge if the Steiner point does not on an adjacent + // segment of this ridge vertex. + face parentseg; + sdecode(point2sh(steinerpt), parentseg); + int segidx = getfacetindex(parentseg); + point pa = segmentendpointslist[segidx * 2]; + point pb = segmentendpointslist[segidx * 2 + 1]; + if ((pa != nearpt) && (pb != nearpt)) { + createflag = true; // create a shorter edge. + } + } else if (steiner_type == FREEFACETVERTEX) { + // Create a shorter edge if the Steiner point does not on an adjacent + // facet of this ridge vertex. + face parentsh; + sdecode(point2sh(steinerpt), parentsh); + if (!facet_ridge_vertex_adjacent(&parentsh, nearpt)) { + createflag = true; // create a shorter edge. + } + } + } else if (nearpt_type == FREESEGVERTEX) { + if (steiner_type == FREESEGVERTEX) { + // Check if they are on the same segment. + face seg1, seg2; + sdecode(point2sh(steinerpt), seg1); + sdecode(point2sh(nearpt), seg2); + int sidx1 = getfacetindex(seg1); + int sidx2 = getfacetindex(seg2); + if (sidx1 != sidx2) { + createflag = true; // create a shorter edge. + } + } else if (steiner_type == FREEFACETVERTEX) { + face parentseg, paresntsh; + sdecode(point2sh(steinerpt), paresntsh); + sdecode(point2sh(nearpt), parentseg); + if (!segfacetadjacent(&parentseg, &paresntsh)) { + createflag = true; // create a shorter edge. + } + } + } else if (nearpt_type == FREEFACETVERTEX) { + if (steiner_type == FREESEGVERTEX) { + //assert(0); // to debug... + face parentseg, paresntsh; + sdecode(point2sh(nearpt), paresntsh); + sdecode(point2sh(steinerpt), parentseg); + if (!segfacetadjacent(&parentseg, &paresntsh)) { + createflag = true; // create a shorter edge. + } + } else if (steiner_type == FREEFACETVERTEX) { + // Create a short edge if they are on two different facets. + face paresntsh1, paresntsh2; + sdecode(point2sh(nearpt), paresntsh1); + sdecode(point2sh(steinerpt), paresntsh2); + int sidx1 = getfacetindex(paresntsh1); + int sidx2 = getfacetindex(paresntsh2); + if (sidx1 != sidx2) { + createflag = true; // create a shorter edge. + } + } + } + + return createflag; +} + +//============================================================================// +// // +// enqueuesubface() Queue a subface or a subsegment for encroachment check.// +// // +//============================================================================// + +void tetgenmesh::enqueuesubface(memorypool *pool, face *chkface) +{ + if (!smarktest2ed(*chkface)) { + smarktest2(*chkface); // Only queue it once. + face *queface = (face *) pool->alloc(); + *queface = *chkface; + } +} + +//============================================================================// +// // +// enqueuetetrahedron() Queue a tetrahedron for quality check. // +// // +//============================================================================// + +void tetgenmesh::enqueuetetrahedron(triface *chktet) +{ + if (!marktest2ed(*chktet)) { + marktest2(*chktet); // Only queue it once. + triface *quetet = (triface *) badtetrahedrons->alloc(); + *quetet = *chktet; + } +} + +//============================================================================// +// // +// check_encroachment() Check whether a given point encroaches upon a line // +// segment or not. // +// // +// 'checkpt' should not be dummypoint. // +// // +//============================================================================// + +bool tetgenmesh::check_encroachment(point pa, point pb, point checkpt) +{ + // dot = (pa->checkpt) * (pb->checkpt) + REAL d = (pa[0] - checkpt[0]) * (pb[0] - checkpt[0]) + + (pa[1] - checkpt[1]) * (pb[1] - checkpt[1]) + + (pa[2] - checkpt[2]) * (pb[2] - checkpt[2]); + return d < 0.; // cos\theta < 0. ==> 90 < theta <= 180 degree. +} + +//============================================================================// +// // +// check_enc_segment() Is a given segment encroached? // +// // +//============================================================================// + +bool tetgenmesh::check_enc_segment(face *chkseg, point *pencpt) +{ + point *ppt = (point *) &(chkseg->sh[3]); + + if (*pencpt != NULL) { + return check_encroachment(ppt[0], ppt[1], *pencpt); + } + + triface searchtet, spintet; + point encpt = NULL, tapex; + REAL prjpt[3]; // The projection point from encpt to segment. + REAL minprjdist = 0., prjdist; + int t1ver; + + sstpivot1(*chkseg, searchtet); + spintet = searchtet; + while (1) { + tapex = apex(spintet); + if (tapex != dummypoint) { + if (check_encroachment(ppt[0], ppt[1], tapex)) { + // Find one encroaching vertex. Calculate its projection distance + projpt2edge(tapex, ppt[0], ppt[1], prjpt); + prjdist = distance(tapex, prjpt); + if (encpt == NULL) { + encpt = tapex; + minprjdist = prjdist; + } else { + if (prjdist < minprjdist) { + encpt = tapex; + minprjdist = prjdist; + } + } + } + } + fnextself(spintet); + if (spintet.tet == searchtet.tet) break; + } + + if (encpt != NULL) { + *pencpt = encpt; // Return this enc point. + return true; + } + + return false; // do not split it. +} + +//============================================================================// +// // +// get_steiner_on_segment() Get the Steiner point to split a given segment.// +// // +//============================================================================// + +bool tetgenmesh::get_steiner_on_segment(face* seg, point refpt, point steinpt) +{ + point ei = sorg(*seg); + point ej = sdest(*seg); + //if (*prefpt == NULL) { + // // Check if this segment is encroached by some existing vertices. + // assert(0); // to do ... + //} + // Is this segment contains an acute seg-seg angle? + bool acute_flag = false; + int i; + + if ((refpt) != NULL) { + // This segment is encroched by an existing vertex. + REAL L, L1, t; + + if (pointtype(refpt) == FREESEGVERTEX) { + face parentseg; + sdecode(point2sh(refpt), parentseg); + int sidx1 = getfacetindex(parentseg); + point far_pi = segmentendpointslist[sidx1 * 2]; + point far_pj = segmentendpointslist[sidx1 * 2 + 1]; + int sidx2 = getfacetindex(*seg); + point far_ei = segmentendpointslist[sidx2 * 2]; + point far_ej = segmentendpointslist[sidx2 * 2 + 1]; + if ((far_pi == far_ei) || (far_pj == far_ei)) { + // Two segments are adjacent at far_ei! + // Create a Steiner point at the intersection of the segment + // [far_ei, far_ej] and the sphere centered at far_ei with + // radius |far_ei - refpt|. + L = distance(far_ei, far_ej); + L1 = distance(far_ei, refpt); + t = L1 / L; + for (i = 0; i < 3; i++) { + steinpt[i] = far_ei[i] + t * (far_ej[i] - far_ei[i]); + } + REAL lfs_at_steiner = distance(refpt, steinpt); + //REAL dist_to_ei = distance(steinpt, ei); + REAL dist_to_ej = distance(steinpt, ej); + if (/*(dist_to_ei < lfs_at_steiner) ||*/ + (dist_to_ej < lfs_at_steiner)) { + // Split the point at the middle. + for (i = 0; i < 3; i++) { + steinpt[i] = ei[i] + 0.5 * (ej[i] - ei[i]); + } + } + set_ridge_vertex_protecting_ball(far_ei); + acute_flag = true; + } else if ((far_pi == far_ej) || (far_pj == far_ej)) { + // Two segments are adjacent at far_ej! + L = distance(far_ei, far_ej); + L1 = distance(far_ej, refpt); + t = L1 / L; + for (i = 0; i < 3; i++) { + steinpt[i] = far_ej[i] + t * (far_ei[i] - far_ej[i]); + } + REAL lfs_at_steiner = distance(refpt, steinpt); + REAL dist_to_ei = distance(steinpt, ei); + //REAL dist_to_ej = distance(steinpt, ej); + if ((dist_to_ei < lfs_at_steiner) /*|| + (dist_to_ej < lfs_at_steiner)*/) { + // Split the point at the middle. + for (i = 0; i < 3; i++) { + steinpt[i] = ei[i] + 0.5 * (ej[i] - ei[i]); + } + } + set_ridge_vertex_protecting_ball(far_ej); + acute_flag = true; + } else { + // Cut the segment by the projection point of refpt. + projpt2edge(refpt, ei, ej, steinpt); + REAL lfs_at_steiner = distance(refpt, steinpt); + REAL dist_to_ei = distance(steinpt, ei); + REAL dist_to_ej = distance(steinpt, ej); + if ((dist_to_ei < lfs_at_steiner) || + (dist_to_ej < lfs_at_steiner)) { + // Split the point at the middle. + for (i = 0; i < 3; i++) { + steinpt[i] = ei[i] + 0.5 * (ej[i] - ei[i]); + } + } + } + } else if (pointtype(refpt) == RIDGEVERTEX) { + int sidx2 = getfacetindex(*seg); + point far_ei = segmentendpointslist[sidx2 * 2]; + point far_ej = segmentendpointslist[sidx2 * 2 + 1]; + if (ridge_vertices_adjacent(far_ei, refpt)) { + // Thjey are adjacent at far_ei. + // Create a Steiner point at the intersection of the segment + // [far_ei, far_ej] and the sphere centered at far_ei with + // radius |far_ei - refpt|. + L = distance(far_ei, far_ej); + L1 = distance(far_ei, refpt); + t = L1 / L; + for (i = 0; i < 3; i++) { + steinpt[i] = far_ei[i] + t * (far_ej[i] - far_ei[i]); + } + REAL lfs_at_steiner = distance(refpt, steinpt); + //REAL dist_to_ei = distance(steinpt, ei); + REAL dist_to_ej = distance(steinpt, ej); + if (/*(dist_to_ei < lfs_at_steiner) ||*/ + (dist_to_ej < lfs_at_steiner)) { + // Split the point at the middle. + for (i = 0; i < 3; i++) { + steinpt[i] = ei[i] + 0.5 * (ej[i] - ei[i]); + } + } + set_ridge_vertex_protecting_ball(far_ei); + acute_flag = true; + } else if (ridge_vertices_adjacent(far_ej, refpt)) { + // Calulate a new point. + L = distance(far_ei, far_ej); + L1 = distance(far_ej, refpt); + t = L1 / L; + for (i = 0; i < 3; i++) { + steinpt[i] = far_ej[i] + t * (far_ei[i] - far_ej[i]); + } + REAL lfs_at_steiner = distance(refpt, steinpt); + REAL dist_to_ei = distance(steinpt, ei); + //REAL dist_to_ej = distance(steinpt, ej); + if ((dist_to_ei < lfs_at_steiner) /*|| + (dist_to_ej < lfs_at_steiner)*/) { + // Split the point at the middle. + for (i = 0; i < 3; i++) { + steinpt[i] = ei[i] + 0.5 * (ej[i] - ei[i]); + } + } + set_ridge_vertex_protecting_ball(far_ej); + acute_flag = true; + } else { + // Cut the segment by the projection point of refpt. + projpt2edge(refpt, ei, ej, steinpt); + REAL lfs_at_steiner = distance(refpt, steinpt); + REAL dist_to_ei = distance(steinpt, ei); + REAL dist_to_ej = distance(steinpt, ej); + if ((dist_to_ei < lfs_at_steiner) || + (dist_to_ej < lfs_at_steiner)) { + // Split the point at the middle. + for (i = 0; i < 3; i++) { + steinpt[i] = ei[i] + 0.5 * (ej[i] - ei[i]); + } + } + } + } else if (pointtype(refpt) == FREEFACETVERTEX) { + // Cut the segment by the projection point of refpt. + projpt2edge(refpt, ei, ej, steinpt); + REAL lfs_at_steiner = distance(refpt, steinpt); + REAL dist_to_ei = distance(steinpt, ei); + REAL dist_to_ej = distance(steinpt, ej); + if ((dist_to_ei < lfs_at_steiner) || + (dist_to_ej < lfs_at_steiner)) { + // Split the point at the middle. + for (i = 0; i < 3; i++) { + steinpt[i] = ei[i] + 0.5 * (ej[i] - ei[i]); + } + } + } else { + // Cut the segment by the projection point of refpt. + projpt2edge(refpt, ei, ej, steinpt); + // Make sure that steinpt is not too close to ei and ej. + REAL lfs_at_steiner = distance(refpt, steinpt); + REAL dist_to_ei = distance(steinpt, ei); + REAL dist_to_ej = distance(steinpt, ej); + if ((dist_to_ei < lfs_at_steiner) || + (dist_to_ej < lfs_at_steiner)) { + // Split the point at the middle. + for (i = 0; i < 3; i++) { + steinpt[i] = ei[i] + 0.5 * (ej[i] - ei[i]); + } + } + } + + // Make sure that steinpt is not too close to ei and ej. + } else { + // Split the point at the middle. + for (i = 0; i < 3; i++) { + steinpt[i] = ei[i] + 0.5 * (ej[i] - ei[i]); + } + } + + + return acute_flag; +} + +//============================================================================// +// // +// split_segment() Split a given segment. // +// // +// If param != NULL, it contains the circumcenter and its insertion radius // +// of a bad quality tetrahedron. Tghis circumcenter is rejected since it // +// encroaches upon this segment. // +// // +//============================================================================// + +bool tetgenmesh::split_segment(face *splitseg, point encpt, REAL *param, + int qflag, int chkencflag, int *iloc) +{ + triface searchtet; + face searchsh; + point newpt; + insertvertexflags ivf; + + + insert_point_count++; + if (!b->quiet && (b->refine_progress_ratio > 0)) { + if (insert_point_count >= report_refine_progress) { + printf(" %ld insertions, added %ld points", + insert_point_count - last_insertion_count, + points->items - last_point_count); + last_point_count = points->items; // update it. + last_insertion_count = insert_point_count; + if (check_tets_list->objects > 0l) { + printf(", %ld tetrahedra in queue.\n", check_tets_list->objects); + } else if (split_subfaces_pool->items > 0l) { + printf(", %ld subfaces in queue.\n", split_subfaces_pool->items); + } else { + printf(", %ld segments in queue.\n", split_segments_pool->items); + } + // The next report event + report_refine_progress *= (1. + b->refine_progress_ratio); + } + } + // Is this segment shared by two facets form an acute dihedral angle? + int segidx = getfacetindex(*splitseg); + bool is_sharp = is_sharp_segment(splitseg); + + if (!qflag && (encpt == NULL)) { + // The split of this segment is due to a rejected ccent of a bad quality + // subface or a tetrahedron. + if (is_sharp) { + // Do not split a sharp segment. + *iloc = (int) SHARPCORNER; + return false; + } + // Do not split this segment if one of its endpoints is a sharp corner. + if (does_seg_contain_acute_vertex(splitseg)) { + *iloc = (int) SHARPCORNER; + return false; + } + } + + // We need to know whether the segment of the new point is adjacent + // to another segment which contains the encroached point (encpt). + makepoint(&newpt, FREESEGVERTEX); + get_steiner_on_segment(splitseg, encpt, newpt); + + // For create_a_shorter_edge() called in insertpoint(). + setpoint2sh(newpt, sencode(*splitseg)); + + // Split the segment by the Bowyer-Watson algorithm. + sstpivot1(*splitseg, searchtet); + ivf.iloc = (int) ONEDGE; + ivf.bowywat = 3; // Use Bowyer-Watson, preserve subsegments and subfaces; + ivf.validflag = 1; // Validate the B-W cavity. + ivf.lawson = 2; // Do flips to recover Delaunayness. + ivf.rejflag = 0; // Do not check encroachment of new segments/facets. + if (b->metric) { + ivf.rejflag |= 4; // Do check encroachment of protecting balls. + } + ivf.chkencflag = chkencflag; + ivf.sloc = (int) INSTAR; // ivf.iloc; + ivf.sbowywat = 3; // ivf.bowywat; // Surface mesh options. + ivf.splitbdflag = 1; + ivf.respectbdflag = 1; + ivf.assignmeshsize = b->metric; + + ivf.smlenflag = useinsertradius; // Return the distance to its nearest vertex. + // Reject a near Steiner point on this segment when: + // - it is only encroached by a rejected circumcenter, or + // - the insertion of the reject ccent is not due to mesh size (qflag). + if (!qflag) { //if (!is_adjacent || !qflag) { + ivf.check_insert_radius = useinsertradius; + } + ivf.parentpt = NULL; + + if (insertpoint(newpt, &searchtet, &searchsh, splitseg, &ivf)) { + st_segref_count++; + if (steinerleft > 0) steinerleft--; + if (useinsertradius) { + REAL rv = 0.0; // param[3]; // emin, maybe zero. + + if (is_sharp) { + // A Steiner point on a sharp segment needs insertion radius. + // Default use the distance to its neartest vertex. + double L = ivf.smlen * 0.95; // (ivf.smlen / 3.); + // Choose the larger one between param[3] and L + rv = (param[3] > L ? param[3] : L); + // Record the minimum insertion radius for this segment. + double minradius = segment_info_list[segidx*4+1]; + if (minradius == 0.) { + minradius = rv; + } else { + if (rv < minradius) minradius = rv; + } + segment_info_list[segidx*4+1] = minradius; + } + + setpointinsradius(newpt, rv); // ivf.smlen + setpoint2ppt(newpt, ivf.parentpt); + if (ivf.smlen < smallest_insradius) { // rv? + smallest_insradius = ivf.smlen; + } + } + if (flipstack != NULL) { + flipconstraints fc; + fc.chkencflag = chkencflag; + fc.enqflag = 2; + lawsonflip3d(&fc); + //unflipqueue->restart(); + } + + if (later_unflip_queue->objects > b->unflip_queue_limit) { + recoverdelaunay(); + } + + *iloc = ivf.iloc; + return true; + } else { + // Point is not inserted. + if (ivf.iloc == (int) NEARVERTEX) { + terminatetetgen(this, 2); // report a bug. + } + + + pointdealloc(newpt); + + *iloc = ivf.iloc; + return false; + } +} + +//============================================================================// +// // +// repairencsegs() Repair encroached (sub) segments. // +// // +//============================================================================// + +void tetgenmesh::repairencsegs(REAL *param, int qflag, int chkencflag) +{ + int split_count = 0, rej_count = 0; + bool ref_segment = ((b->cdtrefine & 1) > 0); // -D1, -D3, -D5, -D7 + + while (ref_segment && + ((badsubsegs->items > 0) || (split_segments_pool->items > 0))) { + + if (badsubsegs->items > 0) { + badsubsegs->traversalinit(); + face *bface = (face *) badsubsegs->traverse(); + while (bface != NULL) { + // A queued segment may have been deleted (split). + if ((bface->sh != NULL) && (bface->sh[3] != NULL)) { + // A queued segment may have been processed. + if (smarktest2ed(*bface)) { + sunmarktest2(*bface); + point encpt = NULL; + if (check_enc_segment(bface, &encpt)) { + badface *bf = (badface *) split_segments_pool->alloc(); + bf->init(); + bf->ss = *bface; + bf->forg = sorg(*bface); + bf->fdest = sdest(*bface); + bf->noppo = encpt; + // Push it onto stack. + bf->nextitem = stack_enc_segments; + stack_enc_segments = bf; + } + } + } + bface = (face *) badsubsegs->traverse(); + } // while (bface != NULL) + badsubsegs->restart(); + } // if (badsubsegs->items > 0) + + if (split_segments_pool->items == 0) break; + + // Stop if we have used the desried number of Steiner points. + if (steinerleft == 0) break; + // Stop if the desried number of tetrahedra is reached. + if ((elem_limit > 0) && + ((tetrahedrons->items - hullsize) > elem_limit)) break; + + // Pop up an encroached segment. + badface *bf = stack_enc_segments; + stack_enc_segments = bf->nextitem; + if ((bf->ss.sh != NULL) && + (sorg(bf->ss) == bf->forg) && + (sdest(bf->ss) == bf->fdest)) { + int iloc = (int) UNKNOWN; + split_count++; + if (!split_segment(&(bf->ss), bf->noppo, param, qflag, chkencflag, &iloc)) { + rej_count++; + } + } + // Return this badface to the pool. + split_segments_pool->dealloc((void *) bf); + } + + if (b->verbose > 2) { + printf(" Trying to split %d segments, %d were rejected.\n", + split_count, rej_count); + } + + if (badsubsegs->items > 0) { + // Clean this list (due to ref_segment). + badsubsegs->traversalinit(); + face *bface = (face *) badsubsegs->traverse(); + while (bface != NULL) { + // A queued segment may have been deleted (split). + if ((bface->sh != NULL) && (bface->sh[3] != NULL)) { + // A queued segment may have been processed. + if (smarktest2ed(*bface)) { + sunmarktest2(*bface); + } + } + bface = (face *) badsubsegs->traverse(); + } // while (bface != NULL) + badsubsegs->restart(); + } // if (badsubsegs->items > 0) + + if (split_segments_pool->items > 0) { + if (steinerleft == 0) { + if (b->verbose) { + printf("The desired number of Steiner points is reached.\n"); + } + } else if (elem_limit > 0) { + if (b->verbose) { + printf("The desired number %ld of elements is reached.\n", elem_limit); + } + } + split_segments_pool->restart(); + stack_enc_segments = NULL; + } +} + +//============================================================================// +// // +// get_subface_ccent() Calculate the circumcenter of the diametrical circ- // +// umsphere of a given subface. // +// // +//============================================================================// + +bool tetgenmesh::get_subface_ccent(face *chkfac, REAL *pos) +{ + point P = (point) chkfac->sh[3]; + point Q = (point) chkfac->sh[4]; + point R = (point) chkfac->sh[5]; + + if (circumsphere(P, Q, R, NULL, pos, NULL)) { + return true; + } else { + terminatetetgen(this, 2); + return false; + } + +} + +//============================================================================// +// // +// check_enc_subface() Check if a given subface is encroached or not. // +// // +//============================================================================// + +bool tetgenmesh::check_enc_subface(face *chkfac, point *pencpt, REAL *ccent, + REAL *radius) +{ + triface adjtet; + point encpt = NULL, pa, pb, pc, toppo; + REAL prjpt[3], minprjdist = 0., prjdist; + REAL ori; + int t1ver; + + //get_subface_ccent(chkfac, ccent); + REAL rd = distance(ccent, sorg(*chkfac)); + *radius = rd; + + if (*pencpt != NULL) { + // This is only used during the insertion of a Steiner point. + REAL len = distance(ccent, *pencpt); + if ((fabs(len - rd) / rd) < 1e-3) len = rd; // Rounding. + if (len < rd) { + return true; + } + return false; + } + + stpivot(*chkfac, adjtet); + if (adjtet.tet == NULL) { + // This subface is not attached to any tet. + return false; + } + for (int i = 0; i < 2; i++) { + toppo = oppo(adjtet); + if (toppo != dummypoint) { + REAL len = distance(ccent, toppo); + //if ((fabs(len - rd) / rd) < b->epsilon) len = rd; // Rounding. + if ((fabs(len - rd) / rd) < 1e-3) len = rd; // Rounding. + if (len < rd) { + int adjacent = 0; // not adjacent + if (pointtype(toppo) == RIDGEVERTEX) { + adjacent = facet_ridge_vertex_adjacent(chkfac, toppo); + } else if (pointtype(toppo) == FREESEGVERTEX) { + face parentseg; + sdecode(point2sh(toppo), parentseg); + adjacent = segfacetadjacent(&parentseg, chkfac); + } else if (pointtype(toppo) == FREEFACETVERTEX) { + face parentsh; + sdecode(point2sh(toppo), parentsh); + int facidx1 = getfacetindex(parentsh); + int facidx2 = getfacetindex(*chkfac); + if (facidx1 == facidx2) { + adjacent = 1; // They are on the same facet. + } + } + if (adjacent) { + // They are adjacent and they are on the same facet. + flippush(flipstack, &adjtet); + return false; + } + pa = org(adjtet); + pb = dest(adjtet); + pc = apex(adjtet); + projpt2face(toppo, pa, pb, pc, prjpt); + ori = orient3d(pa, pb, toppo, prjpt); + if (ori >= 0) { + ori = orient3d(pb, pc, toppo, prjpt); + if (ori >= 0) { + ori = orient3d(pc, pa, toppo, prjpt); + if (ori >= 0) { + prjdist = distance(toppo, prjpt); + if (encpt == NULL) { + encpt = toppo; + minprjdist = prjdist; + } else { + if (prjdist < minprjdist) { + encpt = toppo; + minprjdist = prjdist; + } + } + } // if (ori >= 0) + } // if (ori >= 0) + } // if (ori >= 0) + } // if (len < rd) + } + fsymself(adjtet); + } + + if (encpt != NULL) { + *pencpt = encpt; + return true; + } + + return false; // this subface is not encroached. +} + +//============================================================================// +// // +// check_subface() Is a given subface in a bad shape (radius-edge ratio)? // +// // +//============================================================================// + +bool tetgenmesh::check_subface(face *chkfac, REAL *ccent, REAL radius, REAL *param) +{ + + // Get the shortest edge length. + REAL emin = 1.e+30, dist; + int shver = 0; + for (chkfac->shver = 0; chkfac->shver < 3; chkfac->shver++) { + dist = distance(sorg(*chkfac), sdest(*chkfac)); + if (dist < emin) { + emin = dist; + shver = chkfac->shver; + } + } + chkfac->shver = shver; + + REAL ratio = radius / emin; + if (ratio > b->minratio) { + // Set a small value to protect this vertex (refer to J. Shewchuk). + // Enlarge the insertion radius (due to small angle) + point pa = sorg(*chkfac); + point pb = sdest(*chkfac); + REAL ra = getpointinsradius(pa); + REAL rb = getpointinsradius(pb); + if (ra > 0.) { + if (ra > emin) { + emin = ra; + } + } + if (rb > 0.) { + if (rb > emin) { + emin = rb; + } + } + + param[3] = emin; // emin / 3.; // (emin * b->minratio); + param[4] = ratio; + param[5] = 0.; // not used. + return true; // need to split it. + } + + return false; +} + +//============================================================================// +// // +// enqueue_subface() Push a badly-shaped subface into the priority queue. // +// // +//============================================================================// + +void tetgenmesh::enqueue_subface(face *bface, point encpt, REAL *ccent, REAL *param) +{ + badface *bf = (badface *) split_subfaces_pool->alloc(); + bf->init(); + bf->ss = *bface; + bf->forg = sorg(*bface); + bf->fdest = sdest(*bface); + bf->fapex = sapex(*bface); + bf->noppo = encpt; + int i; + for (i = 0; i < 3; i++) bf->cent[i] = ccent[i]; + for (i = 3; i < 6; i++) bf->cent[i] = param[i]; + + if (encpt != NULL) { + // Push it into the encroaching stack. + bf->nextitem = stack_enc_subfaces; + stack_enc_subfaces = bf; + } else { + // Push it into the priority queue. + REAL qual = 1.0; + if (param[4] > 1.) { + qual = 1.0 / param[4]; // 1 / radius_edge_ratio. + } + // Determine the appropriate queue to put the bad subface into. + int queuenumber = 0; + if (qual < 1) { + queuenumber = (int) (64.0 * (1 - qual)); + if (queuenumber > 63) { + queuenumber = 63; + } + } else { + // It's not a bad shape; put the subface in the lowest-priority queue. + queuenumber = 0; + } + + // Are we inserting into an empty queue? + if (queuefront[queuenumber] == (badface *) NULL) { + // Yes, we are inserting into an empty queue. + // Will this become the highest-priority queue? + if (queuenumber > firstnonemptyq) { + // Yes, this is the highest-priority queue. + nextnonemptyq[queuenumber] = firstnonemptyq; + firstnonemptyq = queuenumber; + } else { + // No, this is not the highest-priority queue. + // Find the queue with next higher priority. + int i = queuenumber + 1; + while (queuefront[i] == (badface *) NULL) { + i++; + } + // Mark the newly nonempty queue as following a higher-priority queue. + nextnonemptyq[queuenumber] = nextnonemptyq[i]; + nextnonemptyq[i] = queuenumber; + } + // Put the bad subface at the beginning of the (empty) queue. + queuefront[queuenumber] = bf; + } else { + // Add the bad tetrahedron to the end of an already nonempty queue. + queuetail[queuenumber]->nextitem = bf; + } + // Maintain a pointer to the last subface of the queue. + queuetail[queuenumber] = bf; + } +} + +// Return the subface at the front of the queue. +tetgenmesh::badface* tetgenmesh::top_subface() +{ + if (stack_enc_subfaces != NULL) { + return stack_enc_subfaces; + } else { + // Keep a record of which queue was accessed in case dequeuebadtetra() + // is called later. + recentq = firstnonemptyq; + // If no queues are nonempty, return NULL. + if (firstnonemptyq < 0) { + return (badface *) NULL; + } else { + // Return the first tetrahedron of the highest-priority queue. + return queuefront[firstnonemptyq]; + } + } +} + +//============================================================================// +// // +// dequeue_subface() Popup a badly-shaped subface from the priority queue. // +// // +//============================================================================// + +void tetgenmesh::dequeue_subface() +{ + badface *bf; + int i; + + if (stack_enc_subfaces != NULL) { + bf = stack_enc_subfaces; + stack_enc_subfaces = bf->nextitem; + // Return the bad subface to the pool. + split_subfaces_pool->dealloc((void *) bf); + } else { + // If queues were empty last time topbadtetra() was called, do nothing. + if (recentq >= 0) { + // Find the tetrahedron last returned by topbadtetra(). + bf = queuefront[recentq]; + // Remove the tetrahedron from the queue. + queuefront[recentq] = bf->nextitem; + // If this queue is now empty, update the list of nonempty queues. + if (bf == queuetail[recentq]) { + // Was this the highest-priority queue? + if (firstnonemptyq == recentq) { + // Yes; find the queue with next lower priority. + firstnonemptyq = nextnonemptyq[firstnonemptyq]; + } else { + // No; find the queue with next higher priority. + i = recentq + 1; + while (queuefront[i] == (badface *) NULL) { + i++; + } + nextnonemptyq[i] = nextnonemptyq[recentq]; + } + } + // Return the bad subface to the pool. + split_subfaces_pool->dealloc((void *) bf); + } + } +} + +//============================================================================// +// // +// parallel_shift() Parallel shift a triangle along its normal. // +// // +// Given a triangle (a, b, c), create a parallel triangle (pa, pb, pc) at a // +// distance above (a, b, c). // +// // +//============================================================================// + +void tetgenmesh::parallel_shift(point pa, point pb, point pc, + point pt, REAL* ppt) +{ + // Get the normal and the average edge length of this triangle. + REAL N[3], Lav; + facenormal(pa, pb, pc, N, 1, &Lav); + + // Normalize the normal. + REAL L = sqrt(N[0]*N[0]+N[1]*N[1]+N[2]*N[2]); + N[0] /= L; + N[1] /= L; + N[2] /= L; + + // Calculate the shifted vertices. + for (int i = 0; i < 3; i++) { + ppt[0] = pt[0] + Lav * N[0]; + ppt[1] = pt[1] + Lav * N[1]; + ppt[2] = pt[2] + Lav * N[2]; + } + +} + +//============================================================================// +// // +// locate_on_surface() Locate a vertex in a facet. // +// // +//============================================================================// + +enum tetgenmesh::locateresult +tetgenmesh::locate_on_surface(point searchpt, face* searchsh) +{ + enum locateresult loc = OUTSIDE; + + triface searchtet; + stpivot(*searchsh, searchtet); + if (ishulltet(searchtet)) { + sesymself(*searchsh); + stpivot(*searchsh, searchtet); + } + + // Select an edge such that pt lies to CCW of it. + point pa, pb, pc; + REAL toppo[3]; // a parallel-shifted point + REAL n1[3], n2[3], cosang; + int t1ver; // used by fnextself() + int i; + + for (i = 0; i < 3; i++) { + pa = org(searchtet); + pb = dest(searchtet); + pc = apex(searchtet); + parallel_shift(pa, pb, pc, pa, toppo); + if (orient3d(pa, pb, toppo, searchpt) > 0) { + break; + } + enextself(searchtet); + } + if (i == 3) { + terminatetetgen(this, 2); + } + + while (true) { + + // Let E = [a,b,c] and p lies to the CCW of [a->b]. + // Make sure that the searching vertex and the current subface (a,b,c) are + // (nearly) coplanar. We check the dihedral angle between (a,b,c) and + // (a,b,searchpt). If it is within the tolerance of co-planar facets, + // then we continue the search, otherwise, the search is stopped. + facenormal(pa, pb, pc, n1, 1, NULL); + facenormal(pb, pa, searchpt, n2, 1, NULL); + cosang = dot(n1, n2) / (sqrt(dot(n1, n1)) * sqrt(dot(n2, n2))); + if (cosang > cos_facet_separate_ang_tol) { + // The searching vertex is not coplanar with this subface. + loc = NONCOPLANAR; + break; + } + + parallel_shift(pa, pb, pc, pc, toppo); + REAL ori1 = orient3d(pb, pc, toppo, searchpt); + REAL ori2 = orient3d(pc, pa, toppo, searchpt); + + if (ori1 > 0) { + if (ori2 > 0) { + //break; // Found. + loc = ONFACE; break; + } else if (ori2 < 0) { + //E.ver = _eprev_tbl[E.ver]; + eprevself(searchtet); + } else { // ori2 == 0 + //E.ver = _eprev_tbl[E.ver]; + //return LOC_ON_EDGE; // ONEDGE p lies on edge [c,a] + eprevself(searchtet); + loc = ONEDGE; break; + } + } else if (ori1 < 0) { + if (ori2 > 0) { + //E.ver = _enext_tbl[E.ver]; + enextself(searchtet); + } else if (ori2 < 0) { + // Randomly choose one. + if (rand() % 2) { // flipping a coin. + //E.ver = _enext_tbl[E.ver]; + enextself(searchtet); + } else { + //E.ver = _eprev_tbl[E.ver]; + eprevself(searchtet); + } + } else { // ori2 == 0 + //E.ver = _enext_tbl[E.ver]; + enextself(searchtet); + } + } else { // ori1 == 0 + if (ori2 > 0) { + //E.ver = _enext_tbl[E.ver]; // p lies on edge [b,c]. + //return LOC_ON_EDGE; // ONEDGE + enextself(searchtet); + loc = ONEDGE; break; + } else if (ori2 < 0) { + //E.ver = _eprev_tbl[E.ver]; + eprevself(searchtet); + } else { // ori2 == 0 + //E.ver = _eprev_tbl[E.ver]; // p is coincident with apex. + //return LOC_ON_VERT; // ONVERTEX Org(E) + eprevself(searchtet); + loc = ONVERTEX; break; + } + } + + // Check if we want to cross a segment. + if (issubseg(searchtet)) { + loc = ENCSEGMENT; break; + } + + // Goto the adjacent subface at this subedge. + int fcount = 0; + while (fcount < 100000) { + esymself(searchtet); + if (issubface(searchtet)) break; + fsymself(searchtet); + fcount++; + } + if (!issubface(searchtet)) { + terminatetetgen(this, 2); // report a bug + } + + // Update the vertices. + pa = org(searchtet); + pb = dest(searchtet); + pc = apex(searchtet); + //toppo = oppo(searchtet); + } // while (true) + + tspivot(searchtet, *searchsh); + + return loc; +} + +//============================================================================// +// // +// split_subface() Split a subface. // +// // +// param[6], it contains the following data: // +// [0],[1],[2] - the location of a rejected circumcent, // +// [3] - the samllest edge length ( = insertion radius) // +// [4] - ratio-edge ratio (of this subface). // +// If it is zero, it is an encroached subface. // +// [5] - no used. // +/// // +//============================================================================// + +bool tetgenmesh::split_subface(face *splitfac, point encpt, REAL *ccent, + REAL *param, int qflag, int chkencflag, int *iloc) +{ + triface searchtet; + face searchsh; + insertvertexflags ivf; + point newpt, bak_pts[3], *ppt; + bool is_adjacent = false; + bool splitflag = false; // Indicate if any Steiner point is added. + int i; + + insert_point_count++; + if (!b->quiet && (b->refine_progress_ratio > 0.)) { + if (insert_point_count >= report_refine_progress) { + printf(" %ld insertions, added %ld points", + insert_point_count - last_insertion_count, + points->items - last_point_count); + last_point_count = points->items; // update it. + last_insertion_count = insert_point_count; + if (check_tets_list->objects > 0l) { + printf(", %ld tetrahedra in queue.\n", check_tets_list->objects); + } else { + printf(", %ld subfaces in queue.\n", split_subfaces_pool->items); + } + // The next report event + report_refine_progress *= (1. + b->refine_progress_ratio); + } + } + + // Check if this subface is adjacent to a sharp segment, i.e., it is incident + // by two facets which form an acute dihedral angle. + face checkface = *splitfac; + face checkseg; + for (i = 0; i < 3; i++) { + sspivot(checkface, checkseg); + if (checkseg.sh != NULL) { + if (is_sharp_segment(&checkseg)) { + is_adjacent = true; + break; + } + } + senext2self(checkface); + } + + if (is_adjacent) { + // Only split it either it is a bad quality triangle, or due to the + // qflag, i.e., mesh size requirement. + if (!qflag) { + if (encpt != NULL) { + *iloc = (int) SHARPCORNER; + return false; // reject splitting this subface. + } else { + if (param[4] == 0.0) { + // It is not a bad quality subface. + *iloc = (int) SHARPCORNER; + return false; // reject splitting this subface. + } + } + } + } // if (is_adjacent) + + + // Deciding the inserting point. + if (encpt != NULL) { + // Insert at the projection of the encpt on the facet. + REAL pos[3]; + ppt = (point *) &(splitfac->sh[3]); + projpt2face(encpt, ppt[0], ppt[1], ppt[2], pos); + makepoint(&newpt, FREEFACETVERTEX); + for (i = 0; i < 3; i++) newpt[i] = pos[i]; + + //if (is_adjacent) { + // Check whether this new position is too close to an existing vertex. + REAL prjdist = distance(encpt, newpt); + REAL dist, mindist = 1.e+30; + for (i = 0; i < 3; i++) { + dist = distance(ppt[i], newpt); + if (dist < mindist) mindist = dist; + } + if (mindist < prjdist) { + // Use the circumcenter of this triange instead of the proj of encpt. + for (i = 0; i < 3; i++) newpt[i] = ccent[i]; + } + //} + } else { + // Split the subface at its circumcenter. + makepoint(&newpt, FREEFACETVERTEX); + for (i = 0; i < 3; i++) newpt[i] = ccent[i]; + } + + // This info is needed by create_a_shorter_edge() (called in insertpoint()). + setpoint2sh(newpt, sencode(*splitfac)); + + + searchsh = *splitfac; + ivf.iloc = (int) locate_on_surface(newpt, &searchsh); + + if (ivf.iloc == (int) ENCSEGMENT) { + // Point lies in the outside of the facet. + pointdealloc(newpt); + *iloc = FENSEDIN; // it is a fested in vertex. + return splitflag; + } else if (ivf.iloc == (int) ONVERTEX) { + pointdealloc(newpt); + *iloc = ONVERTEX; + return splitflag; + } else if (ivf.iloc == (int) NONCOPLANAR) { + pointdealloc(newpt); + *iloc = NONCOPLANAR; + return splitflag; + } + + if ((ivf.iloc != (int) ONFACE) && (ivf.iloc != (int) ONEDGE)) { + terminatetetgen(this, 2); // report a bug + } + + // Insert the point. + stpivot(searchsh, searchtet); + ivf.bowywat = 3; // Use Bowyer-Watson. Preserve subsegments and subfaces; + ivf.lawson = 2; + ivf.rejflag = 1; // Do check the encroachment of segments. + if (b->metric) { + ivf.rejflag |= 4; // Do check encroachment of protecting balls. + } + ivf.chkencflag = (chkencflag & (~1)); + ivf.sloc = (int) INSTAR; // ivf.iloc; + ivf.sbowywat = 3; // ivf.bowywat; + ivf.splitbdflag = 1; + ivf.validflag = 1; + ivf.respectbdflag = 1; + ivf.assignmeshsize = b->metric; + + ivf.refineflag = 2; + ivf.refinesh = *splitfac; + + ivf.smlenflag = useinsertradius; // Update the insertion radius. + + // Reject a near Steiner point on this subface when: + // - the insertion of the reject ccent is not due to mesh size (qflag). + if (!qflag) { + ivf.check_insert_radius = useinsertradius; + } + //if (is_adjacent) { + // ivf.parentpt = encpt; // This allows to insert a shorter edge. + //} else { + ivf.parentpt = NULL; // init + //} + + if (insertpoint(newpt, &searchtet, &searchsh, NULL, &ivf)) { + st_facref_count++; + if (steinerleft > 0) steinerleft--; + if (useinsertradius) { + REAL rv = 0.0; // param[3]; // emin, maybe zero. + + if (is_adjacent) { // if (encpt != NULL) { + // A sharp (dihedral) angle is involved. + // Insertion radius must be > 0. + double L = (ivf.smlen / 3.); + // Choose the larger one between param[3] and L + rv = (param[3] > L ? param[3] : L); + } + + setpointinsradius(newpt, rv); + setpoint2ppt(newpt, ivf.parentpt); + if (smallest_insradius > ivf.smlen) { + smallest_insradius = ivf.smlen; + } + } + if (flipstack != NULL) { + flipconstraints fc; + fc.chkencflag = (chkencflag & (~1)); //chkencflag; + fc.enqflag = 2; + lawsonflip3d(&fc); + //unflipqueue->restart(); + } + + if (later_unflip_queue->objects > b->unflip_queue_limit) { + recoverdelaunay(); + } + + *iloc = ivf.iloc; + return true; + } + + // Point is not inserted. + pointdealloc(newpt); + + if (ivf.iloc == (int) ENCSEGMENT) { + // Bakup the split subface. + ppt = (point *) &(splitfac->sh[3]); + for (i = 0; i < 3; i++) bak_pts[i] = ppt[i]; + + bool ref_segment = ((b->cdtrefine & 1) > 0); // -D1, -D3, -D5, or -D7 + + if (ref_segment || qflag) { + // Select an encroached segment and split it. + for (i = 0; i < encseglist->objects; i++) { + //face *paryseg = (face *) fastlookup(encseglist, i); + badface *bf = (badface *) fastlookup(encseglist, i); + if ((bf->ss.sh == NULL) || + (sorg(bf->ss) != bf->forg) || + (sdest(bf->ss) != bf->fdest)) continue; // Skip this segment. + int tmp_iloc; + if (split_segment(&(bf->ss), NULL, param, qflag, (chkencflag | 1), &tmp_iloc)) { + // A Steiner point is inserted on an encroached segment. + // Check if this subface is split as well. + if ((splitfac->sh == NULL) || (splitfac->sh[3] == NULL)) { + splitflag = true; break; + } else { + ppt = (point *) &(splitfac->sh[3]); + if ((ppt[0] != bak_pts[0]) || + (ppt[1] != bak_pts[1]) || + (ppt[2] != bak_pts[2])) { + splitflag = true; break; + } + } + } + } + } // if (ref_segment) + encseglist->restart(); + // Some segments may be encroached. + if (badsubsegs->items > 0) { + //repairencsegs(param, qflag, (chkencflag | 1)); + repairencsegs(param, 0, (chkencflag | 1)); // qflag = 0 + } + // Check if this subface is split as well. + if ((splitfac->sh == NULL) || (splitfac->sh[3] == NULL)) { + splitflag = true; + } else { + ppt = (point *) &(splitfac->sh[3]); + if ((ppt[0] != bak_pts[0]) || + (ppt[1] != bak_pts[1]) || + (ppt[2] != bak_pts[2])) { + splitflag = true; + } + } + } else if (ivf.iloc == (int) NEARVERTEX) { + terminatetetgen(this, 2); // report a bug + } + + *iloc = ivf.iloc; + return splitflag; +} + +//============================================================================// +// // +// repairencfacs() Repair encroached subfaces. // +// // +//============================================================================// + +void tetgenmesh::repairencfacs(REAL *param, int qflag, int chkencflag) +{ + point encpt = NULL; + REAL ccent[3], radius; //, param[6] = {0.,}; + int split_count = 0, rej_count = 0; + //int qflag = 0; + int i; + + bool ref_subface = ((b->cdtrefine & 2) > 0); // -D2, -D3, -D6, -D7 + + // This function may be called from split_tetrahedron(). In this case, the + // insertion radius of the rejected circumcenter is stored in param[3]. + // The check_subface() will return the insertion radius of the circumcenter + // of a bad quality subface also in param[3]. + REAL tet_emin = param[3]; + + while (ref_subface && + ((badsubfacs->items > 0) || (split_subfaces_pool->items > 0))) { + + if (badsubfacs->items > 0) { + badsubfacs->traversalinit(); + face *bface = (face *) badsubfacs->traverse(); + while (bface != NULL) { + // A queued subface may have been deleted (split). + if ((bface->sh != NULL) && (bface->sh[3] != NULL)) { + // A queued subface may have been processed. + if (smarktest2ed(*bface)) { + sunmarktest2(*bface); + for (i = 3; i < 6; i++) param[i] = 0.; // Clear previous values. + if (get_subface_ccent(bface, ccent)) { + encpt = NULL; + if (check_enc_subface(bface, &encpt, ccent, &radius)) { + param[3] = tet_emin; // maybe zero. + enqueue_subface(bface, encpt, ccent, param); + } else { + if (check_subface(bface, ccent, radius, param)) { + if (tet_emin > 0) { + // Use the larger one. + param[3] = (param[3] > tet_emin ? param[3] : tet_emin); + } + enqueue_subface(bface, NULL, ccent, param); + } + } + } else { + // report a bug. + terminatetetgen(this, 2); + } + } + } + bface = (face *) badsubfacs->traverse(); + } // while (bface != NULL) + + badsubfacs->restart(); // clear this pool + + // check_enc_subface() may find some non-Delaunay subfaces. + if (flippool->items > 0) { + flipconstraints fc; + fc.chkencflag = chkencflag; + fc.enqflag = 2; + lawsonflip3d(&fc); + } + } // if (badsubfacs->items > 0) + + if (split_subfaces_pool->items == 0) break; + + // Stop if we have used the desried number of Steiner points. + if (steinerleft == 0) break; + // Stop if the desried number of tetrahedra is reached. + if ((elem_limit > 0) && + ((tetrahedrons->items - hullsize) > elem_limit)) break; + + + badface *bf = top_subface(); + + if ((bf->ss.sh != NULL) && + ( sorg(bf->ss) == bf->forg) && + (sdest(bf->ss) == bf->fdest) && + (sapex(bf->ss) == bf->fapex)) { + // Try to split this subface. + encpt = bf->noppo; // The encroaching vertex. + for (i = 0; i < 3; i++) ccent[i] = bf->cent[i]; + for (i = 3; i < 6; i++) param[i] = bf->cent[i]; + split_count++; + + int iloc = (int) UNKNOWN; + if (!split_subface(&bf->ss, encpt, ccent, param, qflag, chkencflag, &iloc)) { + rej_count++; + if (qflag || ((param[4] > (3. * b->minratio)) && (iloc != SHARPCORNER))) { + // Queue a unsplit (bad quality) subface. + badface *bt = NULL; + unsplit_subfaces->newindex((void **) &bt); + //bt->init(); + *bt = *bf; + } + } + } + dequeue_subface(); + } // while ((badsubfacs->items > 0) || (split_subfaces_pool->items > 0)) + + if (b->verbose > 3) { + printf(" Tried to split %d subfaces, %d were rejected.\n", + split_count, rej_count); + } + param[3] = tet_emin; // Restore this value. + + if (badsubfacs->items > 0) { + // Clean this list (due to the ref_subface flag) + badsubfacs->traversalinit(); + face *bface = (face *) badsubfacs->traverse(); + while (bface != NULL) { + // A queued subface may have been deleted (split). + if ((bface->sh != NULL) && (bface->sh[3] != NULL)) { + // A queued subface may have been processed. + if (smarktest2ed(*bface)) { + sunmarktest2(*bface); + } + } + bface = (face *) badsubfacs->traverse(); + } // while (bface != NULL) + badsubfacs->restart(); // clear this pool + } // if (badsubfacs->items > 0) + + if (split_subfaces_pool->items > 0) { + if (steinerleft == 0) { + if (b->verbose) { + printf("The desired number of Steiner points is reached.\n"); + } + } else if (elem_limit > 0) { + if (b->verbose) { + printf("The desired number %ld of elements is reached.\n", elem_limit); + } + } + split_subfaces_pool->restart(); // Clear this pool. + unsplit_subfaces->restart(); + stack_enc_subfaces = NULL; + } +} + +//============================================================================// +// // +// check_tetrahedron() Check if the tet needs to be split. // +// // +// "param[6]" returns the following data: // +// [0],[1],[2] - the location of the new point // +// [3] - the samllest edge length ( = insertion radius) // +// [4] - the radius-edge ratio // +// [5] - (optional) edge ratio // +// // +// "chktet" returns the shortest edge of this tet. // +// // +//============================================================================// + +bool tetgenmesh::check_tetrahedron(triface *chktet, REAL* param, int &qflag) +{ + point pd = (point) chktet->tet[7]; + if (pd == dummypoint) { + return false; // Do not split a hull tet. + } + + point pa = (point) chktet->tet[4]; + point pb = (point) chktet->tet[5]; + point pc = (point) chktet->tet[6]; + + + REAL D = orient3dexact(pa, pb, pc, pd); // =6*vol + + if (D >= 0.0) { + // A degenerated tetrahedron. + terminatetetgen(this, 2); + } + + qflag = 0; // default + + REAL elen[6]; + REAL vol = -D / 6.0; + REAL emin = 0., ratio = 0.; + + // Calculate the circumcenter of this tet. + point P = pa, Q = pb, R = pc, S = pd; + + REAL U[3], V[3], W[3], Z[3]; // variables. + + REAL hp = P[0]*P[0] + P[1]*P[1] + P[2]*P[2]; // - wp + REAL hq = Q[0]*Q[0] + Q[1]*Q[1] + Q[2]*Q[2]; // - wq + REAL hr = R[0]*R[0] + R[1]*R[1] + R[2]*R[2]; // - wr + REAL hs = S[0]*S[0] + S[1]*S[1] + S[2]*S[2]; // - wr + + U[0] = hp; U[1] = P[1]; U[2] = P[2]; + V[0] = hq; V[1] = Q[1]; V[2] = Q[2]; + W[0] = hr; W[1] = R[1]; W[2] = R[2]; + Z[0] = hs; Z[1] = S[1]; Z[2] = S[2]; + + REAL D1 = orient3d(U, V, W, Z); + + U[0] = P[0]; U[1] = hp; //U[2] = P[2]; + V[0] = Q[0]; V[1] = hq; //V[2] = Q[2]; + W[0] = R[0]; W[1] = hr; //W[2] = R[2]; + Z[0] = S[0]; Z[1] = hs; //Z[2] = S[2]; + + REAL D2 = orient3d(U, V, W, Z); + + /*U[0] = P[0];*/ U[1] = P[1]; U[2] = hp; + /*V[0] = Q[0];*/ V[1] = Q[1]; V[2] = hq; + /*W[0] = R[0];*/ W[1] = R[1]; W[2] = hr; + /*Z[0] = S[0];*/ Z[1] = S[1]; Z[2] = hs; + + REAL D3 = orient3d(U, V, W, Z); + + REAL DD = D * 2.; + + param[0] = D1 / DD; + param[1] = D2 / DD; + param[2] = D3 / DD; + + + param[4] = 1.0; // default a good ratio. + param[5] = vol; + + elen[0] = distance2(pc, pd); + elen[1] = distance2(pd, pa); + elen[2] = distance2(pa, pb); + elen[3] = distance2(pb, pc); + elen[4] = distance2(pb, pd); + elen[5] = distance2(pa, pc); + + // Find the shortest edge. + emin = elen[0]; + int eidx = 0; + for (int i = 1; i < 6; i++) { + if (emin > elen[i]) { + emin = elen[i]; eidx = i; + } + } + emin = sqrt(emin); + // Let chktet be the shortest edge in this tet. + chktet->ver = edge2ver[eidx]; + + // check mesh size (qflag). + if (b->varvolume || b->fixedvolume) { // -a# + if (b->fixedvolume) { + if (vol > b->maxvolume) { + // set the insertion radius, use the smaller one between the + // smallest edge length of this tet and mesh size; + emin = (emin < b->maxvolume_length ? emin : b->maxvolume_length); + qflag = 1; + } + } + if (!qflag && b->varvolume) { + REAL volbnd = volumebound(chktet->tet); + if ((volbnd > 0.0) && (vol > volbnd)) { + // set the insertion radius; + REAL msize = pow(volbnd, 1./3.) / 3.; + emin = (emin < msize ? emin : msize); + qflag = 1; + } + } + } // -a# + + if (!qflag && b->metric) { // -m + //int eidx = 0; + for (int i = 0; i < 6; i++) { + elen[i] = sqrt(elen[i]); + } + if (pa[pointmtrindex] > 0) { + // Get the longest edge {pa, pd}, {pa, pb}, {pa, pc} + REAL maxelen = elen[1]; //eidx = 1; + if (maxelen < elen[2]) {maxelen = elen[2]; /*eidx = 2;*/} + if (maxelen < elen[5]) {maxelen = elen[5]; /*eidx = 5;*/} + maxelen /= 2.0; + if (maxelen > pa[pointmtrindex]) { + emin = (emin < pa[pointmtrindex] ? emin : pa[pointmtrindex]); + //emax = maxelen; + qflag = 1; + } + } + if (!qflag && (pb[pointmtrindex] > 0)) { + // Get the longest edge at pb. + REAL maxelen = elen[2]; //eidx = 2; + if (maxelen < elen[3]) {maxelen = elen[3]; /*eidx = 3;*/} + if (maxelen < elen[4]) {maxelen = elen[4]; /*eidx = 4;*/} + maxelen /= 2.0; + if (maxelen > pb[pointmtrindex]) { + emin = (emin < pb[pointmtrindex] ? emin : pb[pointmtrindex]); + //emax = maxelen; + qflag = 1; + } + } + if (!qflag && (pc[pointmtrindex] > 0)) { + // Get the longest edge at pc. + REAL maxelen = elen[0]; //eidx = 0; + if (maxelen < elen[3]) {maxelen = elen[3]; /*eidx = 3;*/} + if (maxelen < elen[5]) {maxelen = elen[5]; /*eidx = 5;*/} + maxelen /= 2.0; + if (maxelen > pc[pointmtrindex]) { + emin = (emin < pc[pointmtrindex] ? emin : pc[pointmtrindex]); + //emax = maxelen; + qflag = 1; + } + } + if (!qflag && (pd[pointmtrindex] > 0)) { + // Get the longest edge at pd. + REAL maxelen = elen[0]; //eidx = 0; + if (maxelen < elen[1]) {maxelen = elen[1]; /*eidx = 1;*/} + if (maxelen < elen[4]) {maxelen = elen[4]; /*eidx = 4;*/} + maxelen /= 2.0; + if (maxelen > pd[pointmtrindex]) { + emin = (emin < pd[pointmtrindex] ? emin : pd[pointmtrindex]); + //emax = maxelen; + qflag = 1; + } + } + } // if (!qflag && b->metric) // -m + + if (qflag) { + param[3] = emin; // The desired mesh size. + //param[4] = 1.0; // ratio; // = 0. + //param[5] = vol; + return true; + } + + if (b->minratio > 1.0) { + REAL radius = distance(param, pa); + + ratio = radius / emin; + + + if (ratio > b->minratio) { + //qflag = 0; + // The smallest insertion radius should be at least larger than + // the smallest edge length (==> graded mesh size). + point pa = org(*chktet); + point pb = dest(*chktet); + REAL ra = getpointinsradius(pa); + REAL rb = getpointinsradius(pb); + if ((ra > 0.) && (ra > emin)) { + emin = ra; // the relaxed (enlarged) insertion radius. + } + if ((rb > 0.) && (rb > emin)) { + emin = rb; // the relaxed (enlarged) insertion radius. + } + + param[3] = emin; // (emin * b->minratio); + param[4] = ratio; + //param[5] = vol; + return true; + } + } + + return false; // no need to split this tetrahedron. +} + +//============================================================================// +// // +// checktet4split() Check if a given tet has a bad shape. // +// // +//============================================================================// + +bool tetgenmesh::checktet4split(triface *chktet, REAL* param, int& qflag) +{ + point pa, pb, pc, pd, *ppt; + REAL vda[3], vdb[3], vdc[3]; + REAL vab[3], vbc[3], vca[3]; + REAL N[4][3], L[4], cosd[6], elen[6]; + REAL maxcosd, vol, volbnd, rd, Lmax, Lmin; + REAL A[4][4], rhs[4], D; + int indx[4]; + int i, j; + + if (b->convex) { // -c + // Skip this tet if it lies in the exterior. + if (elemattribute(chktet->tet, numelemattrib - 1) == -1.0) { + return 0; + } + } + + qflag = 0; + for (i = 0; i < 6; i++) param[i] = 0.; + + pd = (point) chktet->tet[7]; + if (pd == dummypoint) { + return 0; // Do not split a hull tet. + } + + pa = (point) chktet->tet[4]; + pb = (point) chktet->tet[5]; + pc = (point) chktet->tet[6]; + + + // Get the edge vectors vda: d->a, vdb: d->b, vdc: d->c. + // Set the matrix A = [vda, vdb, vdc]^T. + for (i = 0; i < 3; i++) A[0][i] = vda[i] = pa[i] - pd[i]; + for (i = 0; i < 3; i++) A[1][i] = vdb[i] = pb[i] - pd[i]; + for (i = 0; i < 3; i++) A[2][i] = vdc[i] = pc[i] - pd[i]; + + // Get the other edge vectors. + for (i = 0; i < 3; i++) vab[i] = pb[i] - pa[i]; + for (i = 0; i < 3; i++) vbc[i] = pc[i] - pb[i]; + for (i = 0; i < 3; i++) vca[i] = pa[i] - pc[i]; + + if (!lu_decmp(A, 3, indx, &D, 0)) { + // Is it a degenerated tet (vol = 0). + REAL D = orient3dexact(pa, pb, pc, pd); // =6*vol + if (D >= 0.0) { + // A degenerated tetrahedron. + terminatetetgen(this, 2); + } + // We temporarily leave this tet. It should be fixed by mesh improvement. + return false; + } + + // Calculate the circumcenter and radius of this tet. + rhs[0] = 0.5 * dot(vda, vda); + rhs[1] = 0.5 * dot(vdb, vdb); + rhs[2] = 0.5 * dot(vdc, vdc); + lu_solve(A, 3, indx, rhs, 0); + + for (i = 0; i < 3; i++) param[i] = pd[i] + rhs[i]; + rd = sqrt(dot(rhs, rhs)); + + // Check volume if '-a#' and '-a' options are used. + if (b->varvolume || b->fixedvolume) { + vol = fabs(A[indx[0]][0] * A[indx[1]][1] * A[indx[2]][2]) / 6.0; + if (b->fixedvolume) { + if (vol > b->maxvolume) { + qflag = 1; + } + } + if (!qflag && b->varvolume) { + volbnd = volumebound(chktet->tet); + if ((volbnd > 0.0) && (vol > volbnd)) { + qflag = 1; + } + } + if (qflag == 1) { + return true; + } + } + + if (b->metric) { // -m option. Check mesh size. + // Check if the ccent lies outside one of the prot.balls at vertices. + ppt = (point *) &(chktet->tet[4]); + for (i = 0; i < 4; i++) { + if (ppt[i][pointmtrindex] > 0) { + if (rd > ppt[i][pointmtrindex]) { + qflag = 1; // Enforce mesh size. + return true; + } + } + } + } + + if (in->tetunsuitable != NULL) { + // Execute the user-defined meshing sizing evaluation. + if ((*(in->tetunsuitable))(pa, pb, pc, pd, NULL, 0)) { + return true; + } + } + + + // Check the radius-edge ratio. Set by -q#. + if (b->minratio > 0) { + elen[0] = dot(vdc, vdc); + elen[1] = dot(vda, vda); + elen[2] = dot(vab, vab); + elen[3] = dot(vbc, vbc); + elen[4] = dot(vdb, vdb); + elen[5] = dot(vca, vca); + + Lmax = Lmin = elen[0]; + int eidx = 0; + for (i = 1; i < 6; i++) { + Lmax = (Lmax < elen[i] ? elen[i] : Lmax); + //Lmin = (Lmin > elen[i] ? elen[i] : Lmin); + if (Lmin > elen[i]) { + Lmin = elen[i]; eidx = i; + } + } + // Let chktet be the shortest edge in this tet. + chktet->ver = edge2ver[eidx]; + + //Lmax = sqrt(Lmax); + Lmin = sqrt(Lmin); + D = rd / Lmin; + if (D > b->minratio) { + // A bad radius-edge ratio. + param[3] = Lmin; + param[4] = D; + param[5] = sqrt(Lmax) / Lmin; // edge ratio. + return true; + } + } // if (b->minratio > 0) + + // Check the minimum dihedral angle. Set by -q/#. + if (b->mindihedral > 0) { + // Compute the 4 face normals (N[0], ..., N[3]). + for (j = 0; j < 3; j++) { + for (i = 0; i < 3; i++) N[j][i] = 0.0; + N[j][j] = 1.0; // Positive means the inside direction + lu_solve(A, 3, indx, N[j], 0); + } + for (i = 0; i < 3; i++) N[3][i] = - N[0][i] - N[1][i] - N[2][i]; + // Normalize the normals. + for (i = 0; i < 4; i++) { + L[i] = sqrt(dot(N[i], N[i])); + if (L[i] == 0) { + terminatetetgen(this, 2); + } + for (j = 0; j < 3; j++) N[i][j] /= L[i]; + } + // Calculate the six dihedral angles. + cosd[0] = -dot(N[0], N[1]); // Edge cd, bd, bc. + cosd[1] = -dot(N[0], N[2]); + cosd[2] = -dot(N[0], N[3]); + cosd[3] = -dot(N[1], N[2]); // Edge ad, ac + cosd[4] = -dot(N[1], N[3]); + cosd[5] = -dot(N[2], N[3]); // Edge ab + // Get the smallest dihedral angle. + //maxcosd = mincosd = cosd[0]; + maxcosd = cosd[0]; + for (i = 1; i < 6; i++) { + //if (cosd[i] > maxcosd) maxcosd = cosd[i]; + maxcosd = (cosd[i] > maxcosd ? cosd[i] : maxcosd); + //mincosd = (cosd[i] < mincosd ? cosd[i] : maxcosd); + } + if (maxcosd > cosmindihed) { + // A bad dihedral angle. + return true; + } + } // if (b->mindihedral > 0) + + return 0; +} + +//============================================================================// +// // +// locate_point_walk() Locate a point by line searching. // +// // +//============================================================================// + +enum tetgenmesh::locateresult + tetgenmesh::locate_point_walk(point searchpt, triface* searchtet, int chkencflag) +{ + // Construct the starting point to be the barycenter of 'searchtet'. + REAL startpt[3]; + point *ppt = (point *) &(searchtet->tet[4]); + for (int i = 0; i < 3; i++) { + startpt[i] = (ppt[0][i] + ppt[1][i] + ppt[2][i] + ppt[3][i]) / 4.; + } + + point torg, tdest, tapex, toppo; + REAL ori, oriorg, oridest, oriapex; + enum locateresult loc = OUTSIDE; + enum {ORGMOVE, DESTMOVE, APEXMOVE} nextmove; + + for (searchtet->ver = 0; searchtet->ver < 4; searchtet->ver++) { + torg = org(*searchtet); + tdest = dest(*searchtet); + tapex = apex(*searchtet); + ori = orient3d(torg, tdest, tapex, searchpt); + if (ori < 0) break; + } + + if (searchtet->ver == 4) { + terminatetetgen(this, 2); + } + int max_visited_tets = 10000; // tetrahedrons->items; + + // Walk through tetrahedra to locate the point. + while (max_visited_tets > 0) { + toppo = oppo(*searchtet); + + // Check if the vertex is we seek. + if (toppo == searchpt) { + // Adjust the origin of searchtet to be searchpt. + esymself(*searchtet); + eprevself(*searchtet); + loc = ONVERTEX; // return ONVERTEX; + break; + } + + // We enter from the crruent face of `serarchtet', which face do we exit? + // Find the next face which is intersect with the line (startpt->searchpt). + oriorg = orient3d(tdest, tapex, toppo, searchpt); + oridest = orient3d(tapex, torg, toppo, searchpt); + oriapex = orient3d( torg, tdest, toppo, searchpt); + + if (oriorg < 0) { + if (oridest < 0) { + if (oriapex < 0) { + // All three faces are possible. + if (tri_edge_test(tdest,tapex,toppo,startpt,searchpt,NULL,0,NULL,NULL)) { + nextmove = ORGMOVE; + } else if (tri_edge_test(tapex,torg,toppo,startpt,searchpt,NULL,0,NULL,NULL)) { + nextmove = DESTMOVE; + } else if (tri_edge_test(torg,tdest,toppo,startpt,searchpt,NULL,0,NULL,NULL)) { + nextmove = APEXMOVE; + } else { + int s = randomnation(3); // 's' is in {0,1,2}. + if (s == 0) { + nextmove = ORGMOVE; + } else if (s == 1) { + nextmove = DESTMOVE; + } else { + nextmove = APEXMOVE; + } + } + } else { + // Two faces, opposite to origin and destination, are viable. + if (tri_edge_test(tdest,tapex,toppo,startpt,searchpt,NULL,0,NULL,NULL)) { + nextmove = ORGMOVE; + } else if (tri_edge_test(tapex,torg,toppo,startpt,searchpt,NULL,0,NULL,NULL)) { + nextmove = DESTMOVE; + } else { + //s = randomnation(2); // 's' is in {0,1}. + if (randomnation(2)) { + nextmove = ORGMOVE; + } else { + nextmove = DESTMOVE; + } + } + } + } else { + if (oriapex < 0) { + // Two faces, opposite to origin and apex, are viable. + if (tri_edge_test(tdest,tapex,toppo,startpt,searchpt,NULL,0,NULL,NULL)) { + nextmove = ORGMOVE; + } else if (tri_edge_test(torg,tdest,toppo,startpt,searchpt,NULL,0,NULL,NULL)) { + nextmove = APEXMOVE; + } else { + //s = randomnation(2); // 's' is in {0,1}. + if (randomnation(2)) { + nextmove = ORGMOVE; + } else { + nextmove = APEXMOVE; + } + } + } else { + // Only the face opposite to origin is viable. + nextmove = ORGMOVE; + } + } + } else { + if (oridest < 0) { + if (oriapex < 0) { + // Two faces, opposite to destination and apex, are viable. + if (tri_edge_test(tapex,torg,toppo,startpt,searchpt,NULL,0,NULL,NULL)) { + nextmove = DESTMOVE; + } else if (tri_edge_test(torg,tdest,toppo,startpt,searchpt,NULL,0,NULL,NULL)) { + nextmove = APEXMOVE; + } else { + //s = randomnation(2); // 's' is in {0,1}. + if (randomnation(2)) { + nextmove = DESTMOVE; + } else { + nextmove = APEXMOVE; + } + } + } else { + // Only the face opposite to destination is viable. + nextmove = DESTMOVE; + } + } else { + if (oriapex < 0) { + // Only the face opposite to apex is viable. + nextmove = APEXMOVE; + } else { + // The point we seek must be on the boundary of or inside this + // tetrahedron. Check for boundary cases. + if (oriorg == 0) { + // Go to the face opposite to origin. + enextesymself(*searchtet); + if (oridest == 0) { + eprevself(*searchtet); // edge oppo->apex + if (oriapex == 0) { + // oppo is duplicated with p. + loc = ONVERTEX; // return ONVERTEX; + break; + } + loc = ONEDGE; // return ONEDGE; + break; + } + if (oriapex == 0) { + enextself(*searchtet); // edge dest->oppo + loc = ONEDGE; // return ONEDGE; + break; + } + loc = ONFACE; // return ONFACE; + break; + } + if (oridest == 0) { + // Go to the face opposite to destination. + eprevesymself(*searchtet); + if (oriapex == 0) { + eprevself(*searchtet); // edge oppo->org + loc = ONEDGE; // return ONEDGE; + break; + } + loc = ONFACE; // return ONFACE; + break; + } + if (oriapex == 0) { + // Go to the face opposite to apex + esymself(*searchtet); + loc = ONFACE; // return ONFACE; + break; + } + loc = INTETRAHEDRON; // return INTETRAHEDRON; + break; + } + } + } + + // Move to the selected face. + if (nextmove == ORGMOVE) { + enextesymself(*searchtet); + } else if (nextmove == DESTMOVE) { + eprevesymself(*searchtet); + } else { + esymself(*searchtet); + } + if (chkencflag) { + // Check if we are walking across a subface. + if (issubface(*searchtet)) { + loc = ENCSUBFACE; + break; + } + } + // Move to the adjacent tetrahedron (maybe a hull tetrahedron). + //fsymself(*searchtet); + //if (oppo(*searchtet) == dummypoint) { + // loc = OUTSIDE; // return OUTSIDE; + // break; + //} + decode(searchtet->tet[searchtet->ver & 3], *searchtet); // fsymself + if (ishulltet(*searchtet)) { + loc = OUTSIDE; // return OUTSIDE; + break; + } + max_visited_tets--; + + // Retreat the three vertices of the base face. + torg = org(*searchtet); + tdest = dest(*searchtet); + tapex = apex(*searchtet); + } // while (true) + + return loc; +} + +//============================================================================// +// // +// splittetrahedron() Split a tetrahedron. // +// // +//============================================================================// + +bool tetgenmesh::split_tetrahedron(triface* splittet, // the tet to be split. + REAL *param, // param[6], it contains the following data + // [0],[1],[2] - the location of the new point + // [3] - the samllest edge length ( = insertion radius) + // [4] - radius-edge ratio + // [5] - its volume + int qflag, // split due to mesh size enforcement. + int chkencflag, + insertvertexflags &ivf) +{ + triface searchtet; + point newpt, bak_pts[4], *ppt; + bool splitflag = false; + int i; + + + insert_point_count++; + if (!b->quiet && (b->refine_progress_ratio > 0.)) { + if (insert_point_count >= report_refine_progress) { + printf(" %ld insertions, added %ld points, %ld tetrahedra in queue.\n", + insert_point_count - last_insertion_count, + points->items - last_point_count, + check_tets_list->objects); + last_point_count = points->items; // update it. + last_insertion_count = insert_point_count; + // The next report event + report_refine_progress *= (1. + b->refine_progress_ratio); + } + } + + makepoint(&newpt, FREEVOLVERTEX); + for (i = 0; i < 3; i++) newpt[i] = param[i]; + + // Locate the new point. Starting from an interior point 'q' of the + // splittet. We perform a walk from q to the 'newpt', stop walking + // either we hit a subface or enter OUTSIDE. + searchtet = *splittet; + ivf.iloc = (int) OUTSIDE; + //ivf.iloc = locate(newpt, &searchtet, 1); // 'chkencflag' = 1. + ivf.iloc = locate_point_walk(newpt, &searchtet, 1); // 'chkencflag' = 1. + + + if ((ivf.iloc == (int) ENCSUBFACE) || (ivf.iloc == (int) OUTSIDE)) { + // The circumcenter 'c' is not visible from 'q' (the interior of the tet). + pointdealloc(newpt); // Do not insert this vertex. + + + ivf.iloc = (int) FENSEDIN; + return splitflag; + } // if (ivf.iloc == (int) ENCSUBFACE) + + // Use Bowyer-Watson algorithm. Preserve subsegments and subfaces; + ivf.bowywat = 3; + ivf.lawson = 2; + ivf.rejflag = 3; // Do check for encroached segments and subfaces. + if (b->metric) { + ivf.rejflag |= 4; // Reject it if it lies in some protecting balls. + } + ivf.chkencflag = (chkencflag & (~3)); // chkencflag; + ivf.sloc = ivf.sbowywat = 0; // No use. + ivf.splitbdflag = 0; // No use (its an interior vertex). + ivf.validflag = 1; + ivf.respectbdflag = 1; + ivf.assignmeshsize = b->metric; + + // Mesh refinement options. + ivf.refineflag = 1; + ivf.refinetet = *splittet; + // get the shortest edge length to the new point. + ivf.smlenflag = useinsertradius; + if (!qflag) { + // Avoid creating an unnecessarily short edge. + ivf.check_insert_radius = useinsertradius; + } else { + ivf.check_insert_radius = 0; + } + ivf.parentpt = NULL; // init. + + if (insertpoint(newpt, &searchtet, NULL, NULL, &ivf)) { + // Vertex is inserted. + st_volref_count++; + if (steinerleft > 0) steinerleft--; + if (useinsertradius) { + // Save the shortest edge between: emin and ivf.smlen + REAL rv = 0.0; // ivf.smlen; + if (param[3] > 0.0) { // The smallest edge length of this tet. + rv = (param[3] < ivf.smlen ? param[3] : ivf.smlen); + } + setpointinsradius(newpt, rv); // ivf.smlen + setpoint2ppt(newpt, ivf.parentpt); + if (ivf.smlen < smallest_insradius) { // ivf.smlen + smallest_insradius = ivf.smlen; + } + } + if (flipstack != NULL) { + flipconstraints fc; + fc.chkencflag = (chkencflag & (~3)); //chkencflag; + fc.enqflag = 2; + lawsonflip3d(&fc); + //unflipqueue->restart(); + } + + if (later_unflip_queue->objects > b->unflip_queue_limit) { + recoverdelaunay(); + } + + return true; + } + + // Point is not inserted. + pointdealloc(newpt); + + if (ivf.iloc == (int) ENCSEGMENT) { + if (!b->nobisect) { //if (!b->nobisect && qflag) { // no -Y + // bakup the vertices of this tet. + ppt = (point *) &(splittet->tet[4]); + for (i = 0; i < 4; i++) bak_pts[i] = ppt[i]; + + bool ref_segment = ((b->cdtrefine & 1) > 0); + + if (ref_segment || qflag) { + for (i = 0; i < encseglist->objects; i++) { + //face *paryseg = (face *) fastlookup(encseglist, i); + badface *bf = (badface *) fastlookup(encseglist, i); + if ((bf->ss.sh == NULL) || + (sorg(bf->ss) != bf->forg) || + (sdest(bf->ss) != bf->fdest)) { + continue; // Skip this segment. + } + int tmp_iloc; + if (split_segment(&(bf->ss), NULL, param, qflag, (chkencflag | 3), &tmp_iloc)) { + // A Steienr point is inserted on a segment. + // Check if this tet is split as well. + if ((splittet->tet == NULL) || (splittet->tet[4] == NULL)) { + splitflag = true; // The tet is split as well. + } else { + ppt = (point *) &(splittet->tet[4]); + if ((ppt[0] != bak_pts[0]) || + (ppt[1] != bak_pts[1]) || + (ppt[2] != bak_pts[2]) || + (ppt[3] != bak_pts[3])) { + splitflag = true; // The tet is split as well. + } + } + if (splitflag) { + break; // This tetrahedron is split. + } + } + } // i + } // if (ref_segment ||qflag) + encseglist->restart(); + // Some segments may need to be repaired. + if (badsubsegs->items > 0) { + //repairencsegs(param, qflag, (chkencflag | 3)); // Queue new enroached subsegments and subfaces. + repairencsegs(param, 0, (chkencflag | 3)); // qflag = 0 + } + // Some subfaces may need to be repaired. + if (badsubfacs->items > 0) { + //repairencfacs(param, qflag, (chkencflag | 2)); // Queue new encroached subfaces. + repairencfacs(param, 0, (chkencflag | 2)); // qflag = 0 + if (unsplit_subfaces->objects > 0) { + unsplit_subfaces->restart(); // clear this list; + } + } + // Check if this tet is split as well. + if ((splittet->tet == NULL) || (splittet->tet[4] == NULL)) { + splitflag = true; // The tet is split as well. + } else { + ppt = (point *) &(splittet->tet[4]); + if ((ppt[0] != bak_pts[0]) || + (ppt[1] != bak_pts[1]) || + (ppt[2] != bak_pts[2]) || + (ppt[3] != bak_pts[3])) { + splitflag = true; // The tet is split as well. + } + } + } else { // if (!b->nobisect) { // no -Y + encseglist->restart(); + } + } else if (ivf.iloc == (int) ENCSUBFACE) { + if (!b->nobisect) { //if (!b->nobisect && qflag) { // no -Y + // bakup the vertices of this tet. + ppt = (point *) &(splittet->tet[4]); + for (i = 0; i < 4; i++) bak_pts[i] = ppt[i]; + + bool ref_subface = ((b->cdtrefine & 2) > 0); + + if (ref_subface || qflag) { + // This rejected Steiner point may encroach upon more than one subfaces. + // We split the one which contains the projection of this rejected + // Steiner point. Moreover, there may be many subfaces. + triface adjtet; + point pa, pb, pc, toppo; + REAL prjpt[3], ori; + int scount = 0; + int t1ver; + + // Clean the bad radius-edge ratio, so split_subface() knows that + // the split of this subface is due to a rejected tet ccenter. + param[4] = 0.0; + + for (i = 0; i < encshlist->objects; i++) { + badface *bface = (badface *) fastlookup(encshlist, i); + // This subface may be split. + if ((bface->ss.sh == NULL) || + (sorg(bface->ss) != bface->forg) || + (sdest(bface->ss) != bface->fdest) || + (sapex(bface->ss) != bface->fapex)) { + continue; + } + stpivot(bface->ss, adjtet); + if (ishulltet(adjtet)) { + fsymself(adjtet); + } + toppo = oppo(adjtet); // used by orient3d() + //assert(toppo != dummypoint); + pa = org(adjtet); + pb = dest(adjtet); + pc = apex(adjtet); + projpt2face(param, pa, pb, pc, prjpt); + ori = orient3d(pa, pb, toppo, prjpt); + if (ori >= 0) { + ori = orient3d(pb, pc, toppo, prjpt); + if (ori >= 0) { + ori = orient3d(pc, pa, toppo, prjpt); + if (ori >= 0) { + scount++; + // Found such a subface, try to split it. + int tmp_iloc; + split_subface(&(bface->ss), NULL, bface->cent, param, qflag, + chkencflag | 2, &tmp_iloc); + // This subface may not be split while some encroached subsegments + // might be split. + // Check if this tet is split as well. + if ((splittet->tet == NULL) || (splittet->tet[4] == NULL)) { + splitflag = true; // The tet is split as well. + } else { + ppt = (point *) &(splittet->tet[4]); + if ((ppt[0] != bak_pts[0]) || + (ppt[1] != bak_pts[1]) || + (ppt[2] != bak_pts[2]) || + (ppt[3] != bak_pts[3])) { + splitflag = true; // The tet is split as well. + } + } + if (splitflag) { + break; + } + } // if (ori >= 0) + } + } + } // i + if (scount == 0) { + // Not such subface is found! This can happen due to the existence + // of small angles and non-Delaunay elements. + // Select an encroached subface and split it. + for (i = 0; i < encshlist->objects; i++) { + badface *bface = (badface *) fastlookup(encshlist, i); + if ((bface->ss.sh == NULL) || + (sorg(bface->ss) != bface->forg) || + (sdest(bface->ss) != bface->fdest) || + (sapex(bface->ss) != bface->fapex)) { + continue; + } + //if (get_subface_ccent(&(bface->ss), ccent)) { + int tmp_iloc; + split_subface(&(bface->ss), NULL, bface->cent, param, qflag, + chkencflag | 2, &tmp_iloc); + // Check if this tet is split as well. + if ((splittet->tet == NULL) || (splittet->tet[4] == NULL)) { + splitflag = true; // The tet is split as well. + } else { + ppt = (point *) &(splittet->tet[4]); + if ((ppt[0] != bak_pts[0]) || + (ppt[1] != bak_pts[1]) || + (ppt[2] != bak_pts[2]) || + (ppt[3] != bak_pts[3])) { + splitflag = true; // The tet is split as well. + } + } + if (splitflag) { + break; // This tetrahedron is split. + } + } + } // if (scount == 0) + } // if (ref_subface) + encshlist->restart(); // Clear the list. + // Some subfaces may need to be repaired. + if (badsubfacs->items > 0) { + //repairencfacs(param, qflag, (chkencflag | 2)); // Queue new encroached subfaces. + repairencfacs(param, 0, (chkencflag | 2)); // qflag = 0 + if (unsplit_subfaces->objects > 0) { + unsplit_subfaces->restart(); // clear this list. + } + } + // Check if this tet is split as well. + if ((splittet->tet == NULL) || (splittet->tet[4] == NULL)) { + splitflag = true; // The tet is split as well. + } else { + ppt = (point *) &(splittet->tet[4]); + if ((ppt[0] != bak_pts[0]) || + (ppt[1] != bak_pts[1]) || + (ppt[2] != bak_pts[2]) || + (ppt[3] != bak_pts[3])) { + splitflag = true; // The tet is split as well. + } + } + } else { // if (!b->nobisect) + encshlist->restart(); + } + } + + return splitflag; +} + +//============================================================================// +// // +// repairbadtets() Repair bad quality tetrahedra. // +// // +//============================================================================// + +void tetgenmesh::repairbadtets(REAL queratio, int chkencflag) +{ + triface *bface, *quetet, *last_quetet; + triface checktet; + REAL param[6] = {0.,}; + int qflag = 0; + int i; + + while ((badtetrahedrons->items > 0) || (check_tets_list->objects > 0)) { + + if (badtetrahedrons->items > 0) { + badtetrahedrons->traversalinit(); + bface = (triface *) badtetrahedrons->traverse(); + while (bface != NULL) { + check_tets_list->newindex((void **) &quetet); + *quetet = *bface; + bface = (triface *) badtetrahedrons->traverse(); + } + badtetrahedrons->restart(); + } + + // Stop if we have used the desried number of Steiner points. + if (steinerleft == 0) break; + // Stop if the desried number of tetrahedra is reached. + if ((elem_limit > 0) && + ((tetrahedrons->items - hullsize) > elem_limit)) break; + + + // Randomly select a tet to split. + i = rand() % check_tets_list->objects; + quetet = (triface *) fastlookup(check_tets_list, i); + checktet = *quetet; + + // Fill the current position by the last tet in the list. + i = check_tets_list->objects - 1; + last_quetet = (triface *) fastlookup(check_tets_list, i); + *quetet = *last_quetet; + check_tets_list->objects--; + + if (!isdeadtet(checktet)) { + if (marktest2ed(checktet)) { + unmarktest2(checktet); + //if (check_tetrahedron(&checktet, param, qflag)) { + if (checktet4split(&checktet, param, qflag)) { + bool splitflag = false; + insertvertexflags ivf; + splitflag = split_tetrahedron(&checktet, param, qflag, chkencflag, ivf); + if (!splitflag) { + if (qflag || (param[4] > queratio)) { // radius-edge ratio + badface *bt = NULL; + unsplit_badtets->newindex((void **) &bt); + bt->init(); + bt->tt = checktet; + bt->forg = org(checktet); + bt->fdest = dest(checktet); + bt->fapex = apex(checktet); + bt->foppo = oppo(checktet); + for (i = 0; i < 6; i++) bt->cent[i] = param[i]; + bt->key = (double) qflag; + } + } + } + } + } // if (!isdeadtet(checktet)) { + + } // while ((badtetrahedrons->items > 0) || (check_tets_list->objects > 0)) + + if (check_tets_list->objects > 0) { + if (steinerleft == 0) { + if (b->verbose) { + printf("The desired number of Steiner points is reached.\n"); + } + } else if (elem_limit > 0) { + if (b->verbose) { + printf("The desired number %ld of elements is reached.\n", elem_limit); + } + } + //split_tets_pool->restart(); // Clear this pool. + // Unmark all unchecked tetrahedra. + for (i = 0; i < check_tets_list->objects; i++) { + quetet = (triface *) fastlookup(check_tets_list, i); + if (!isdeadtet(*quetet)) { + unmarktest2(*quetet); + } + } + check_tets_list->restart(); + } +} + +//============================================================================// +// // +// delaunayrefinement() Refine the mesh by Delaunay refinement. // +// // +//============================================================================// + +void tetgenmesh::delaunayrefinement() +{ + triface checktet; + face checksh; + face checkseg; + long steinercount; + REAL param[6] = {0., 0., 0., 0., 0., 0.}; + int qflag = 0; + int chkencflag = 0; + int i; + + long bak_segref_count, bak_facref_count, bak_volref_count; + + if (!b->quiet) { + printf("Refining mesh...\n"); + } + + if (b->verbose) { + printf(" Min radius-edge ratio = %g.\n", b->minratio); + if (b->mindihedral > 0.) { + printf(" Min dihedral angle = %g.\n", b->mindihedral); + } + if (b->fixedvolume) { + printf(" Max tet volume = %g.\n", b->maxvolume); + } + //printf(" Min Edge length = %g.\n", b->minedgelength); + } + // Used in locate_point_on_surface(); + cos_facet_separate_ang_tol = cos(b->facet_separate_ang_tol/180.*PI); // -p/# + // Used in function is_collinear_at(mid, left, right); + cos_collinear_ang_tol = cos(b->collinear_ang_tol/180.*PI); // -p///# + + // The cosine value of the min dihedral angle (-q/#) for tetrahedra. + cosmindihed = cos(b->mindihedral / 180.0 * PI); + + steinerleft = b->steinerleft; // Upperbound of # Steiner points (by -S#). + if (steinerleft > 0) { + // Check if we've already used up the given number of Steiner points. + steinercount = st_segref_count + st_facref_count + st_volref_count; + if (steinercount < steinerleft) { + steinerleft -= steinercount; + } else { + if (!b->quiet) { + printf("\nWarning: "); + printf("The desired number of Steiner points (%d) has reached.\n\n", + b->steinerleft); + } + return; // No more Steiner points. + } + } + + if (b->refine && (b->elem_growth_ratio > 0.0)) { // -r# + int ntet = in->numberoftetrahedra; // tetrahedrons->items - hullsize; + elem_limit = ntet * (1.0 + b->elem_growth_ratio); + } + + if (b->refine_progress_ratio > 0) { // -r/# default is 0.333 + insert_point_count = 0l; + last_insertion_count = 0l; + last_point_count = points->items; + report_refine_progress = points->items * (1. + b->refine_progress_ratio); + } + + if (!b->nobisect) { // no -Y. + if (segmentendpointslist == NULL) { + makesegmentendpointsmap(); // create ridge_vertex-to-segment map. + } + create_segment_info_list(); + makefacetverticesmap(); // create ridge_vertex-to-facet map. + create_segment_facet_map(); // vreate segment-to-facet map. + } + + + // Begin of memory allocation =============================================== + // Initialize the pools and priority queues. + long bls = b->shellfaceperblock; + long blt = b->tetrahedraperblock; + + badsubsegs = new memorypool(sizeof(face), 256, sizeof(void *), 0); + badsubfacs = new memorypool(sizeof(face), 256, sizeof(void *), 0); + badtetrahedrons = new memorypool(sizeof(triface), blt, sizeof(void *), 0); + + split_segments_pool = new memorypool(sizeof(badface), bls, sizeof(void *), 0); + split_subfaces_pool = new memorypool(sizeof(badface), bls, sizeof(void *), 0); + + long est_size = blt; + int log2objperblk = 0; + while (est_size >>= 1) log2objperblk++; + if (log2objperblk < 10) log2objperblk = 10; // At least 1024. + + check_tets_list = new arraypool(sizeof(triface), log2objperblk); + + unsplit_segments = new arraypool(sizeof(badface), 10); + unsplit_subfaces = new arraypool(sizeof(badface), 10); + unsplit_badtets = new arraypool(sizeof(badface), 10); + + stack_enc_segments = stack_enc_subfaces = NULL; + + for (i = 0; i < 64; i++) { + queuefront[i] = NULL; + } + firstnonemptyq = -1; + recentq = -1; + + encseglist = new arraypool(sizeof(badface), 8); + encshlist = new arraypool(sizeof(badface), 8); + // End of memory allocation ================================================= + + + // with -r and an .elem file ================================================ + if (b->refine && (in->refine_elem_list != NULL)) { + if (b->verbose) { + printf(" Refining a list of given elements.\n"); + } + //assert(b->varvolume > 0); // -a option must be used. + chkencflag = 4; // Check bad tetrahedra. + steinercount = points->items; + + REAL queratio = b->minratio > 2. ? b->minratio : 2.0; + queratio *= 2.0; // queratio; // increase this value. + + // Create a map from indices to points. + point *idx2verlist; + makeindex2pointmap(idx2verlist); + + int *elelist = in->refine_elem_list; + int elem; + + for (elem = 0; elem < in->numberofrefineelems; elem++) { + point p1 = idx2verlist[elelist[elem*4]]; + point p2 = idx2verlist[elelist[elem*4+1]]; + point p3 = idx2verlist[elelist[elem*4+2]]; + point p4 = idx2verlist[elelist[elem*4+3]]; + + if (!get_tet(p1, p2, p3, p4, &checktet)) { + continue; + } + + REAL volume_limit; + if (in->refine_elem_vol_list != NULL) { + volume_limit = in->refine_elem_vol_list[i]; + } else { + point *ppt = (point *) &(checktet.tet[4]); + REAL volume = orient3dfast(ppt[1], ppt[0], ppt[2], ppt[3]) / 6.; + volume_limit = volume / 3.; + } + setvolumebound(checktet.tet, volume_limit); + + //assert(check_tets_list->objects == 0l); + triface *quetet; + marktest2(checktet); + check_tets_list->newindex((void **) &quetet); + *quetet = checktet; + + int maxiter = 2, iter; + + for (iter = 0; iter < maxiter; iter++) { + repairbadtets(queratio, chkencflag); + + if (later_unflip_queue->objects > 0l) { + recoverdelaunay(); + } + + // Split unsplit tetrahedra + long badtetcount = 0, splitcount = 0; + int j; + + for (i = 0; i < unsplit_badtets->objects; i++) { + badface *bt = (badface *) fastlookup(unsplit_badtets, i); + if ((bt->tt.tet != NULL) && + ( org(bt->tt) == bt->forg ) && + (dest(bt->tt) == bt->fdest) && + (apex(bt->tt) == bt->fapex) && + (oppo(bt->tt) == bt->foppo)) { + + if (steinerleft == 0) break; + if (elem_limit > 0) { + if ((tetrahedrons->items - hullsize) > elem_limit) { + break; + } + } + + // Count a live tet. + badtetcount++; + insertvertexflags ivf; + qflag = (int) bt->key; + point *ppt = (point *) &(bt->tt.tet[4]); + for (j = 0; j < 3; j++) { + param[j] = (ppt[0][j]+ppt[1][j]+ppt[2][j]+ppt[3][j]) / 4.0; + } + for (; j < 6; j++) { + param[j] = bt->cent[j]; + } + if (split_tetrahedron(&bt->tt, param, qflag, chkencflag, ivf)) { + splitcount++; + } + + if (badtetrahedrons->items > 0) { + // Push new bad quality tetrahedron into queue. + badtetrahedrons->traversalinit(); + triface *bface = (triface *) badtetrahedrons->traverse(); + while (bface != NULL) { + check_tets_list->newindex((void **) &quetet); + *quetet = *bface; + bface = (triface *) badtetrahedrons->traverse(); + } + badtetrahedrons->restart(); + } + } + } // i + + unsplit_badtets->restart(); + + if (splitcount == 0) break; + } // iter + + if (check_tets_list->objects > 0) { + // Clean the list. + for (i = 0; i < check_tets_list->objects; i++) { + quetet = (triface *) fastlookup(check_tets_list, i); + if (!isdeadtet(*quetet)) { + unmarktest2(*quetet); + } + } + check_tets_list->restart(); + } + + if (steinerleft == 0) break; + if (elem_limit > 0) { + if ((tetrahedrons->items - hullsize) > elem_limit) { + break; + } + } + + } // elem + + if (b->verbose) { + printf(" Added %ld Steiner points.\n", points->items - steinercount); + } + delete [] idx2verlist; + } // if (b->refine && (in->refine_elem_list != NULL)) + // with -r and an .elem file ================================================ + + bool force_quit_refinement = false; + + if (steinerleft == 0) { + force_quit_refinement = true; + } else if (elem_limit > 0) { + if ((tetrahedrons->items - hullsize) > elem_limit) { + force_quit_refinement = true; + } + } + + if (!b->nobisect) { // no -Y + bool ref_segment = ((b->cdtrefine & 1) > 0); // -D1, -D3, -D5, or -D7 + + if (ref_segment && !force_quit_refinement) { + if (b->verbose) { + printf(" Splitting encroached subsegments.\n"); + } + + chkencflag = 1; // Only check encroaching subsegments. + steinercount = points->items; + + // Add all segments into the pool. + subsegs->traversalinit(); + checkseg.sh = shellfacetraverse(subsegs); + while (checkseg.sh != (shellface *) NULL) { + //enqueuesubface(badsubsegs, &checkseg); + point encpt = NULL; + if (check_enc_segment(&checkseg, &encpt)) { + badface *bf = (badface *) split_segments_pool->alloc(); + bf->init(); + bf->ss = checkseg; + bf->forg = sorg(checkseg); + bf->fdest = sdest(checkseg); + bf->noppo = encpt; + // Push it into stack. + bf->nextitem = stack_enc_segments; + stack_enc_segments = bf; + } + checkseg.sh = shellfacetraverse(subsegs); + } + + // Split all encroached segments. + for (i = 0; i < 6; i++) param[i] = 0.0; + qflag = 0; + + repairencsegs(param, qflag, chkencflag); + + if (b->verbose) { + printf(" Added %ld Steiner points.\n", points->items - steinercount); + } + + if (later_unflip_queue->objects > 0l) { + recoverdelaunay(); + } + } // if (ref_segment) + + bool ref_surface = ((b->cdtrefine & 2) > 0); // -D2, -D3, or -D7 + + if (ref_surface && !force_quit_refinement) { + if (b->verbose) { + printf(" Splitting encroached and bad quality subfaces.\n"); + } + + chkencflag = 2; // only check encroaching subfaces. + steinercount = points->items; + bak_segref_count = st_segref_count; + bak_facref_count = st_facref_count; + + // Add all subfaces into the pool. + REAL ccent[3], radius; + point encpt = NULL; + + subfaces->traversalinit(); + checksh.sh = shellfacetraverse(subfaces); + while (checksh.sh != (shellface *) NULL) { + //enqueuesubface(badsubfacs, &checksh); + if (get_subface_ccent(&checksh, ccent)) { + encpt = NULL; + for (i = 3; i < 6; i++) param[i] = 0.0; + if (check_enc_subface(&checksh, &encpt, ccent, &radius)) { + enqueue_subface(&checksh, encpt, ccent, param); + } else { + if (check_subface(&checksh, ccent, radius, param)) { + enqueue_subface(&checksh, NULL, ccent, param); + } + } + } else { + terminatetetgen(this, 2); // report a bug. + } + checksh.sh = shellfacetraverse(subfaces); + } + + // check_enc_subface() may find some non-Delaunay faces. + if (flippool->items > 0) { + flipconstraints fc; + fc.chkencflag = chkencflag; + fc.enqflag = 2; + lawsonflip3d(&fc); + } + + // Split all encroached subfaces. + for (i = 0; i < 6; i++) param[i] = 0.0; + qflag = 0; + + int maxiter = 3, iter; + + for (iter = 0; iter < maxiter; iter++) { + + if (b->verbose > 1) { + printf(" iter = %d\n", iter+1); + } + long iter_steinercount = points->items; + long iter_segref_count = st_segref_count; + long iter_facref_count = st_facref_count; + + repairencfacs(param, qflag, chkencflag); + + if (b->verbose > 1) { + printf(" Added %ld (%ld,%ld) Steiner points.\n", + points->items-iter_steinercount, + st_segref_count-iter_segref_count, + st_facref_count-iter_facref_count); + } + + if (unsplit_subfaces->objects > 0) { + if (b->verbose > 1) { + printf(" splitting %ld unsplit subfaces\n", unsplit_subfaces->objects); + } + int scount = 0; // Count the split subfaces. + + for (i = 0; i < unsplit_subfaces->objects; i++) { + badface *bf = (badface *) fastlookup(unsplit_subfaces, i); + if ((bf->ss.sh != NULL) && + ( sorg(bf->ss) == bf->forg) && + (sdest(bf->ss) == bf->fdest) && + (sapex(bf->ss) == bf->fapex)) { + // Try to split it in its barycenter. + int iloc, j; + for (j = 0; j < 3; j++) { + ccent[j] = (bf->forg[j] + bf->fdest[j] + bf->fapex[j]) / 3.; + } + for (j = 3; j < 6; j++) param[j] = bf->cent[j]; + encpt = bf->noppo; // The encroaching vertex. + if (split_subface(&bf->ss, encpt, ccent, param, qflag, chkencflag, &iloc)) { + scount++; + } + } + } // i + + unsplit_subfaces->restart(); + + if (b->verbose > 1) { + printf(" Split %d subfaces.\n", scount); + } + } else { + break; // no unsplit subfaces. + } // if (unsplit_subfaces->objects > 0) + } // iter + + if (b->verbose) { + printf(" Added %ld (%ld,%ld) Steiner points.\n", + points->items-steinercount, st_segref_count-bak_segref_count, + st_facref_count-bak_facref_count); + } + + if (badsubfacs->items > 0) { + // Clean this pool. + badsubfacs->traversalinit(); + face *bface = (face *) badsubfacs->traverse(); + while (bface != NULL) { + if ((bface->sh != NULL) && (bface->sh[3] != NULL)) { + if (smarktest2ed(*bface)) { + sunmarktest2(*bface); + } + } + bface = (face *) badsubfacs->traverse(); + } + badsubfacs->restart(); + } + + if (later_unflip_queue->objects > 0l) { + recoverdelaunay(); + } + } // if (ref_subface) + } // if (!b->nobisect) + + if (((b->cdtrefine & 4) > 0) && !force_quit_refinement) { // -D4, -D5, or -D7 + // Begin of adding Steiner points in volume =============================== + + // A Steiner point can be added only if it does not encroach upon any + // boundary segment or subface. + if (b->verbose) { + printf(" Splitting bad quality tets.\n"); + } + + for (i = 0; i < 6; i++) param[i] = 0.0; + qflag = 0; + + // Add all tetrahedra (no hull tets) into the pool. + triface *quetet; + tetrahedrons->traversalinit(); + checktet.tet = tetrahedrontraverse(); + while (checktet.tet != NULL) { + marktest2(checktet); + check_tets_list->newindex((void **) &quetet); + *quetet = checktet; + checktet.tet = tetrahedrontraverse(); + } + + + chkencflag = 4; // Check bad tetrahedra. + + REAL queratio = b->minratio > 2. ? b->minratio : 2.0; + queratio *= 2.0; // queratio; // increase this value. + + int maxiter = 3, iter; + + for (iter = 0; iter < maxiter; iter++) { + steinercount = points->items; + bak_segref_count = st_segref_count; + bak_facref_count = st_facref_count; + bak_volref_count = st_volref_count; + + if (b->verbose > 1) { + printf(" iter = %d: queratio = %g\n", iter + 1, queratio); + } + + // Split all bad quality tetrahedra. + repairbadtets(queratio, chkencflag); + + if (b->verbose) { + printf(" Added %ld (%ld,%ld,%ld) Steiner points.\n", + points->items - steinercount, + st_segref_count - bak_segref_count, + st_facref_count - bak_facref_count, + st_volref_count - bak_volref_count); + } + + + if (later_unflip_queue->objects > 0l) { + recoverdelaunay(); + } + + if (unsplit_badtets->objects == 0) break; + + //queratio *= 2.0; // queratio; // increase this value. + + // Split unsplit tetrahedra + long badtetcount = 0, splitcount = 0; + int j; + + for (i = 0; i < unsplit_badtets->objects; i++) { + badface *bt = (badface *) fastlookup(unsplit_badtets, i); + if ((bt->tt.tet != NULL) && + ( org(bt->tt) == bt->forg ) && + (dest(bt->tt) == bt->fdest) && + (apex(bt->tt) == bt->fapex) && + (oppo(bt->tt) == bt->foppo)) { + if (steinerleft == 0) break; + if (elem_limit > 0) { + if ((tetrahedrons->items - hullsize) > elem_limit) { + break; + } + } + + // Count a live tet. + badtetcount++; + insertvertexflags ivf; + qflag = (int) bt->key; + point *ppt = (point *) &(bt->tt.tet[4]); + for (j = 0; j < 3; j++) { + param[j] = (ppt[0][j]+ppt[1][j]+ppt[2][j]+ppt[3][j]) / 4.0; + } + for (; j < 6; j++) { + param[j] = bt->cent[j]; + } + if (split_tetrahedron(&bt->tt, param, qflag, chkencflag, ivf)) { + splitcount++; + } + if (badtetrahedrons->items > 0) { + // Push new bad quality tetrahedron into queue. + badtetrahedrons->traversalinit(); + triface *bface = (triface *) badtetrahedrons->traverse(); + while (bface != NULL) { + check_tets_list->newindex((void **) &quetet); + *quetet = *bface; + bface = (triface *) badtetrahedrons->traverse(); + } + badtetrahedrons->restart(); + } + } + } // i + unsplit_badtets->restart(); + + + if (splitcount == 0) break; + } // iter + + if (check_tets_list->objects > 0) { + // Unmark all unchecked tetrahedra. + for (i = 0; i < check_tets_list->objects; i++) { + quetet = (triface *) fastlookup(check_tets_list, i); + if (!isdeadtet(*quetet)) { + unmarktest2(*quetet); + } + } + check_tets_list->restart(); + } + + // End of adding Steiner points in volume ================================= + } // if ((b->cdtrefine & 4) > 0) { + + + delete encseglist; + delete encshlist; + encseglist = NULL; + encshlist = NULL; + + totalworkmemory += (badsubsegs->maxitems * badsubsegs->itembytes); + delete badsubsegs; + badsubsegs = NULL; + totalworkmemory += (badsubfacs->maxitems * badsubfacs->itembytes); + delete badsubfacs; + badsubfacs = NULL; + totalworkmemory += (split_subfaces_pool->maxitems * split_subfaces_pool->itembytes); + delete split_subfaces_pool; + split_subfaces_pool = NULL; + delete split_segments_pool; + split_segments_pool = NULL; + delete unsplit_segments; + delete unsplit_subfaces; + unsplit_segments = unsplit_subfaces = NULL; + + totalworkmemory += (badtetrahedrons->maxitems*badtetrahedrons->itembytes); + delete badtetrahedrons; + badtetrahedrons = NULL; + totalworkmemory += (check_tets_list->totalmemory); + delete check_tets_list; + check_tets_list = NULL; + delete unsplit_badtets; + unsplit_badtets = NULL; +} + +// // +// // +//== refine_cxx ==============================================================// + +//== optimize_cxx ============================================================// +// // +// // + +//============================================================================// +// // +// lawsonflip3d() A three-dimensional Lawson's algorithm. // +// // +//============================================================================// + +long tetgenmesh::lawsonflip3d(flipconstraints *fc) +{ + triface fliptets[5], neightet, hulltet; + face checksh, casingout; + badface *popface, *bface; + point pd, pe, *pts; + REAL sign, ori; + REAL vol, len3; + long flipcount, totalcount = 0l; + long sliver_peels = 0l; + int t1ver; + int i; + + + while (flippool->items != 0l) { + if (b->verbose > 2) { + printf(" Lawson flip %ld faces.\n", flippool->items); + } + flipcount = 0l; + + while (flipstack != (badface *) NULL) { + // Pop a face from the stack. + popface = flipstack; + fliptets[0] = popface->tt; + flipstack = flipstack->nextitem; // The next top item in stack. + flippool->dealloc((void *) popface); + + // Skip it if it is a dead tet (destroyed by previous flips). + if (isdeadtet(fliptets[0])) continue; + // Skip it if it is not the same tet as we saved. + if (!facemarked(fliptets[0])) continue; + + unmarkface(fliptets[0]); + + + if (ishulltet(fliptets[0])) continue; + + fsym(fliptets[0], fliptets[1]); + if (ishulltet(fliptets[1])) { + if (nonconvex) { + // Check if 'fliptets[0]' it is a hull sliver. + tspivot(fliptets[0], checksh); + for (i = 0; i < 3; i++) { + if (!isshsubseg(checksh)) { + spivot(checksh, casingout); + //assert(casingout.sh != NULL); + if (sorg(checksh) != sdest(casingout)) sesymself(casingout); + stpivot(casingout, neightet); + if (neightet.tet == fliptets[0].tet) { + // Found a hull sliver 'neightet'. Let it be [e,d,a,b], where + // [e,d,a] and [d,e,b] are hull faces. + edestoppo(neightet, hulltet); // [a,b,e,d] + fsymself(hulltet); // [b,a,e,#] + if (oppo(hulltet) == dummypoint) { + pe = org(neightet); + if ((pointtype(pe) == FREEFACETVERTEX) || + (pointtype(pe) == FREESEGVERTEX)) { + removevertexbyflips(pe); + } + } else { + eorgoppo(neightet, hulltet); // [b,a,d,e] + fsymself(hulltet); // [a,b,d,#] + if (oppo(hulltet) == dummypoint) { + pd = dest(neightet); + if ((pointtype(pd) == FREEFACETVERTEX) || + (pointtype(pd) == FREESEGVERTEX)) { + removevertexbyflips(pd); + } + } else { + // Perform a 3-to-2 flip to remove the sliver. + // To avoid creating an "inverted" subface in the surface + // Check the normals of the two new subfaces, they must + // not be opposite. + point chk_pe = org(neightet); + point chk_pd = dest(neightet); + point chk_pa = apex(neightet); + point chk_pb = oppo(neightet); + REAL n1[3], n2[3]; + facenormal(chk_pa, chk_pb, chk_pe, n1, 1, NULL); + facenormal(chk_pb, chk_pa, chk_pd, n2, 1, NULL); + double dot = n1[0]*n2[0]+n1[1]*n2[1]+n1[2]*n2[2]; + if (dot > 0.) { + fliptets[0] = neightet; // [e,d,a,b] + fnext(fliptets[0], fliptets[1]); // [e,d,b,c] + fnext(fliptets[1], fliptets[2]); // [e,d,c,a] + flip32(fliptets, 1, fc); + // Update counters. + flip32count--; + flip22count--; + sliver_peels++; + if (fc->remove_ndelaunay_edge) { + // Update the volume (must be decreased). + //assert(fc->tetprism_vol_sum <= 0); + tetprism_vol_sum += fc->tetprism_vol_sum; + fc->tetprism_vol_sum = 0.0; // Clear it. + } + } // if (dot. > 0) + } + } + break; + } // if (neightet.tet == fliptets[0].tet) + } // if (!isshsubseg(checksh)) + senextself(checksh); + } // i + } // if (nonconvex) + continue; + } + + if (checksubfaceflag) { + // Do not flip if it is a subface. + if (issubface(fliptets[0])) continue; + } + + // Test whether the face is locally Delaunay or not. + pts = (point *) fliptets[1].tet; + sign = insphere_s(pts[4], pts[5], pts[6], pts[7], oppo(fliptets[0])); + + if (sign < 0) { + // A non-Delaunay face. Try to flip it. + pd = oppo(fliptets[0]); + pe = oppo(fliptets[1]); + + // Use the length of the edge [d,e] as a reference to determine + // a nearly degenerated new tet. + len3 = distance(pd, pe); + len3 = (len3 * len3 * len3); + int round_flag = 0; // [2017-10-20] + // Check the convexity of its three edges. Stop checking either a + // locally non-convex edge (ori < 0) or a flat edge (ori = 0) is + // encountered, and 'fliptet' represents that edge. + for (i = 0; i < 3; i++) { + ori = orient3d(org(fliptets[0]), dest(fliptets[0]), pd, pe); + if (ori > 0) { + // Avoid creating a nearly degenerated new tet at boundary. + // Re-use fliptets[2], fliptets[3]; + esym(fliptets[0], fliptets[2]); + esym(fliptets[1], fliptets[3]); + if (issubface(fliptets[2]) || issubface(fliptets[3])) { + vol = orient3dfast(org(fliptets[0]), dest(fliptets[0]), pd, pe); + if ((fabs(vol) / len3) < b->epsilon) { + ori = 0.0; // Do rounding. + round_flag = 1; // [2017-10-20] + } + } + } // Rounding check + if (ori <= 0) break; + enextself(fliptets[0]); + eprevself(fliptets[1]); + } + + if (ori > 0) { + // A 2-to-3 flip is found. + // [0] [a,b,c,d], + // [1] [b,a,c,e]. no dummypoint. + flip23(fliptets, 0, fc); + flipcount++; + if (fc->remove_ndelaunay_edge) { + // Update the volume (must be decreased). + //assert(fc->tetprism_vol_sum <= 0); + tetprism_vol_sum += fc->tetprism_vol_sum; + fc->tetprism_vol_sum = 0.0; // Clear it. + } + continue; + } else { // ori <= 0 + // The edge ('fliptets[0]' = [a',b',c',d]) is non-convex or flat, + // where the edge [a',b'] is one of [a,b], [b,c], and [c,a]. + if (checksubsegflag) { + // Do not flip if it is a segment. + if (issubseg(fliptets[0])) continue; + } + // Count the number of interior subfaces for a valid 2-2 flip. + int scount = 0; + // Check if there are three or four tets sharing at this edge. + esymself(fliptets[0]); // [b,a,d,c] + for (i = 0; i < 3; i++) { + if (issubface(fliptets[i])) scount++; + fnext(fliptets[i], fliptets[i+1]); + } + if (fliptets[3].tet == fliptets[0].tet) { + // A 3-2 flip is found. "scount" must be either 0 or 2. + if (scount == 1) { + // This can happen during the boundary recovery. The adjacent + // subface is either missing or not recovered yet. + continue; + } else if (scount == 2) { + // Valid if a 2-2 flip is possible. + for (i = 0; i < 3; i++) { + if (!issubface(fliptets[i])) break; + } + // Assume fliptets[i] is the tet (b,a,c,e). The two subfaces are + // fliptets[(i+1)%3] (b,a,e,d) and fliptets[(i+2)%3] (b,a,d,c). + // A 2-2 flip is possible if the two faces (d,e,a) and (e,d,b) + // are not subfaces. + triface face1, face2; + neightet = fliptets[(i+1)%3]; // (b,a,e,d) + enext(neightet, face1); + esymself(face1); // (e,a,d) + eprev(neightet, face2); + esymself(face2); // (b,e,d) + if (issubface(face1) || issubface(face2)) { + continue; + } + } + // A 3-to-2 flip is found. (No hull tet.) + flip32(fliptets, 0, fc); + flipcount++; + if (fc->remove_ndelaunay_edge) { + // Update the volume (must be decreased). + //assert(fc->tetprism_vol_sum <= 0); + tetprism_vol_sum += fc->tetprism_vol_sum; + fc->tetprism_vol_sum = 0.0; // Clear it. + } + continue; + } else { + // There are more than 3 tets at this edge. + fnext(fliptets[3], fliptets[4]); + if (fliptets[4].tet == fliptets[0].tet) { + if (ori != 0.) { + if (nonconvex) { + if (apex(fliptets[3]) == dummypoint) { + // This edge is locally non-convex on the hull. + // It can be removed by a 4-to-4 flip. + ori = 0; + round_flag = 1; + } + } // if (nonconvex) + } + if (ori == 0) { + // A 4-to-4 flip is found. (Two hull tets may be involved.) + // Current tets in 'fliptets': + // [0] [b,a,d,c] (d may be newpt) + // [1] [b,a,c,e] + // [2] [b,a,e,f] (f may be dummypoint) + // [3] [b,a,f,d] + // There are exactly 4 tets at this edge. + // Moreover, a,b,e,d are coplanar. This 4-4 flip will replace + // edge (a,b) to edge (d,e). + + // A valid 2-2 flip is when both faces (a,b,d) and (a,b,e) are + // subfaces, and (a,b,c) and (a,b,f) are not subfaces. + if (issubface(fliptets[0])) { // (a,b,d) + if (!issubface(fliptets[2])) { // (a,b,e) + continue; // not valid 2-2 flip. + } + if (issubface(fliptets[1]) || + issubface(fliptets[3])) { + continue; // The surface mesh is degnerated. + } + } else { + if (issubface(fliptets[1]) || + issubface(fliptets[2]) || + issubface(fliptets[3])) { + continue; // not valid 2-2 flip. + } + } + + if (round_flag == 1) { + //continue; // [2017-10-20] + // We want to flip (nearly coplanar) edges [a,b] to [d,e]. + // Only allow this flip if all new faces are locally Delaunay. + // Otherwise, this routine may not terminate. + point pb = org(fliptets[0]); + point pa = dest(fliptets[0]); + point pc = apex(fliptets[1]); + point pf = apex(fliptets[3]); // pf may be dummypoint + + if (is_collinear_at(pa, pd, pe) || + is_collinear_at(pb, pd, pe)) { + continue; // avoid creating a degenerated (sub)face. + } + + // Validate the four new tets (not inverted) + REAL o1, o2; + o1 = orient3d(pe, pd, pc, pa); + o2 = orient3d(pe, pd, pb, pc); + if ((o1 >= 0.) || (o2 >= 0.)) { + //assert(0); // to debug... + continue; // inverted new tets + } + if (pf != dummypoint) { + REAL o3, o4; + o3 = orient3d(pe, pd, pa, pf); + o4 = orient3d(pe, pd, pf, pb); + if ((o3 >= 0.) || (o4 >= 0.)) { + continue; // inverted new tets + } + } + // Validate locally Delaunay properties of new faces. + REAL test_sign = insphere_s(pe, pd, pc, pa, pb); + if (test_sign < 0) { + // Locally non-Delaunay. Do not perform the 4-4 flip. + continue; + } + if (pf != dummypoint) { + test_sign = insphere_s(pe, pd, pf, pb, pa); + if (test_sign < 0) { + // Locally non-Delaunay. Do not perform the 4-4 flip. + continue; + } + } + } // if (round_flag == 1) + esymself(fliptets[0]); // [a,b,c,d] + // A 2-to-3 flip replaces face [a,b,c] by edge [e,d]. + // This creates a degenerate tet [e,d,a,b] (tmpfliptets[0]). + // It will be removed by the followed 3-to-2 flip. + flip23(fliptets, 0, fc); // No hull tet. + fnext(fliptets[3], fliptets[1]); + fnext(fliptets[1], fliptets[2]); + // Current tets in 'fliptets': + // [0] [...] + // [1] [b,a,d,e] (degenerated, d may be new point). + // [2] [b,a,e,f] (f may be dummypoint) + // [3] [b,a,f,d] + // A 3-to-2 flip replaces edge [b,a] by face [d,e,f]. + // Hull tets may be involved (f may be dummypoint). + flip32(&(fliptets[1]), (apex(fliptets[3]) == dummypoint), fc); + flipcount++; + flip23count--; + flip32count--; + flip44count++; + if (fc->remove_ndelaunay_edge) { + // Update the volume (must be decreased). + //assert(fc->tetprism_vol_sum <= 0); + tetprism_vol_sum += fc->tetprism_vol_sum; + fc->tetprism_vol_sum = 0.0; // Clear it. + } + continue; + } // if (ori == 0) + } + } + // This non-Delaunay face is unflippable. Save it. + // unflipqueue->newindex((void **) &bface); + bface = (badface *) flippool->alloc(); + bface->init(); + esymself(fliptets[0]); // *** The original non-Delaunay face **** + bface->tt = fliptets[0]; + bface->forg = org(fliptets[0]); + bface->fdest = dest(fliptets[0]); + bface->fapex = apex(fliptets[0]); + // Add it into the unflip queue. + if (unflip_queue_front == NULL) { + unflip_queue_front = bface; + } else { + unflip_queue_tail->nextitem = bface; + } + unflip_queue_tail = bface; + } // if (ori <= 0) + } // if (sign < 0) + } // while (flipstack) + + if (b->verbose > 2) { + if (flipcount > 0) { + printf(" Performed %ld flips.\n", flipcount); + } + if (flippool->items > 0) { + printf(" Saved %ld unflippbale faces.\n", flippool->items); + } + } + // Accumulate the counter of flips. + totalcount += flipcount; + + // Return if no unflippable faces left. + //if (unflipqueue->objects == 0l) break; + if (flippool->items == 0l) break; + // Return if no flip has been performed. + if (flipcount == 0l) break; + + // Try to flip the unflippable faces. + while (unflip_queue_front != NULL) { + bface = unflip_queue_front; + if (!isdeadtet(bface->tt) && + (org(bface->tt) == bface->forg) && + (dest(bface->tt) == bface->fdest) && + (apex(bface->tt) == bface->fapex)) { + flippush(flipstack, &(bface->tt)); + } + unflip_queue_front = bface->nextitem; + flippool->dealloc((void *) bface); + } + unflip_queue_tail = NULL; + + } // while (flippool->items != 0l) + + if (flippool->items > 0l) { + // Save the unflippable faces to flip them later. + badface *bf; + while (unflip_queue_front != NULL) { + bface = unflip_queue_front; + if (!isdeadtet(bface->tt) && + (org(bface->tt) == bface->forg) && + (dest(bface->tt) == bface->fdest) && + (apex(bface->tt) == bface->fapex)) { + //flippush(flipstack, &(bface->tt)); + later_unflip_queue->newindex((void **) &bf); + *bf = *bface; + } + unflip_queue_front = bface->nextitem; + //flippool->dealloc((void *) bface); + } + //unflip_queue_tail = NULL; + flippool->restart(); // Clear the pool. + } + + if (b->verbose > 2) { + if (totalcount > 0) { + printf(" Performed %ld flips.\n", totalcount); + } + if (sliver_peels > 0) { + printf(" Removed %ld hull slivers.\n", sliver_peels); + } + //if (unflipqueue->objects > 0l) { + // printf(" %ld unflippable edges remained.\n", unflipqueue->objects); + //} + } + + return totalcount + sliver_peels; +} + +//============================================================================// +// // +// recoverdelaunay() Recovery the locally Delaunay property. // +// // +//============================================================================// + +void tetgenmesh::recoverdelaunay() +{ + badface *bface, *parybface; + flipconstraints fc; + int i, j; + + if (b->verbose > 2) { + printf(" Recovering Delaunayness...\n"); + } + tetprism_vol_sum = 0.0; // Initialize it. + + if (later_unflip_queue->objects > 0) { + // Flip the saved unflippable faces. + for (i = 0; i < later_unflip_queue->objects; i++) { + bface = (badface *) fastlookup(later_unflip_queue, i); + if (!isdeadtet(bface->tt) && + (org(bface->tt) == bface->forg) && + (dest(bface->tt) == bface->fdest) && + (apex(bface->tt) == bface->fapex)) { + flippush(flipstack, &(bface->tt)); + } + } + later_unflip_queue->restart(); // clean it. + if (flippool->items == 0l) { + return; + } + } else { + if (flippool->items == 0l) { + // Flip all locally non-Delaunay faces of the tetrahedralisation. + triface tetloop, neightet; //, *parytet; + tetrahedrons->traversalinit(); + tetloop.tet = tetrahedrontraverse(); + while (tetloop.tet != NULL) { + for (tetloop.ver = 0; tetloop.ver < 4; tetloop.ver++) { + decode(tetloop.tet[tetloop.ver], neightet); + if (!facemarked(neightet)) { + flippush(flipstack, &tetloop); + } + } + point *ppt = (point *) &(tetloop.tet[4]); + tetprism_vol_sum += tetprismvol(ppt[0], ppt[1], ppt[2], ppt[3]); + tetloop.tet = tetrahedrontraverse(); + } + } + } + + recover_delaunay_count++; + + // Calulate a relatively lower bound for small improvement. + // Used to avoid rounding error in volume calculation. + fc.bak_tetprism_vol = tetprism_vol_sum * b->epsilon * 1e-3; + + if (b->verbose > 2) { + printf(" Initial obj = %.17g\n", tetprism_vol_sum); + } + + if (b->verbose > 2) { + printf(" Recover Delaunay [Lawson] : %ld\n", flippool->items); + } + + // First only use the basic Lawson's flip. + fc.remove_ndelaunay_edge = 1; + fc.enqflag = 2; + + lawsonflip3d(&fc); + + if (b->verbose > 2) { + printf(" obj (after Lawson) = %.17g\n", tetprism_vol_sum); + } + + if (later_unflip_queue->objects == 0l) { + return; + } + + fc.unflip = 0; // fc.unflip = 1; // Unflip if the edge is not flipped. + fc.collectnewtets = 1; // new tets are returned in 'cavetetlist'. + fc.enqflag = 0; + + int bak_autofliplinklevel = autofliplinklevel; + int bak_fliplinklevel = b->fliplinklevel; + autofliplinklevel = 1; // Init level. + b->fliplinklevel = -1; // No fixed level. + + badface *bfarray = new badface[later_unflip_queue->objects]; + + while ((later_unflip_queue->objects > 0) && + (autofliplinklevel < 4)) { // level = 1,2,3 //< 10 + + int nbf = later_unflip_queue->objects; + for (i = 0; i < nbf; i++) { + bfarray[i] = * (badface *) fastlookup(later_unflip_queue, i); + } + later_unflip_queue->restart(); // clean it. + + if (b->verbose > 2) { + printf(" Recover Delaunay [level = %2d] #: %d.\n", + autofliplinklevel, nbf); + } + + for (i = 0; i < nbf; i++) { + bface = &(bfarray[i]); + if (getedge(bface->forg, bface->fdest, &bface->tt)) { + if (removeedgebyflips(&(bface->tt), &fc) == 2) { + tetprism_vol_sum += fc.tetprism_vol_sum; + } else { + // This edge is not removed. Save it in later_flip_queue. + later_unflip_queue->newindex((void **) &parybface); + *parybface = bfarray[i]; // *bface; + } + fc.tetprism_vol_sum = 0.0; // Clear it. + if (cavetetlist->objects > 0) { + // Queue new faces for flips. + triface neightet, *parytet; + for (j = 0; j < cavetetlist->objects; j++) { + parytet = (triface *) fastlookup(cavetetlist, j); + // A queued new tet may be dead. + if (!isdeadtet(*parytet)) { + for (parytet->ver = 0; parytet->ver < 4; parytet->ver++) { + // Avoid queue a face twice. + decode(parytet->tet[parytet->ver], neightet); + if (!facemarked(neightet)) { + flippush(flipstack, parytet); + } + } // parytet->ver + } + } // j + cavetetlist->restart(); + } // if (cavetetlist->objects > 0) + } + } // i + + autofliplinklevel++; // =b->fliplinklevelinc; + } // while (later_unflip_queue->objects > 0) + + delete [] bfarray; + + if (b->verbose > 2) { + if (later_unflip_queue->objects > 0l) { + printf(" %ld non-Delaunay edges remained.\n", later_unflip_queue->objects); + } + } + + if (flippool->items > 0l) { + // Flip locally non-Delaunay faces. Unflippable faces are queued + // in later_flip_queue. + fc.remove_ndelaunay_edge = 1; + fc.enqflag = 2; // queue exteior faces of a flip. + lawsonflip3d(&fc); + //fc.enqflag = 0; // for removedgebyflips(). + } + + if (b->verbose > 2) { + printf(" Final obj = %.17g\n", tetprism_vol_sum); + } + + if (later_unflip_queue->objects > 0l) { + if (b->verbose > 2) { + printf(" %ld non-Delaunay edges remained.\n", later_unflip_queue->objects); + } + later_unflip_queue->restart(); + } + + autofliplinklevel = bak_autofliplinklevel; // Restore this value. + b->fliplinklevel = bak_fliplinklevel; +} + +//============================================================================// +// // +// get_seg_laplacian_center() Get the Laplcian center of a mesh vertex. // +// // +// "mesh_vert" must be a Steiner vertex (FREESEGVERTEX) in a segment. // +// // +//============================================================================// + +int tetgenmesh::get_seg_laplacian_center(point mesh_vert, REAL target[3]) +{ + if (pointtype(mesh_vert) == UNUSEDVERTEX) { + return 0; + } + + face leftseg, rightseg; + + sdecode(point2sh(mesh_vert), leftseg); + leftseg.shver = 0; + if (sdest(leftseg) == mesh_vert) { + senext(leftseg, rightseg); + spivotself(rightseg); + rightseg.shver = 0; + if (sorg(rightseg) != mesh_vert) { + sesymself(rightseg); + } + if (sorg(rightseg) != mesh_vert) { + terminatetetgen(this, 2); + } + } else { + rightseg = leftseg; + senext2(rightseg, leftseg); + spivotself(leftseg); + leftseg.shver = 0; + if (sdest(leftseg) != mesh_vert) { + sesymself(leftseg); + } + if (sdest(leftseg) != mesh_vert) { + terminatetetgen(this, 2); + } + } + point lpt = sorg(leftseg); + point rpt = sdest(rightseg); + + int j; + + for (j = 0; j < 3; j++) { + target[j] = 0.5 * (lpt[j] + rpt[j]); + } + + return 1; +} + +//============================================================================// +// // +// get_surf_laplacian_center() Get the Laplcian center of a mesh vertex. // +// // +// "mesh_vert" must be a Steiner vertex (FREEFACETVERTEX) in a facet. // +// // +//============================================================================// + +int tetgenmesh::get_surf_laplacian_center(point mesh_vert, REAL target[3]) +{ + if (pointtype(mesh_vert) == UNUSEDVERTEX) { + return 0; + } + + getvertexstar(1, mesh_vert, caveoldtetlist, NULL, caveshlist); + + // The number of vertices is the same as the number of edges. + int npt = (int) caveshlist->objects; + int i, j; + + for (j = 0; j < 3; j++) { + target[j] = 0.; + } + + for (i = 0; i < npt; i++) { + face *cavesh = (face *) fastlookup(caveshlist, i); + point e1 = sorg(*cavesh); + point e2 = sdest(*cavesh); + for (j = 0; j < 3; j++) { + target[j] += e1[j]; + } + for (j = 0; j < 3; j++) { + target[j] += e2[j]; + } + } + + // We added every link vertex twice. + int npt2 = npt * 2; + + for (j = 0; j < 3; j++) { + target[j] /= (double) npt2; + } + + caveoldtetlist->restart(); + caveshlist->restart(); + return 1; +} + +//============================================================================// +// // +// get_laplacian_center() Get the Laplcian center of a mesh vertex. // +// // +// "mesh_vert" must be a Steiner vertex (FREEVOLVERTEX) in volume. // +// // +//============================================================================// + +int tetgenmesh::get_laplacian_center(point mesh_vert, REAL target[3]) +{ + if (pointtype(mesh_vert) == UNUSEDVERTEX) { + return 0; + } + getvertexstar(1, mesh_vert, caveoldtetlist, cavetetvertlist, NULL); + + // Calculate the laplacian center. + int npt = (int) cavetetvertlist->objects; + int i, j; + + for (j = 0; j < 3; j++) { + target[j] = 0.; + } + + for (i = 0; i < npt; i++) { + point *pt = (point *) fastlookup(cavetetvertlist, i); + for (j = 0; j < 3; j++) { + target[j] += (*pt)[j]; + } + } + + for (j = 0; j < 3; j++) { + target[j] /= (double) npt; + } + + cavetetvertlist->restart(); + return 1; +} + +//============================================================================// +// // +// move_vertex() Try to move a given vertex towards the target position. // +// // +//============================================================================// + +bool tetgenmesh::move_vertex(point mesh_vert, REAL target[3]) +{ + if (pointtype(mesh_vert) == UNUSEDVERTEX) { + if (caveoldtetlist->objects > 0l) { + caveoldtetlist->restart(); + } + return 0; + } + // Do not move if the target is already very close the vertex. + if (distance(mesh_vert, target) < minedgelength) { + if (caveoldtetlist->objects > 0l) { + caveoldtetlist->restart(); + } + return 0; + } + triface* cavetet; + point pa, pb, pc; + REAL ori; + int i, j; + + REAL dir[3], newpos[3]; + REAL alpha = b->smooth_alpha; // 0.3; + + for (j = 0; j < 3; j++) { + dir[j] = target[j] - mesh_vert[j]; + newpos[j] = mesh_vert[j] + alpha * dir[j]; + } + + if (caveoldtetlist->objects == 0l) { + getvertexstar(1, mesh_vert, caveoldtetlist, NULL, NULL); + } + + bool moveflag = true; + int iter = 0; + + while (iter < 3) { + for (i = 0; i < caveoldtetlist->objects; i++) { + cavetet = (triface *) fastlookup(caveoldtetlist, i); + if (ishulltet(*cavetet)) continue; // Skip a hull face. + + pa = org(*cavetet); + pb = dest(*cavetet); + pc = apex(*cavetet); + ori = orient3d(pa, pb, pc, newpos); + if (ori >= 0) { + moveflag = false; + break; // This tet becomes invalid. + } + } + if (moveflag) { + break; + } else { + alpha = (alpha / 2.); + for (j = 0; j < 3; j++) { + newpos[j] = mesh_vert[j] + alpha * dir[j]; + } + iter++; + } + } // while (iter < 3) + + if (moveflag) { + for (j = 0; j < 3; j++) { + mesh_vert[j] = newpos[j]; + } + + triface checkface, neightet; + //int j; + + // Push all faces of this vertex star and link into queue. + for (i = 0; i < caveoldtetlist->objects; i++) { + cavetet = (triface *) fastlookup(caveoldtetlist, i); + if (ishulltet(*cavetet)) continue; // Skip a hull face. + flippush(flipstack, cavetet); + for (j = 0; j < 3; j++) { + esym(*cavetet, checkface); + fsym(checkface, neightet); + if (!facemarked(neightet)) { + flippush(flipstack, &checkface); + } + enextself(*cavetet); + } + } + + if (badtetrahedrons != NULL) { + // queue all cavity tets for quality check. + for (i = 0; i < caveoldtetlist->objects; i++) { + cavetet = (triface *) fastlookup(caveoldtetlist, i); + if (ishulltet(*cavetet)) continue; // Skip a hull face. + enqueuetetrahedron(cavetet); + } + } + + flipconstraints fc; + fc.enqflag = 2; // queue all exterior faces of a flip. + if (badtetrahedrons != NULL) { + fc.chkencflag = 4; // queue new tets for quality check. + } + lawsonflip3d(&fc); + + + } // if (moveflag) + + caveoldtetlist->restart(); + return moveflag; +} + +//============================================================================// +// // +// smooth_vertices() Smooth vertices. // +// // +//============================================================================// + +void tetgenmesh::smooth_vertices() +{ + if (!b->quiet) { + printf("Smoothing vertices...\n"); + } + + if (b->verbose) { + printf(" Smooth criterion = %d\n", b->smooth_cirterion); + printf(" Smooth iterations = %d\n", b->smooth_maxiter); + printf(" Smooth relax-alpha = %g\n", b->smooth_alpha); + } + + point *smpt_list = NULL; + point *surf_smpt_list = NULL; + point *seg_smpt_list = NULL; + int volcount = 0, faccount = 0, segcount = 0; + + // Only use it when we have Steiner points. + if (st_segref_count > 0) { + seg_smpt_list = new point[st_segref_count]; + } + if (st_volref_count > 0) { + smpt_list = new point[st_volref_count]; + } + if (st_facref_count > 0) { + surf_smpt_list = new point[st_facref_count]; + } + + points->traversalinit(); + point ptloop = pointtraverse(); + while (ptloop != NULL) { + enum verttype vt = pointtype(ptloop); + if (vt == FREEVOLVERTEX) { + smpt_list[volcount++] = ptloop; + } else if (vt == FREEFACETVERTEX) { + surf_smpt_list[faccount++] = ptloop; + } else if (vt == FREESEGVERTEX) { + seg_smpt_list[segcount++] = ptloop; + } + ptloop = pointtraverse(); + } + + if ((volcount != st_volref_count) || + (faccount != st_facref_count) || + (segcount != st_segref_count)) { + terminatetetgen(this, 2); + } + + if (b->verbose > 1) { + printf(" Smoothing (%ld, %ld) %ld vertices.\n", + st_segref_count, st_facref_count, st_volref_count); + } + + // Allocate a list of target points. + REAL *target_list = NULL; + REAL *surf_target_list = NULL; + REAL *seg_target_list = NULL; + + if (st_volref_count > 0) { + target_list = new REAL[st_volref_count * 3]; + } + if (st_facref_count > 0) { + surf_target_list = new REAL[st_facref_count * 3]; + } + if (st_segref_count > 0) { + seg_target_list = new REAL[st_segref_count * 3]; + } + + long bak_flipcount = flip23count + flip32count + flip44count; + int movedcount = 0, total_movecount = 0; + int unmovedcount = 0, total_unmovedcount = 0; + int iter = 0, maxiter = b->smooth_maxiter; + int i; + + for (iter = 0; iter < maxiter; iter++) { + + movedcount = unmovedcount = 0; + + if (((b->smooth_cirterion & 4) > 0)) { // -s4, -s5, -s6, -s7, default -s3 + //if (st_segref_count > 0) { + for (i = 0; i < st_segref_count; i++) { + get_seg_laplacian_center(seg_smpt_list[i], &(seg_target_list[i*3])); + } + for (i = 0; i < st_segref_count; i++) { + if (move_vertex(seg_smpt_list[i], &(seg_target_list[i*3]))) { + if (later_unflip_queue->objects > b->unflip_queue_limit) { + recoverdelaunay(); + } + movedcount++; + } else { + unmovedcount++; + } + } + //} // if (st_segref_count > 0) + } + + if (((b->smooth_cirterion & 2) > 0)) { // default -s3 + //if (st_facref_count > 0) { + for (i = 0; i < st_facref_count; i++) { + get_surf_laplacian_center(surf_smpt_list[i], &(surf_target_list[i*3])); + } + for (i = 0; i < st_facref_count; i++) { + if (move_vertex(surf_smpt_list[i], &(surf_target_list[i*3]))) { + if (later_unflip_queue->objects > b->unflip_queue_limit) { + recoverdelaunay(); + } + movedcount++; + } else { + unmovedcount++; + } + } + //} // if (st_facref_count > 0) + } + + if (((b->smooth_cirterion & 1) > 0)) { // default -s3 + //if (st_volref_count > 0) { + for (i = 0; i < st_volref_count; i++) { + get_laplacian_center(smpt_list[i], &(target_list[i*3])); + caveoldtetlist->restart(); + } + for (i = 0; i < st_volref_count; i++) { + if (move_vertex(smpt_list[i], &(target_list[i*3]))) { + if (later_unflip_queue->objects > b->unflip_queue_limit) { + recoverdelaunay(); + } + movedcount++; + } else { + unmovedcount++; + } + } + //} // if (st_volref_count > 0) + } + + if (movedcount == 0) break; + + if (b->verbose > 1) { + printf(" iter=%d, smoothed %d vertices, %d unsmoothed\n", iter, + movedcount, unmovedcount); + } + + + total_movecount += movedcount; + total_unmovedcount += unmovedcount; + + if (later_unflip_queue->objects > 0) { + recoverdelaunay(); + } + } // iter + + if (b->verbose > 1) { + printf(" Smoothed %d (%d) times, flipped %ld faces.\n", + total_movecount, total_unmovedcount, + flip23count + flip32count + flip44count - bak_flipcount); + } + + if (st_segref_count > 0) { + delete [] seg_smpt_list; + delete [] seg_target_list; + } + if (st_facref_count > 0) { + delete [] surf_target_list; + delete [] surf_smpt_list; + } + if (st_volref_count > 0) { + delete [] target_list; + delete [] smpt_list; + } +} + +//============================================================================// +// // +// get_tet() Get the tetrahedron with the given vertices. // +// // +//============================================================================// + +bool tetgenmesh::get_tet(point pa, point pb, point pc, point pd, triface *searchtet) +{ + if (getedge(pa, pb, searchtet)) { + int t1ver; + triface spintet = *searchtet; + while (1) { + if (apex(spintet) == pc) { + *searchtet = spintet; + break; + } + fnextself(spintet); + if (spintet.tet == searchtet->tet) break; + } + if (apex(*searchtet) == pc) { + if (oppo(*searchtet) == pd) { + return true; + } else { + fsymself(*searchtet); + if (oppo(*searchtet) == pd) { + return true; + } + } + } + } + + return false; +} + +//============================================================================// +// // +// get_tetqual() Calculate various quality measures of a given tetrahedron.// +// // +// Calculate the aspect ratio (Lmax / hmin), edge ratio (Lmax/Lmin), maximal // +// and minimal dihedral angles of this tetrahedron. // +// // +// These values are returned by: // +// bf->key, aspect ratio // +// bf->cent[0], cosine of maximal dihedral angle // +// bf->cent[1], cosine of minimal dihedral angle // +// bf->cent[2], edge ratio // +// bf->cent[3], minimal edge length // +// bf->cent[4], volume (used to validate whether it is modified or not). // +// bf->cent[5], (no use). // +// bf->tet, the edge with maximal dihedral angle. // +// bf->ss.shver, (re-used) count the number of dihedrals > 165 degree. // +// // +//============================================================================// + +bool tetgenmesh::get_tetqual(triface *chktet, point oppo_pt, badface *bf) +{ + if (chktet != NULL) { + bf->init(); + if (oppo_pt == NULL) { + point *ppt = (point *) &(chktet->tet[4]); + bf->forg = ppt[0]; // pa + bf->fdest = ppt[1]; // pb + bf->fapex = ppt[2]; // pc + bf->foppo = ppt[3]; // pd + } else { + bf->forg = org(*chktet); + bf->fdest = dest(*chktet); + bf->fapex = apex(*chktet); + bf->foppo = oppo_pt; + } + } + + REAL A[4][4], rhs[4], D; + int indx[4]; + int i, j; + + // get the entries of A[3][3]. + for (i = 0; i < 3; i++) A[0][i] = bf->forg[i] - bf->foppo[i]; // d->a vec + for (i = 0; i < 3; i++) A[1][i] = bf->fdest[i] - bf->foppo[i]; // d->b vec + for (i = 0; i < 3; i++) A[2][i] = bf->fapex[i] - bf->foppo[i]; // d->c vec + + // Get the max-min edge length + REAL L[6], Lmax, Lmin; + REAL Vab[3], Vbc[3], Vca[3]; + + for (i = 0; i < 3; i++) Vab[i] = bf->fdest[i] - bf->forg[i]; // a->b vec + for (i = 0; i < 3; i++) Vbc[i] = bf->fapex[i] - bf->fdest[i]; // b->c vec + for (i = 0; i < 3; i++) Vca[i] = bf->forg[i] - bf->fapex[i]; // c->a vec + + // Use the idx2edge + L[0] = dot(A[2], A[2]); // edge c,d + L[1] = dot(A[0], A[0]); // edge a,d + L[2] = dot(Vab, Vab); // edge a,b + L[3] = dot(Vbc, Vbc); // edge b,c + L[4] = dot(A[1], A[1]); // edge b,d + L[5] = dot(Vca, Vca); // edge a,c + + Lmax = Lmin = L[0]; + //int idx = 0; + for (i = 1; i < 6; i++) { + Lmax = (Lmax < L[i] ? L[i] : Lmax); + Lmin = (Lmin > L[i] ? L[i] : Lmin); + //if (Lmin > L[i]) { + // Lmin = L[i]; idx = i; + //} + } + + Lmax = sqrt(Lmax); + Lmin = sqrt(Lmin); + + // Caluclate the Lmax / Lmin edge ratio (to detect very short edge). + bf->cent[2] = Lmax / Lmin; + bf->cent[3] = Lmin; + + // Calculate the normals and heights. + REAL N[4][3]; // The normals of the four faces. + REAL H[4]; // H[i] is the inverse of the height of its corresponding face. + bool flat_flag = false; + + if (lu_decmp(A, 3, indx, &D, 0)) { + // Get the volume of this tet. + bf->cent[4] = fabs((A[indx[0]][0] * A[indx[1]][1] * A[indx[2]][2])); + if (bf->cent[4] > 0.0) { + // Compute the inverse of matrix A, to get 3 normals of the 4 faces. + for (j = 0; j < 3; j++) { + for (i = 0; i < 3; i++) rhs[i] = 0.0; + rhs[j] = 1.0; // Positive means the inside direction + lu_solve(A, 3, indx, rhs, 0); + for (i = 0; i < 3; i++) N[j][i] = rhs[i]; + } + // Get the fourth normal by summing up the first three. + for (i = 0; i < 3; i++) N[3][i] = - N[0][i] - N[1][i] - N[2][i]; + } else { + // This is a very flat tet. + flat_flag = true; + } + } else { + flat_flag = true; + } + + if (flat_flag) { + // This tet is nearly degenerate. + bf->cent[4] = orient3d(bf->fdest, bf->forg, bf->fapex, bf->foppo); + if (bf->cent[4] <= 0.0) { + return false; // degenerated or inverted. + } + // Calculate the normals of the four faces. + facenormal(bf->fapex, bf->fdest, bf->foppo, N[0], 1, NULL); // face [c,b,d] + facenormal(bf->forg, bf->fapex, bf->foppo, N[1], 1, NULL); // face [a,c,d] + facenormal(bf->fdest, bf->forg, bf->foppo, N[2], 1, NULL); // face [b,a,d] + facenormal(bf->forg, bf->fdest, bf->fapex, N[3], 1, NULL); // face [a,b,c] + } // if (!success) + + // Normalized the normals. + for (i = 0; i < 4; i++) { + H[i] = sqrt(dot(N[i], N[i])); + if (H[i] > 0.0) { + for (j = 0; j < 3; j++) N[i][j] /= H[i]; + } else { + return false; // H[i] == 0.0; + } + } + + if (!flat_flag) { + // Get the biggest H[i] (corresponding to the smallest height). + REAL minheightinv = H[0]; + for (i = 1; i < 4; i++) { + if (H[i] > minheightinv) minheightinv = H[i]; + } + // Calulcate the aspect ratio = L_max / h_min. + bf->key = Lmax * minheightinv; + } else { + // A very flat tet. + //if (bf->key <= 0.0) { + bf->key = 1.e+30; // infinity. + //} + } + + // Calculate the cosine of the dihedral angles of the edges. + REAL cosmaxd = 1.0, cosmind = -1.0, cosd; + int f1, f2, idx = 0; + bf->ss.shver = 0; // // Count the number of large dihedrals. + for (i = 0; i < 6; i++) { + switch (i) { + case 0: f1 = 0; f2 = 1; break; // [c,d]. + case 1: f1 = 1; f2 = 2; break; // [a,d]. + case 2: f1 = 2; f2 = 3; break; // [a,b]. + case 3: f1 = 0; f2 = 3; break; // [b,c]. + case 4: f1 = 2; f2 = 0; break; // [b,d]. + case 5: f1 = 1; f2 = 3; break; // [a,c]. + } + cosd = -dot(N[f1], N[f2]); + if (cosd < -1.0) cosd = -1.0; // Rounding. + if (cosd > 1.0) cosd = 1.0; // Rounding. + // cosmaxd = cosd < cosmaxd ? cosd : cosmaxd; + if (cosd < cosmaxd) {cosmaxd = cosd; idx = i;} + cosmind = (cosd > cosmind ? cosd : cosmind); + // Count the number of large dihedrals. + if (cosd < cos_large_dihed) bf->ss.shver++; + } // i + + bf->cent[0] = cosmaxd; + bf->cent[1] = cosmind; + + // Remember the edge with largest dihedral angle. + if (chktet) bf->tt.tet = chktet->tet; + bf->tt.ver = edge2ver[idx]; + + bf->cent[5] = 0.0; + + return true; +} + +bool tetgenmesh::get_tetqual(point pa, point pb, point pc, point pd, badface *bf) +{ + bf->init(); + + bf->forg = pa; + bf->fdest = pb; + bf->fapex = pc; + bf->foppo = pd; + + return get_tetqual(NULL, NULL, bf); +} + +//============================================================================// +// // +// enqueue_badtet() Push a bad-quality tet into the proority queue. // +// // +//============================================================================// + +void tetgenmesh:: enqueue_badtet(badface *bf) +{ + badface *bt = (badface *) badqual_tets_pool->alloc(); + + *bt = *bf; + + // The following vertices are used by get_tet(...) to identify whether + // the saved tet is still alive or not. + //bt->forg = org(bf->tt); + //bt->fdest = dest(bf->tt); + //bt->fapex = apex(bf->tt); + //bt->foppo = oppo(bf->tt); + + bt->nextitem = NULL; // important, this pointer is used to recongise the last + // item in each queue. + + // Push it into the priority queue. + REAL qual = 1.0 / log(bf->key); + + // Determine the appropriate queue to put the bad subface into. + int queuenumber = 0; + if (qual < 1.0) { + queuenumber = (int) (64.0 * (1.0 - qual)); + if (queuenumber > 63) { + queuenumber = 63; + } + } else { + // It's not a bad shape; put the subface in the lowest-priority queue. + queuenumber = 0; + } + + // Are we inserting into an empty queue? + if (bt_queuefront[queuenumber] == (badface *) NULL) { + // Yes, we are inserting into an empty queue. + // Will this become the highest-priority queue? + if (queuenumber > bt_firstnonemptyq) { + // Yes, this is the highest-priority queue. + bt_nextnonemptyq[queuenumber] = bt_firstnonemptyq; + bt_firstnonemptyq = queuenumber; + } else { + // No, this is not the highest-priority queue. + // Find the queue with next higher priority. + int i = queuenumber + 1; + while (bt_queuefront[i] == (badface *) NULL) { + i++; + } + // Mark the newly nonempty queue as following a higher-priority queue. + bt_nextnonemptyq[queuenumber] = bt_nextnonemptyq[i]; + bt_nextnonemptyq[i] = queuenumber; + } + // Put the bad subface at the beginning of the (empty) queue. + bt_queuefront[queuenumber] = bt; + } else { + // Add the bad tetrahedron to the end of an already nonempty queue. + bt_queuetail[queuenumber]->nextitem = bt; + } + // Maintain a pointer to the last subface of the queue. + bt_queuetail[queuenumber] = bt; +} + +//============================================================================// +// // +// top_badtet() Get a bad-quality tet from the priority queue. // +// // +//============================================================================// + +tetgenmesh::badface* tetgenmesh::top_badtet() +{ + // Keep a record of which queue was accessed in case dequeuebadtetra() + // is called later. + bt_recentq = bt_firstnonemptyq; + // If no queues are nonempty, return NULL. + if (bt_firstnonemptyq < 0) { + return (badface *) NULL; + } else { + // Return the first tetrahedron of the highest-priority queue. + return bt_queuefront[bt_firstnonemptyq]; + } +} + +//============================================================================// +// // +// dequeue_badtet() Popup a bad-quality tet from the priority queue. // +// // +//============================================================================// + +void tetgenmesh::dequeue_badtet() +{ + badface *bt; + int i; + + // If queues were empty last time topbadtetra() was called, do nothing. + if (bt_recentq >= 0) { + // Find the tetrahedron last returned by topbadtetra(). + bt = bt_queuefront[bt_recentq]; + // Remove the tetrahedron from the queue. + bt_queuefront[bt_recentq] = bt->nextitem; + // If this queue is now empty, update the list of nonempty queues. + if (bt == bt_queuetail[bt_recentq]) { + // Was this the highest-priority queue? + if (bt_firstnonemptyq == bt_recentq) { + // Yes; find the queue with next lower priority. + bt_firstnonemptyq = bt_nextnonemptyq[bt_firstnonemptyq]; + } else { + // No; find the queue with next higher priority. + i = bt_recentq + 1; + while (bt_queuefront[i] == (badface *) NULL) { + i++; + } + bt_nextnonemptyq[i] = bt_nextnonemptyq[bt_recentq]; + } + } + // Return the badface to the pool. + badqual_tets_pool->dealloc((void *) bt); + } +} + + +//============================================================================// +// // +// add_steinerpt_to_repair() Add Steiner to repair a bad-qaulity tet. // +// // +//============================================================================// + +bool tetgenmesh::add_steinerpt_to_repair(badface *bf, bool bSmooth) +{ + REAL cosmaxd = bf->cent[0]; + REAL eta = bf->cent[2]; + int lcount = bf->ss.shver; // the number of large dihedrals. + + triface splittet; + splittet.tet = NULL; + + if (cosmaxd < cosslidihed) { // cossmtdihed + // It is a sliver (flat) (might contain a short edge -- skinny). + triface sliver_edge; + char shape = 0; + + // Determine the outer shape of this sliver, i.e., a square of a triangle? + if (lcount == 2) { + // It is a square. Try to remove the edge [a,b] + shape = 'S'; + sliver_edge = bf->tt; + } else if (lcount == 3) { + // It is a triangle. Try to remove the edge [c,d] + shape = 'T'; + edestoppo(bf->tt, sliver_edge); // face [c,d,a] + } + + // Determine a Steiner point according to the shape of this sliver. + if (shape == 'S') { + REAL vol, max_vol = 0.0; + + triface check_sliver = sliver_edge; + for (int i = 0; i < 2; i++) { + bool is_bdry = false; + if (issubseg(check_sliver)) { + is_bdry = true; + } else { + triface spintet = check_sliver; + int t1ver; + do { + if (issubface(spintet)) { + is_bdry = true; break; + } + fnextself(spintet); + } while (spintet.tet != check_sliver.tet); + } + + if (!is_bdry) { + triface spintet = check_sliver; + int t1ver; + do { + point *ppt = (point *) &(spintet.tet[4]); + vol = orient3d(ppt[1], ppt[0], ppt[2], ppt[3]); + if (vol > max_vol) { + max_vol = vol; + splittet = spintet; + } + fnextself(spintet); + } while (spintet.tet != check_sliver.tet); + } + + // Check the opposite edge. + edestoppoself(check_sliver); + } // i + } else if (shape == 'T') { + } + } else if (eta > b->opt_max_edge_ratio) { + // It is a skinny tet. + // This tet contains a relatively short edge. Check if it can be collapsed. + REAL Lmin = bf->cent[3]; + + // Get the shortest edge of this tet. + triface short_edge = bf->tt; + int i; + for (i = 0; i < 6; i++) { + short_edge.ver = edge2ver[i]; + REAL dd = distance(org(short_edge), dest(short_edge)); + if ((fabs(Lmin - dd) / Lmin) < 1e-4) break; + } + if (i == 6) { + terminatetetgen(this, 2); + } + + if (Lmin <= minedgelength) { + // A very short edge. Check if it was correctly created. + point e1 = org(short_edge); + point e2 = dest(short_edge); + if (issteinerpoint(e1)) { + if (!create_a_shorter_edge(e1, e2)) { + terminatetetgen(this, 2); + } + } else if (issteinerpoint(e2)) { + if (!create_a_shorter_edge(e2, e1)) { + terminatetetgen(this, 2); + } + } + } + } + + if (splittet.tet == NULL) { + return false; // not added. + } + + // Do not add if the splittet is also a bad qual tet. + badface tmpbf; + if (get_tetqual(&splittet, NULL, &tmpbf)) { + if (tmpbf.cent[0] < cosslidihed) { + return false; + } + } else { + return false; + } + + point steinerpt; + makepoint(&steinerpt, FREEVOLVERTEX); + point *ppt = (point *) &(splittet.tet[4]); + for (int j = 0; j < 3; j++) { + steinerpt[j] = (ppt[0][j]+ppt[1][j]+ppt[2][j]+ppt[3][j]) / 4.0; + } + + insertvertexflags ivf; + + //triface searchtet = splittet; + ivf.iloc = (int) OUTSIDE; + ivf.bowywat = 3; + ivf.lawson = 2; + ivf.rejflag = 0; + if (badtetrahedrons != NULL) { + ivf.chkencflag = 4; // queue new tets. + } + ivf.sloc = ivf.sbowywat = 0; // No use. + ivf.splitbdflag = 0; // No use (its an interior vertex). + ivf.validflag = 1; + ivf.respectbdflag = 1; + + ivf.smlenflag = 1; // avoid creating very short edges + ivf.parentpt = NULL; // init. + + if (insertpoint(steinerpt, &splittet, NULL, NULL, &ivf)) { + st_volref_count++; + //if (steinerleft > 0) steinerleft--; + + if (flipstack != NULL) { + flipconstraints fc; + fc.enqflag = 2; + if (badtetrahedrons != NULL) { + fc.chkencflag = 4; + } + lawsonflip3d(&fc); + } + + if (later_unflip_queue->objects > b->unflip_queue_limit) { + //recoverdelaunay(); + later_unflip_queue->restart(); // clean it. + } + } else { + // Point is not inserted. + pointdealloc(steinerpt); + return false; + } + + if (bSmooth) { + REAL ccent[3]; + get_laplacian_center(steinerpt, ccent); + if (move_vertex(steinerpt, ccent)) { + opt_smooth_count++; + } + } // if (bSmooth) + + if (badtetrahedrons->items > 0) { + // Push new bad quality tetrahedron into queue. + badface bf; + REAL max_asp = 0., cosmaxd = 1.; + badtetrahedrons->traversalinit(); + triface *bface = (triface *) badtetrahedrons->traverse(); + while (bface != NULL) { + if (!isdeadtet(*bface)) { + // A queued tet may have been processed. + if (marktest2ed(*bface)) { + unmarktest2(*bface); + if (!ishulltet(*bface)) { + get_tetqual(bface, NULL, &bf); + // Save the worst quality. + max_asp = (max_asp > bf.key ? max_asp : bf.key); + cosmaxd = (cosmaxd < bf.cent[0] ? cosmaxd : bf.cent[0]); + if ((bf.key > b->opt_max_asp_ratio) || (bf.cent[0] < cosmaxdihed)) { + bf.forg = org(bf.tt); + bf.fdest = dest(bf.tt); + bf.fapex = apex(bf.tt); + bf.foppo = oppo(bf.tt); + enqueue_badtet(&bf); + } + } // if (!ishulltet(*bface)) + } + } + bface = (triface *) badtetrahedrons->traverse(); + } + badtetrahedrons->restart(); + } + + // Check if the bad quality tet is removed or not. + if (get_tet(bf->forg, bf->fdest, bf->fapex, bf->foppo, &(bf->tt))) { + // Try to remove it. + if (repair_tet(bf, true, false, false)) { + return true; + } + } else { + // This tet is removed. + return true; + } + + return false; // not added. +} + + + + + + +//============================================================================// +// // +// flip_edge_to_improve() Flip an edge of a bad-quality tet. // +// // +//============================================================================// + +bool tetgenmesh::flip_edge_to_improve(triface *sliver_edge, REAL& improved_cosmaxd) +{ + if (issubseg(*sliver_edge)) { + return false; + } + + flipconstraints fc; + + //fc.noflip_in_surface = 1; // do not flip in surface. + fc.noflip_in_surface = ((b->nobisect > 0) || ((b->cdtrefine & 2) == 0)); + fc.remove_large_angle = 1; + fc.unflip = 1; + fc.collectnewtets = 1; + fc.checkflipeligibility = 1; + fc.cosdihed_in = improved_cosmaxd; // cosmaxd; + fc.cosdihed_out = 0.0; // 90 degree. + fc.max_asp_out = 0.0; + + if (removeedgebyflips(sliver_edge, &fc) == 2) { + // This sliver is removed by flips. + if ((fc.cosdihed_out < cosmaxdihed) || (fc.max_asp_out > b->opt_max_asp_ratio)) { + // Queue new bad tets for further improvements. + badface bf; + for (int j = 0; j < cavetetlist->objects; j++) { + triface *parytet = (triface *) fastlookup(cavetetlist, j); + if (!isdeadtet(*parytet) && !ishulltet(*parytet)) { + if (get_tetqual(parytet, NULL, &bf)) { + if ((bf.key > b->opt_max_asp_ratio) || (bf.cent[0] < cosmaxdihed)) { + bf.forg = org(bf.tt); + bf.fdest = dest(bf.tt); + bf.fapex = apex(bf.tt); + bf.foppo = oppo(bf.tt); + enqueue_badtet(&bf); + } + } else { + terminatetetgen(this, 2); + } + } + } // j + } + cavetetlist->restart(); + return true; + } + + return false; +} + +//============================================================================// +// // +// repair_tet() Repair a bad-qaulity tet. // +// // +//============================================================================// + +bool tetgenmesh::repair_tet(badface *bf, bool bFlips, bool bSmooth, bool bSteiners) +{ + REAL cosmaxd = bf->cent[0]; + REAL eta = bf->cent[2]; + int lcount = bf->ss.shver; // the number of large dihedrals. + + if (cosmaxd < cossmtdihed) { + // It is a sliver (flat) (it might contain a short edge -- skinny). + //triface sliver_edge; + char shape = '0'; + + // Determine the outer shape of this sliver, i.e., a square of a triangle? + if (lcount == 2) { + // It is a square. Try to remove the edge [a,b] + shape = 'S'; + } else if (lcount == 3) { + // It is a triangle. Try to remove the edge [c,d] + shape = 'T'; + //edestoppo(bf->tt, sliver_edge); // face [c,d,a] + } + + if (bFlips) { + if (shape == 'S') { + triface sliver_edge = bf->tt; + if (flip_edge_to_improve(&sliver_edge, cosmaxd)) { + opt_flips_count++; + return true; + } + // Due to 'unflip', the flip function may modify the sliver. + if (get_tet(bf->forg, bf->fdest, bf->fapex, bf->foppo, &(bf->tt))) { + // Try to flip the opposite edge of this sliver. + edestoppo(bf->tt, sliver_edge); // face [c,d,a] + if (flip_edge_to_improve(&sliver_edge, cosmaxd)) { + opt_flips_count++; + return true; + } + } + } else if (shape == 'T') { + triface sliver_edge; + // flip_face_to_improve(...) + edestoppo(bf->tt, sliver_edge); // face [c,d,a] + if (flip_edge_to_improve(&sliver_edge, cosmaxd)) { + opt_flips_count++; + return true; + } + } + } + } else if (eta > b->opt_max_edge_ratio) { + // It is a skinny tet. + // This tet contains a relatively short edge. Check if it can be collapsed. + REAL Lmin = bf->cent[3]; + + // Get the shortest edge of this tet. + triface short_edge = bf->tt; + int i; + for (i = 0; i < 6; i++) { + short_edge.ver = edge2ver[i]; + REAL dd = distance(org(short_edge), dest(short_edge)); + //if (fabs(Lmin - dd) < 1e-8) break; + if ((fabs(Lmin - dd) / Lmin) < 1e-4) break; + } + if (i == 6) { + terminatetetgen(this, 2); + } + + + if (Lmin <= minedgelength) { + // A very short edge. Check if it was correctly created. + point e1 = org(short_edge); + point e2 = dest(short_edge); + if (issteinerpoint(e1)) { + if (!create_a_shorter_edge(e1, e2)) { + terminatetetgen(this, 2); + } + } else if (issteinerpoint(e2)) { + if (!create_a_shorter_edge(e2, e1)) { + terminatetetgen(this, 2); + } + } + } + + } else { + // It is neither a flat nor skinny tet. While it has a large asp. + + } + + + if (bSteiners && + ((bf->key > opt_max_sliver_asp_ratio) || (cosmaxd < cosslidihed))) { + // This sliver is not removed. Due to 'unflip', the flip function may + // modify the sliver. + if (get_tet(bf->forg, bf->fdest, bf->fapex, bf->foppo, &(bf->tt))) { + if (add_steinerpt_to_repair(bf, bSmooth)) { + return true; + } + } + } // if (bSteiners) + + return false; // not repaired +} + +//============================================================================// +// // +// repair_badqual_tets() Repair all queued bad quality tet. // +// // +//============================================================================// + +long tetgenmesh::repair_badqual_tets(bool bFlips, bool bSmooth, bool bSteiners) +{ + if (b->verbose > 1) { + printf(" Repairing %ld bad quality tets.\n", badqual_tets_pool->items); + } + long repaired_count = 0l; + + while (badqual_tets_pool->items > 0) { + + // Get a badtet of highest priority. + badface *bt = top_badtet(); + + if (get_tet(bt->forg, bt->fdest, bt->fapex, bt->foppo, &(bt->tt))) { + if (repair_tet(bt, bFlips, bSmooth, bSteiners)) { + repaired_count++; + } else { + // Failed to repair this tet. Save it. + badface *bf = NULL; + unsplit_badtets->newindex((void **) &bf); + *bf = *bt; + } + } // if (get_tet(...)) + + // Return the badtet to the pool. + dequeue_badtet(); + } // while (badqual_tets_pool->items > 0) + + if (unsplit_badtets->objects > 0l) { + // Re-initialise the priority queue + for (int i = 0; i < 64; i++) { + bt_queuefront[i] = bt_queuetail[i] = NULL; + } + bt_firstnonemptyq = -1; + bt_recentq = -1; + + for (int i = 0; i < unsplit_badtets->objects; i++) { + badface *bt = (badface *) fastlookup(unsplit_badtets, i); + enqueue_badtet(bt); + } + unsplit_badtets->restart(); + } // if (unsplit_badtets->objects > 0l) + + return repaired_count; +} + +//============================================================================// +// // +// improve_mesh() Mesh improvement. // +// // +//============================================================================// + +void tetgenmesh::improve_mesh() +{ + if (!b->quiet) { + printf("Improving mesh...\n"); + } + + if (b->verbose) { + printf(" Target maximum aspect ratio = %g.\n", b->opt_max_asp_ratio); + printf(" Target maximum dihedral angle = %g.\n", b->optmaxdihedral); + printf(" Maximum flip level = %d.\n", b->opt_max_flip_level); // -O# + printf(" Number of iterations = %d.\n", b->opt_iterations); // -O///# + } + + long blt = b->tetrahedraperblock; + badqual_tets_pool = new memorypool(sizeof(badface), blt, sizeof(void *), 0); + badtetrahedrons = new memorypool(sizeof(triface), blt, sizeof(void *), 0); + unsplit_badtets = new arraypool(sizeof(badface), 10); + + for (int i = 0; i < 64; i++) { + bt_queuefront[i] = NULL; + } + bt_firstnonemptyq = -1; + bt_recentq = -1; + + cos_large_dihed = cos(135. / 180. * PI); // used in get_tetqual + + cosmaxdihed = cos(b->optmaxdihedral / 180.0 * PI); // set by -o/# + + // The smallest dihedral angle to identify slivers. + REAL sliver_ang_tol = b->optmaxdihedral - 5.0; + if (sliver_ang_tol < 172.0) { + sliver_ang_tol = 172.; + } + cossmtdihed = cos(sliver_ang_tol / 180.0 * PI); + + // The smallest dihedral angle to split slivers. + REAL split_sliver_ang_tol = b->optmaxdihedral + 10.0; + if (split_sliver_ang_tol < 179.0) { + split_sliver_ang_tol = 179.0; + } else if (split_sliver_ang_tol > 180.0) { + split_sliver_ang_tol = 179.9; + } + cosslidihed = cos(split_sliver_ang_tol / 180.0 * PI); + + opt_max_sliver_asp_ratio = b->opt_max_asp_ratio * 10.; // set by -o//# + + int attrnum = numelemattrib - 1; + triface checktet; badface bf; + + // Put all bad tetrahedra into array. + tetrahedrons->traversalinit(); + checktet.tet = tetrahedrontraverse(); + while (checktet.tet != NULL) { + if (b->convex) { // -c + // Skip this tet if it lies in the exterior. + if (elemattribute(checktet.tet, attrnum) == -1.0) { + checktet.tet = tetrahedrontraverse(); + continue; + } + } + if (get_tetqual(&checktet, NULL, &bf)) { + if ((bf.key > b->opt_max_asp_ratio) || (bf.cent[0] < cosmaxdihed)) { + bf.forg = org(bf.tt); + bf.fdest = dest(bf.tt); + bf.fapex = apex(bf.tt); + bf.foppo = oppo(bf.tt); + enqueue_badtet(&bf); + } + } else { + terminatetetgen(this, 2); // a degenerated tet. + } + checktet.tet = tetrahedrontraverse(); + } + + // Backup flip edge options. + int bakautofliplinklevel = autofliplinklevel; + int bakfliplinklevel = b->fliplinklevel; + int bakmaxflipstarsize = b->flipstarsize; + + b->fliplinklevel = 1; // initial (<= b->opt_max_flip_level, -O#) + b->flipstarsize = 10; // b->optmaxflipstarsize; + + long total_repaired_count = 0l; + long bak_pt_count = points->items; + + // Only using flips. + while (badqual_tets_pool->items > 0) { + long repaired_count = repair_badqual_tets(true, false, false); + total_repaired_count += repaired_count; + if (b->fliplinklevel < b->opt_max_flip_level) { + b->fliplinklevel++; + } else { + break; // maximal flip level is reached. + } + } // while (badqual_tets_pool->items > 0) + + if (b->verbose > 1) { + printf(" Repaired %ld tetrahedra by flips.\n", total_repaired_count); + printf(" %ld badqual tets remained.\n", badqual_tets_pool->items); + } + + int iter = 0; + long bak_st_count = st_volref_count; + while ((badqual_tets_pool->items > 0) && (iter < b->opt_iterations)) { + //b->fliplinklevel++; + long repaired_count = repair_badqual_tets(true, true, true); + // Break if no repair and no new Steiner point. + if ((repaired_count == 0l) && (bak_st_count == st_volref_count)) { + break; + } + total_repaired_count += repaired_count; + bak_st_count = st_volref_count; + iter++; + } // while (badqual_tets_pool->items > 0) + + // Do last flips. + if (badqual_tets_pool->items > 0) { + long repaired_count = repair_badqual_tets(true, false, false); + total_repaired_count += repaired_count; + } + + if (b->verbose > 1) { + printf(" Repaired %ld tetrahedra.\n", total_repaired_count); + printf(" %ld badqual tets remained.\n", badqual_tets_pool->items); + } + + if (later_unflip_queue->objects > b->unflip_queue_limit) { + //recoverdelaunay(); + later_unflip_queue->restart(); // clean it. + } + + if (b->verbose) { + if (opt_flips_count > 0l) { + printf(" Removed %ld edges/faces.\n", opt_flips_count); + } + if (opt_collapse_count > 0l) { + printf(" Collapsed %ld edges/faces.\n", opt_collapse_count); + } + if (opt_smooth_count > 0l) { + printf(" Smoothed %ld vertices.\n", opt_smooth_count); + } + if ((points->items - bak_pt_count) > 0l) { + printf(" Added %ld Steiner points.\n", points->items - bak_pt_count); + } + } + + + // Restore original flip edge options. + autofliplinklevel = bakautofliplinklevel; + b->fliplinklevel = bakfliplinklevel; + b->flipstarsize = bakmaxflipstarsize; + + delete badtetrahedrons; + badtetrahedrons = NULL; + delete badqual_tets_pool; + badqual_tets_pool = NULL; + delete unsplit_badtets; + unsplit_badtets = NULL; +} + +// // +// // +//== optimize_cxx ============================================================// + +//== meshstat_cxx ============================================================// +// // +// // + +//============================================================================// +// // +// printfcomma() Print a (large) number with the 'thousands separator'. // +// // +// The following code was simply copied from "stackoverflow". // +// // +//============================================================================// + +void tetgenmesh::printfcomma(unsigned long n) +{ + unsigned long n2 = 0; + int scale = 1; + while (n >= 1000) { + n2 = n2 + scale * (n % 1000); + n /= 1000; + scale *= 1000; + } + printf ("%ld", n); + while (scale != 1) { + scale /= 1000; + n = n2 / scale; + n2 = n2 % scale; + printf (",%03ld", n); + } +} + +//============================================================================// +// // +// checkmesh() Test the mesh for topological consistency. // +// // +// If 'topoflag' is set, only check the topological connection of the mesh, // +// i.e., do not report degenerated or inverted elements. // +// // +//============================================================================// + +int tetgenmesh::check_mesh(int topoflag) +{ + triface tetloop, neightet, symtet; + point pa, pb, pc, pd; + REAL ori; + int horrors, i; + + if (!b->quiet) { + printf(" Checking consistency of mesh...\n"); + } + + horrors = 0; + tetloop.ver = 0; + // Run through the list of tetrahedra, checking each one. + tetrahedrons->traversalinit(); + tetloop.tet = alltetrahedrontraverse(); + while (tetloop.tet != (tetrahedron *) NULL) { + // Check all four faces of the tetrahedron. + for (tetloop.ver = 0; tetloop.ver < 4; tetloop.ver++) { + pa = org(tetloop); + pb = dest(tetloop); + pc = apex(tetloop); + pd = oppo(tetloop); + if (tetloop.ver == 0) { // Only test for inversion once. + if (!ishulltet(tetloop)) { // Only do test if it is not a hull tet. + if (!topoflag) { + ori = orient3d(pa, pb, pc, pd); + if (ori >= 0.0) { + printf(" !! !! %s ", ori > 0.0 ? "Inverted" : "Degenerated"); + printf(" (%d, %d, %d, %d) (ori = %.17g)\n", pointmark(pa), + pointmark(pb), pointmark(pc), pointmark(pd), ori); + horrors++; + } + } + } + if (infected(tetloop)) { + // This may be a bug. Report it. + printf(" !! (%d, %d, %d, %d) is infected.\n", pointmark(pa), + pointmark(pb), pointmark(pc), pointmark(pd)); + horrors++; + } + if (marktested(tetloop)) { + // This may be a bug. Report it. + printf(" !! (%d, %d, %d, %d) is marked.\n", pointmark(pa), + pointmark(pb), pointmark(pc), pointmark(pd)); + horrors++; + } + } + if (tetloop.tet[tetloop.ver] == NULL) { + printf(" !! !! No neighbor at face (%d, %d, %d).\n", pointmark(pa), + pointmark(pb), pointmark(pc)); + horrors++; + } else { + // Find the neighboring tetrahedron on this face. + fsym(tetloop, neightet); + if (neightet.tet != NULL) { + // Check that the tetrahedron's neighbor knows it's a neighbor. + fsym(neightet, symtet); + if ((tetloop.tet != symtet.tet) || (tetloop.ver != symtet.ver)) { + printf(" !! !! Asymmetric tetra-tetra bond:\n"); + if (tetloop.tet == symtet.tet) { + printf(" (Right tetrahedron, wrong orientation)\n"); + } + printf(" First: (%d, %d, %d, %d)\n", pointmark(pa), + pointmark(pb), pointmark(pc), pointmark(pd)); + printf(" Second: (%d, %d, %d, %d)\n", pointmark(org(neightet)), + pointmark(dest(neightet)), pointmark(apex(neightet)), + pointmark(oppo(neightet))); + horrors++; + } + // Check if they have the same edge (the bond() operation). + if ((org(neightet) != pb) || (dest(neightet) != pa)) { + printf(" !! !! Wrong edge-edge bond:\n"); + printf(" First: (%d, %d, %d, %d)\n", pointmark(pa), + pointmark(pb), pointmark(pc), pointmark(pd)); + printf(" Second: (%d, %d, %d, %d)\n", pointmark(org(neightet)), + pointmark(dest(neightet)), pointmark(apex(neightet)), + pointmark(oppo(neightet))); + horrors++; + } + // Check if they have the same apex. + if (apex(neightet) != pc) { + printf(" !! !! Wrong face-face bond:\n"); + printf(" First: (%d, %d, %d, %d)\n", pointmark(pa), + pointmark(pb), pointmark(pc), pointmark(pd)); + printf(" Second: (%d, %d, %d, %d)\n", pointmark(org(neightet)), + pointmark(dest(neightet)), pointmark(apex(neightet)), + pointmark(oppo(neightet))); + horrors++; + } + // Check if they have the same opposite. + if (oppo(neightet) == pd) { + printf(" !! !! Two identical tetra:\n"); + printf(" First: (%d, %d, %d, %d)\n", pointmark(pa), + pointmark(pb), pointmark(pc), pointmark(pd)); + printf(" Second: (%d, %d, %d, %d)\n", pointmark(org(neightet)), + pointmark(dest(neightet)), pointmark(apex(neightet)), + pointmark(oppo(neightet))); + horrors++; + } + } else { + printf(" !! !! Tet-face has no neighbor (%d, %d, %d) - %d:\n", + pointmark(pa), pointmark(pb), pointmark(pc), pointmark(pd)); + horrors++; + } + } + if (facemarked(tetloop)) { + // This may be a bug. Report it. + printf(" !! tetface (%d, %d, %d) %d is marked.\n", pointmark(pa), + pointmark(pb), pointmark(pc), pointmark(pd)); + } + } + // Check the six edges of this tet. + for (i = 0; i < 6; i++) { + tetloop.ver = edge2ver[i]; + if (edgemarked(tetloop)) { + // This may be a bug. Report it. + printf(" !! tetedge (%d, %d) %d, %d is marked.\n", + pointmark(org(tetloop)), pointmark(dest(tetloop)), + pointmark(apex(tetloop)), pointmark(oppo(tetloop))); + } + } + tetloop.tet = alltetrahedrontraverse(); + } + if (horrors == 0) { + if (!b->quiet) { + printf(" In my studied opinion, the mesh appears to be consistent.\n"); + } + } else { + printf(" !! !! !! !! %d %s witnessed.\n", horrors, + horrors > 1 ? "abnormity" : "abnormities"); + } + + return horrors; +} + +//============================================================================// +// // +// checkshells() Test the boundary mesh for topological consistency. // +// // +//============================================================================// + +int tetgenmesh::check_shells() +{ + triface neightet, symtet; + face shloop, spinsh, nextsh; + face checkseg; + point pa, pb; + int bakcount; + int horrors, i; + + if (!b->quiet) { + printf(" Checking consistency of the mesh boundary...\n"); + } + horrors = 0; + + void **bakpathblock = subfaces->pathblock; + void *bakpathitem = subfaces->pathitem; + int bakpathitemsleft = subfaces->pathitemsleft; + int bakalignbytes = subfaces->alignbytes; + + subfaces->traversalinit(); + shloop.sh = shellfacetraverse(subfaces); + while (shloop.sh != NULL) { + shloop.shver = 0; + for (i = 0; i < 3; i++) { + // Check the face ring at this edge. + pa = sorg(shloop); + pb = sdest(shloop); + spinsh = shloop; + spivot(spinsh, nextsh); + bakcount = horrors; + while ((nextsh.sh != NULL) && (nextsh.sh != shloop.sh)) { + if (nextsh.sh[3] == NULL) { + printf(" !! !! Wrong subface-subface connection (Dead subface).\n"); + printf(" First: x%lu (%d, %d, %d).\n", (uintptr_t) spinsh.sh, + pointmark(sorg(spinsh)), pointmark(sdest(spinsh)), + pointmark(sapex(spinsh))); + printf(" Second: x%lu (DEAD)\n", (uintptr_t) nextsh.sh); + horrors++; + break; + } + // check if they have the same edge. + if (!(((sorg(nextsh) == pa) && (sdest(nextsh) == pb)) || + ((sorg(nextsh) == pb) && (sdest(nextsh) == pa)))) { + printf(" !! !! Wrong subface-subface connection.\n"); + printf(" First: x%lu (%d, %d, %d).\n", (uintptr_t) spinsh.sh, + pointmark(sorg(spinsh)), pointmark(sdest(spinsh)), + pointmark(sapex(spinsh))); + printf(" Scond: x%lu (%d, %d, %d).\n", (uintptr_t) nextsh.sh, + pointmark(sorg(nextsh)), pointmark(sdest(nextsh)), + pointmark(sapex(nextsh))); + horrors++; + break; + } + // Check they should not have the same apex. + if (sapex(nextsh) == sapex(spinsh)) { + printf(" !! !! Existing two duplicated subfaces.\n"); + printf(" First: x%lu (%d, %d, %d).\n", (uintptr_t) spinsh.sh, + pointmark(sorg(spinsh)), pointmark(sdest(spinsh)), + pointmark(sapex(spinsh))); + printf(" Scond: x%lu (%d, %d, %d).\n", (uintptr_t) nextsh.sh, + pointmark(sorg(nextsh)), pointmark(sdest(nextsh)), + pointmark(sapex(nextsh))); + horrors++; + break; + } + spinsh = nextsh; + spivot(spinsh, nextsh); + } + // Check subface-subseg bond. + sspivot(shloop, checkseg); + if (checkseg.sh != NULL) { + if (checkseg.sh[3] == NULL) { + printf(" !! !! Wrong subface-subseg connection (Dead subseg).\n"); + printf(" Sub: x%lu (%d, %d, %d).\n", (uintptr_t) shloop.sh, + pointmark(sorg(shloop)), pointmark(sdest(shloop)), + pointmark(sapex(shloop))); + printf(" Sub: x%lu (Dead)\n", (uintptr_t) checkseg.sh); + horrors++; + } else { + if (!(((sorg(checkseg) == pa) && (sdest(checkseg) == pb)) || + ((sorg(checkseg) == pb) && (sdest(checkseg) == pa)))) { + printf(" !! !! Wrong subface-subseg connection.\n"); + printf(" Sub: x%lu (%d, %d, %d).\n", (uintptr_t) shloop.sh, + pointmark(sorg(shloop)), pointmark(sdest(shloop)), + pointmark(sapex(shloop))); + printf(" Seg: x%lu (%d, %d).\n", (uintptr_t) checkseg.sh, + pointmark(sorg(checkseg)), pointmark(sdest(checkseg))); + horrors++; + } + } + } + if (horrors > bakcount) break; // An error detected. + senextself(shloop); + } + // Check tet-subface connection. + stpivot(shloop, neightet); + if (neightet.tet != NULL) { + if (neightet.tet[4] == NULL) { + printf(" !! !! Wrong sub-to-tet connection (Dead tet)\n"); + printf(" Sub: x%lu (%d, %d, %d).\n", (uintptr_t) shloop.sh, + pointmark(sorg(shloop)), pointmark(sdest(shloop)), + pointmark(sapex(shloop))); + printf(" Tet: x%lu (DEAD)\n", (uintptr_t) neightet.tet); + horrors++; + } else { + if (!((sorg(shloop) == org(neightet)) && + (sdest(shloop) == dest(neightet)))) { + printf(" !! !! Wrong sub-to-tet connection\n"); + printf(" Sub: x%lu (%d, %d, %d).\n", (uintptr_t) shloop.sh, + pointmark(sorg(shloop)), pointmark(sdest(shloop)), + pointmark(sapex(shloop))); + printf(" Tet: x%lu (%d, %d, %d, %d).\n", + (uintptr_t) neightet.tet, pointmark(org(neightet)), + pointmark(dest(neightet)), pointmark(apex(neightet)), + pointmark(oppo(neightet))); + horrors++; + } + tspivot(neightet, spinsh); + if (!((sorg(spinsh) == org(neightet)) && + (sdest(spinsh) == dest(neightet)))) { + printf(" !! !! Wrong tet-sub connection.\n"); + printf(" Sub: x%lu (%d, %d, %d).\n", (uintptr_t) spinsh.sh, + pointmark(sorg(spinsh)), pointmark(sdest(spinsh)), + pointmark(sapex(spinsh))); + printf(" Tet: x%lu (%d, %d, %d, %d).\n", + (uintptr_t) neightet.tet, pointmark(org(neightet)), + pointmark(dest(neightet)), pointmark(apex(neightet)), + pointmark(oppo(neightet))); + horrors++; + } + fsym(neightet, symtet); + tspivot(symtet, spinsh); + if (spinsh.sh != NULL) { + if (!((sorg(spinsh) == org(symtet)) && + (sdest(spinsh) == dest(symtet)))) { + printf(" !! !! Wrong tet-sub connection.\n"); + printf(" Sub: x%lu (%d, %d, %d).\n", (uintptr_t) spinsh.sh, + pointmark(sorg(spinsh)), pointmark(sdest(spinsh)), + pointmark(sapex(spinsh))); + printf(" Tet: x%lu (%d, %d, %d, %d).\n", + (uintptr_t) symtet.tet, pointmark(org(symtet)), + pointmark(dest(symtet)), pointmark(apex(symtet)), + pointmark(oppo(symtet))); + horrors++; + } + } else { + printf(" Warning: Broken tet-sub-tet connection.\n"); + } + } + } + if (sinfected(shloop)) { + // This may be a bug. report it. + printf(" !! A infected subface: (%d, %d, %d).\n", + pointmark(sorg(shloop)), pointmark(sdest(shloop)), + pointmark(sapex(shloop))); + } + if (smarktested(shloop)) { + // This may be a bug. report it. + printf(" !! A marked subface: (%d, %d, %d).\n", pointmark(sorg(shloop)), + pointmark(sdest(shloop)), pointmark(sapex(shloop))); + } + shloop.sh = shellfacetraverse(subfaces); + } + + if (horrors == 0) { + if (!b->quiet) { + printf(" Mesh boundaries connected correctly.\n"); + } + } else { + printf(" !! !! !! !! %d boundary connection viewed with horror.\n", + horrors); + } + + subfaces->pathblock = bakpathblock; + subfaces->pathitem = bakpathitem; + subfaces->pathitemsleft = bakpathitemsleft; + subfaces->alignbytes = bakalignbytes; + + return horrors; +} + +//============================================================================// +// // +// checksegments() Check the connections between tetrahedra and segments. // +// // +//============================================================================// + +int tetgenmesh::check_segments() +{ + triface tetloop, neightet, spintet; + shellface *segs; + face neighsh, spinsh, checksh; + face sseg, checkseg; + point pa, pb; + int miscount; + int t1ver; + int horrors, i; + + + if (!b->quiet) { + printf(" Checking tet->seg connections...\n"); + } + + horrors = 0; + tetrahedrons->traversalinit(); + tetloop.tet = tetrahedrontraverse(); + while (tetloop.tet != NULL) { + // Loop the six edges of the tet. + if (tetloop.tet[8] != NULL) { + segs = (shellface *) tetloop.tet[8]; + for (i = 0; i < 6; i++) { + sdecode(segs[i], sseg); + if (sseg.sh != NULL) { + // Get the edge of the tet. + tetloop.ver = edge2ver[i]; + // Check if they are the same edge. + pa = (point) sseg.sh[3]; + pb = (point) sseg.sh[4]; + if (!(((org(tetloop) == pa) && (dest(tetloop) == pb)) || + ((org(tetloop) == pb) && (dest(tetloop) == pa)))) { + printf(" !! Wrong tet-seg connection.\n"); + printf(" Tet: x%lu (%d, %d, %d, %d) - Seg: x%lu (%d, %d).\n", + (uintptr_t) tetloop.tet, pointmark(org(tetloop)), + pointmark(dest(tetloop)), pointmark(apex(tetloop)), + pointmark(oppo(tetloop)), (uintptr_t) sseg.sh, + pointmark(pa), pointmark(pb)); + horrors++; + } else { + // Loop all tets sharing at this edge. + neightet = tetloop; + do { + tsspivot1(neightet, checkseg); + if (checkseg.sh != sseg.sh) { + printf(" !! Wrong tet->seg connection.\n"); + printf(" Tet: x%lu (%d, %d, %d, %d) - ", + (uintptr_t) neightet.tet, pointmark(org(neightet)), + pointmark(dest(neightet)), pointmark(apex(neightet)), + pointmark(oppo(neightet))); + if (checkseg.sh != NULL) { + printf("Seg x%lu (%d, %d).\n", (uintptr_t) checkseg.sh, + pointmark(sorg(checkseg)),pointmark(sdest(checkseg))); + } else { + printf("Seg: NULL.\n"); + } + horrors++; + } + fnextself(neightet); + } while (neightet.tet != tetloop.tet); + } + // Check the seg->tet pointer. + sstpivot1(sseg, neightet); + if (neightet.tet == NULL) { + printf(" !! Wrong seg->tet connection (A NULL tet).\n"); + horrors++; + } else { + if (!(((org(neightet) == pa) && (dest(neightet) == pb)) || + ((org(neightet) == pb) && (dest(neightet) == pa)))) { + printf(" !! Wrong seg->tet connection (Wrong edge).\n"); + printf(" Tet: x%lu (%d, %d, %d, %d) - Seg: x%lu (%d, %d).\n", + (uintptr_t) neightet.tet, pointmark(org(neightet)), + pointmark(dest(neightet)), pointmark(apex(neightet)), + pointmark(oppo(neightet)), (uintptr_t) sseg.sh, + pointmark(pa), pointmark(pb)); + horrors++; + } + } + } + } + } + // Loop the six edge of this tet. + neightet.tet = tetloop.tet; + for (i = 0; i < 6; i++) { + neightet.ver = edge2ver[i]; + if (edgemarked(neightet)) { + // A possible bug. Report it. + printf(" !! A marked edge: (%d, %d, %d, %d) -- x%lu %d.\n", + pointmark(org(neightet)), pointmark(dest(neightet)), + pointmark(apex(neightet)), pointmark(oppo(neightet)), + (uintptr_t) neightet.tet, neightet.ver); + // Check if all tets at the edge are marked. + spintet = neightet; + while (1) { + fnextself(spintet); + if (!edgemarked(spintet)) { + printf(" !! !! An unmarked edge (%d, %d, %d, %d) -- x%lu %d.\n", + pointmark(org(spintet)), pointmark(dest(spintet)), + pointmark(apex(spintet)), pointmark(oppo(spintet)), + (uintptr_t) spintet.tet, spintet.ver); + horrors++; + } + if (spintet.tet == neightet.tet) break; + } + } + } + tetloop.tet = tetrahedrontraverse(); + } + + if (!b->quiet) { + printf(" Checking seg->tet connections...\n"); + } + + miscount = 0; // Count the number of unrecovered segments. + subsegs->traversalinit(); + sseg.shver = 0; + sseg.sh = shellfacetraverse(subsegs); + while (sseg.sh != NULL) { + pa = sorg(sseg); + pb = sdest(sseg); + spivot(sseg, neighsh); + if (neighsh.sh != NULL) { + spinsh = neighsh; + while (1) { + // Check seg-subface bond. + point e1 = sorg(spinsh); + point e2 = sdest(spinsh); + if (((e1 == pa) && (e2 == pb)) || + ((e1 == pb) && (e2 == pa))) { + // Keep the same rotate direction. + //if (sorg(spinsh) != pa) { + // sesymself(spinsh); + // printf(" !! Wrong ori at subface (%d, %d, %d) -- x%lu %d\n", + // pointmark(sorg(spinsh)), pointmark(sdest(spinsh)), + // pointmark(sapex(spinsh)), (uintptr_t) spinsh.sh, + // spinsh.shver); + // horrors++; + //} + stpivot(spinsh, spintet); + if (spintet.tet != NULL) { + // Check if all tets at this segment. + while (1) { + tsspivot1(spintet, checkseg); + if (checkseg.sh == NULL) { + printf(" !! !! No seg at tet (%d, %d, %d, %d) -- x%lu %d\n", + pointmark(org(spintet)), pointmark(dest(spintet)), + pointmark(apex(spintet)), pointmark(oppo(spintet)), + (uintptr_t) spintet.tet, spintet.ver); + horrors++; + } + if (checkseg.sh != sseg.sh) { + printf(" !! !! Wrong seg (%d, %d) at tet (%d, %d, %d, %d)\n", + pointmark(sorg(checkseg)), pointmark(sdest(checkseg)), + pointmark(org(spintet)), pointmark(dest(spintet)), + pointmark(apex(spintet)), pointmark(oppo(spintet))); + horrors++; + } + fnextself(spintet); + // Stop at the next subface. + tspivot(spintet, checksh); + if (checksh.sh != NULL) break; + } // while (1) + } + } else { + point e3 = sapex(spinsh); + printf(" !! Wrong seg-subface (%d, %d) - (%d, %d, %d) connect\n", + pointmark(pa), pointmark(pb), + (e1 != NULL ? pointmark(e1) : -1), + (e2 != NULL ? pointmark(e2) : -1), + (e3 != NULL ? pointmark(e3) : -1) + //(uintptr_t) spinsh.sh, + //spinsh.shver + ); + horrors++; + break; + } // if pa, pb + spivotself(spinsh); + if (spinsh.sh == NULL) break; // A dangling segment. + if (spinsh.sh == neighsh.sh) break; + } // while (1) + } // if (neighsh.sh != NULL) + // Count the number of "un-recovered" segments. + sstpivot1(sseg, neightet); + if (neightet.tet == NULL) { + miscount++; + } + sseg.sh = shellfacetraverse(subsegs); + } + + if (!b->quiet) { + printf(" Checking seg->seg connections...\n"); + } + + points->traversalinit(); + pa = pointtraverse(); + while (pa != NULL) { + if (pointtype(pa) == FREESEGVERTEX) { + // There should be two subsegments connected at 'pa'. + // Get a subsegment containing 'pa'. + sdecode(point2sh(pa), sseg); + if ((sseg.sh == NULL) || sseg.sh[3] == NULL) { + printf(" !! Dead point-to-seg pointer at point %d.\n", + pointmark(pa)); + horrors++; + } else { + sseg.shver = 0; + if (sorg(sseg) != pa) { + if (sdest(sseg) != pa) { + printf(" !! Wrong point-to-seg pointer at point %d.\n", + pointmark(pa)); + horrors++; + } else { + // Find the next subsegment at 'pa'. + senext(sseg, checkseg); + if ((checkseg.sh == NULL) || (checkseg.sh[3] == NULL)) { + printf(" !! Dead seg-seg connection at point %d.\n", + pointmark(pa)); + horrors++; + } else { + spivotself(checkseg); + checkseg.shver = 0; + if ((sorg(checkseg) != pa) && (sdest(checkseg) != pa)) { + printf(" !! Wrong seg-seg connection at point %d.\n", + pointmark(pa)); + horrors++; + } + } + } + } else { + // Find the previous subsegment at 'pa'. + senext2(sseg, checkseg); + if ((checkseg.sh == NULL) || (checkseg.sh[3] == NULL)) { + printf(" !! Dead seg-seg connection at point %d.\n", + pointmark(pa)); + horrors++; + } else { + spivotself(checkseg); + checkseg.shver = 0; + if ((sorg(checkseg) != pa) && (sdest(checkseg) != pa)) { + printf(" !! Wrong seg-seg connection at point %d.\n", + pointmark(pa)); + horrors++; + } + } + } + } + } + pa = pointtraverse(); + } + + if (horrors == 0) { + printf(" Segments are connected properly.\n"); + } else { + printf(" !! !! !! !! Found %d missing connections.\n", horrors); + } + if (miscount > 0) { + printf(" !! !! Found %d missing segments.\n", miscount); + } + + return horrors; +} + +//============================================================================// +// // +// checkdelaunay() Ensure that the mesh is (constrained) Delaunay. // +// // +//============================================================================// + +int tetgenmesh::check_delaunay(int perturb) +{ + triface tetloop; + triface symtet; + face checksh; + point pa, pb, pc, pd, pe; + REAL sign; + int ndcount; // Count the non-locally Delaunay faces. + int horrors; + + if (!b->quiet) { + printf(" Checking Delaunay property of the mesh...\n"); + } + + ndcount = 0; + horrors = 0; + tetloop.ver = 0; + // Run through the list of triangles, checking each one. + tetrahedrons->traversalinit(); + tetloop.tet = tetrahedrontraverse(); + while (tetloop.tet != (tetrahedron *) NULL) { + // Check all four faces of the tetrahedron. + for (tetloop.ver = 0; tetloop.ver < 4; tetloop.ver++) { + fsym(tetloop, symtet); + // Only do test if its adjoining tet is not a hull tet or its pointer + // is larger (to ensure that each pair isn't tested twice). + if (((point) symtet.tet[7] != dummypoint)&&(tetloop.tet < symtet.tet)) { + pa = org(tetloop); + pb = dest(tetloop); + pc = apex(tetloop); + pd = oppo(tetloop); + pe = oppo(symtet); + if (perturb) { + sign = insphere_s(pa, pb, pc, pd, pe); + } else { + sign = insphere(pa, pb, pc, pd, pe); + } + if (sign < 0.0) { + ndcount++; + if (checksubfaceflag) { + tspivot(tetloop, checksh); + } + if (checksh.sh == NULL) { + printf(" !! Non-locally Delaunay (%d, %d, %d) - %d, %d\n", + pointmark(pa), pointmark(pb), pointmark(pc), pointmark(pd), + pointmark(pe)); + horrors++; + } + } + } + } + tetloop.tet = tetrahedrontraverse(); + } + + if (horrors == 0) { + if (!b->quiet) { + if (ndcount > 0) { + printf(" The mesh is constrained Delaunay.\n"); + } else { + printf(" The mesh is Delaunay.\n"); + } + } + } else { + printf(" !! !! !! !! Found %d non-Delaunay faces.\n", horrors); + } + + return horrors; +} + +//============================================================================// +// // +// Check if the current tetrahedralization is (constrained) regular. // +// // +// The parameter 'type' determines which regularity should be checked: // +// - 0: check the Delaunay property. // +// - 1: check the Delaunay property with symbolic perturbation. // +// - 2: check the regular property, the weights are stored in p[3]. // +// - 3: check the regular property with symbolic perturbation. // +// // +//============================================================================// + +int tetgenmesh::check_regular(int type) +{ + triface tetloop; + triface symtet; + face checksh; + point p[5]; + REAL sign; + int ndcount; // Count the non-locally Delaunay faces. + int horrors; + + if (!b->quiet) { + printf(" Checking %s %s property of the mesh...\n", + (type & 2) == 0 ? "Delaunay" : "regular", + (type & 1) == 0 ? " " : "(s)"); + } + + // Make sure orient3d(p[1], p[0], p[2], p[3]) > 0; + // Hence if (insphere(p[1], p[0], p[2], p[3], p[4]) > 0) means that + // p[4] lies inside the circumsphere of p[1], p[0], p[2], p[3]. + // The same if orient4d(p[1], p[0], p[2], p[3], p[4]) > 0 means that + // p[4] lies below the oriented hyperplane passing through + // p[1], p[0], p[2], p[3]. + + ndcount = 0; + horrors = 0; + tetloop.ver = 0; + // Run through the list of triangles, checking each one. + tetrahedrons->traversalinit(); + tetloop.tet = tetrahedrontraverse(); + while (tetloop.tet != (tetrahedron *) NULL) { + // Check all four faces of the tetrahedron. + for (tetloop.ver = 0; tetloop.ver < 4; tetloop.ver++) { + fsym(tetloop, symtet); + // Only do test if its adjoining tet is not a hull tet or its pointer + // is larger (to ensure that each pair isn't tested twice). + if (((point) symtet.tet[7] != dummypoint)&&(tetloop.tet < symtet.tet)) { + p[0] = org(tetloop); // pa + p[1] = dest(tetloop); // pb + p[2] = apex(tetloop); // pc + p[3] = oppo(tetloop); // pd + p[4] = oppo(symtet); // pe + + if (type == 0) { + sign = insphere(p[1], p[0], p[2], p[3], p[4]); + } else if (type == 1) { + sign = insphere_s(p[1], p[0], p[2], p[3], p[4]); + } else if (type == 2) { + sign = orient4d(p[1], p[0], p[2], p[3], p[4], + p[1][3], p[0][3], p[2][3], p[3][3], p[4][3]); + } else { // type == 3 + sign = orient4d_s(p[1], p[0], p[2], p[3], p[4], + p[1][3], p[0][3], p[2][3], p[3][3], p[4][3]); + } + + if (sign > 0.0) { + ndcount++; + if (checksubfaceflag) { + tspivot(tetloop, checksh); + } + if (checksh.sh == NULL) { + printf(" !! Non-locally %s (%d, %d, %d) - %d, %d\n", + (type & 2) == 0 ? "Delaunay" : "regular", + pointmark(p[0]), pointmark(p[1]), pointmark(p[2]), + pointmark(p[3]), pointmark(p[4])); + horrors++; + } + } + } + } + tetloop.tet = tetrahedrontraverse(); + } + + if (horrors == 0) { + if (!b->quiet) { + if (ndcount > 0) { + printf(" The mesh is constrained %s.\n", + (type & 2) == 0 ? "Delaunay" : "regular"); + } else { + printf(" The mesh is %s.\n", (type & 2) == 0 ? "Delaunay" : "regular"); + } + } + } else { + printf(" !! !! !! !! Found %d non-%s faces.\n", horrors, + (type & 2) == 0 ? "Delaunay" : "regular"); + } + + return horrors; +} + +//============================================================================// +// // +// checkconforming() Ensure that the mesh is conforming Delaunay. // +// // +// If 'flag' is 1, only check subsegments. If 'flag' is 2, check subfaces. // +// If 'flag' is 3, check both subsegments and subfaces. // +// // +//============================================================================// + +int tetgenmesh::check_conforming(int flag) +{ + triface searchtet, neightet, spintet; + face shloop; + face segloop; + point eorg, edest, eapex, pa, pb, pc; + REAL cent[3], radius, dist, diff, rd, len; + bool enq; + int encsubsegs, encsubfaces; + int t1ver; + int i; + + REAL A[4][4], rhs[4], D; + int indx[4]; + REAL elen[3]; + + encsubsegs = 0; + + if (flag & 1) { + if (!b->quiet) { + printf(" Checking conforming property of segments...\n"); + } + encsubsegs = 0; + + // Run through the list of subsegments, check each one. + subsegs->traversalinit(); + segloop.sh = shellfacetraverse(subsegs); + while (segloop.sh != (shellface *) NULL) { + eorg = (point) segloop.sh[3]; + edest = (point) segloop.sh[4]; + radius = 0.5 * distance(eorg, edest); + for (i = 0; i < 3; i++) cent[i] = 0.5 * (eorg[i] + edest[i]); + + enq = false; + sstpivot1(segloop, neightet); + if (neightet.tet != NULL) { + spintet = neightet; + while (1) { + eapex= apex(spintet); + if (eapex != dummypoint) { + dist = distance(eapex, cent); + diff = dist - radius; + if (fabs(diff) / radius <= b->epsilon) diff = 0.0; // Rounding. + if (diff < 0) { + enq = true; break; + } + } + fnextself(spintet); + if (spintet.tet == neightet.tet) break; + } + } + if (enq) { + printf(" !! !! Non-conforming segment: (%d, %d)\n", + pointmark(eorg), pointmark(edest)); + encsubsegs++; + } + segloop.sh = shellfacetraverse(subsegs); + } + + if (encsubsegs == 0) { + if (!b->quiet) { + printf(" The segments are conforming Delaunay.\n"); + } + } else { + printf(" !! !! %d subsegments are non-conforming.\n", encsubsegs); + } + } // if (flag & 1) + + encsubfaces = 0; + + if (flag & 2) { + if (!b->quiet) { + printf(" Checking conforming property of subfaces...\n"); + } + + // Run through the list of subfaces, check each one. + subfaces->traversalinit(); + shloop.sh = shellfacetraverse(subfaces); + while (shloop.sh != (shellface *) NULL) { + pa = (point) shloop.sh[3]; + pb = (point) shloop.sh[4]; + pc = (point) shloop.sh[5]; + + // Compute the coefficient matrix A (3x3). + A[0][0] = pb[0] - pa[0]; + A[0][1] = pb[1] - pa[1]; + A[0][2] = pb[2] - pa[2]; // vector V1 (pa->pb) + A[1][0] = pc[0] - pa[0]; + A[1][1] = pc[1] - pa[1]; + A[1][2] = pc[2] - pa[2]; // vector V2 (pa->pc) + cross(A[0], A[1], A[2]); // vector V3 (V1 X V2) + + // Compute the right hand side vector b (3x1). + elen[0] = dot(A[0], A[0]); + elen[1] = dot(A[1], A[1]); + rhs[0] = 0.5 * elen[0]; + rhs[1] = 0.5 * elen[1]; + rhs[2] = 0.0; + + if (lu_decmp(A, 3, indx, &D, 0)) { + lu_solve(A, 3, indx, rhs, 0); + cent[0] = pa[0] + rhs[0]; + cent[1] = pa[1] + rhs[1]; + cent[2] = pa[2] + rhs[2]; + rd = sqrt(rhs[0] * rhs[0] + rhs[1] * rhs[1] + rhs[2] * rhs[2]); + + // Check if this subface is encroached. + for (i = 0; i < 2; i++) { + stpivot(shloop, searchtet); + if (!ishulltet(searchtet)) { + len = distance(oppo(searchtet), cent); + if ((fabs(len - rd) / rd) < b->epsilon) len = rd; // Rounding. + if (len < rd) { + printf(" !! !! Non-conforming subface: (%d, %d, %d)\n", + pointmark(pa), pointmark(pb), pointmark(pc)); + encsubfaces++; + enq = true; break; + } + } + sesymself(shloop); + } + } + shloop.sh = shellfacetraverse(subfaces); + } + + if (encsubfaces == 0) { + if (!b->quiet) { + printf(" The subfaces are conforming Delaunay.\n"); + } + } else { + printf(" !! !! %d subfaces are non-conforming.\n", encsubfaces); + } + } // if (flag & 2) + + return encsubsegs + encsubfaces; +} + +//============================================================================// +// // +// qualitystatistics() Print statistics about the quality of the mesh. // +// // +//============================================================================// + +void tetgenmesh::qualitystatistics() +{ + triface tetloop, neightet; + point p[4]; + char sbuf[128]; + REAL radiusratiotable[12]; + REAL aspectratiotable[12]; + REAL A[4][4], rhs[4], D; + REAL V[6][3], N[4][3], H[4]; // edge-vectors, face-normals, face-heights. + REAL edgelength[6], alldihed[6], faceangle[3]; + REAL shortest, longest; + REAL smallestvolume, biggestvolume; + REAL smallestratio, biggestratio; + REAL smallestradiusratio, biggestradiusratio; // radius-edge ratio. + REAL smallestdiangle, biggestdiangle; + REAL smallestfaangle, biggestfaangle; + REAL total_tet_vol, total_tetprism_vol; + REAL tetvol, minaltitude; + REAL cirradius, minheightinv; // insradius; + REAL shortlen, longlen; + REAL tetaspect, tetradius; + REAL smalldiangle, bigdiangle; + REAL smallfaangle, bigfaangle; + unsigned long radiustable[12]; + unsigned long aspecttable[16]; + unsigned long dihedangletable[18]; + unsigned long faceangletable[18]; + int indx[4]; + int radiusindex; + int aspectindex; + int tendegree; + int i, j; + // Report the tet which has the biggest radius-edge ratio. + triface biggestradiusratiotet; + // Report the tet which has the biggest volume. + triface biggestvolumetet; + triface longestedgetet; + + printf("Mesh quality statistics:\n\n"); + + shortlen = longlen = 0.0; + smalldiangle = bigdiangle = 0.0; + total_tet_vol = 0.0; + total_tetprism_vol = 0.0; + + radiusratiotable[0] = 0.707; radiusratiotable[1] = 1.0; + radiusratiotable[2] = 1.1; radiusratiotable[3] = 1.2; + radiusratiotable[4] = 1.4; radiusratiotable[5] = 1.6; + radiusratiotable[6] = 1.8; radiusratiotable[7] = 2.0; + radiusratiotable[8] = 2.5; radiusratiotable[9] = 3.0; + radiusratiotable[10] = 10.0; radiusratiotable[11] = 0.0; + + aspectratiotable[0] = 1.5; aspectratiotable[1] = 2.0; + aspectratiotable[2] = 2.5; aspectratiotable[3] = 3.0; + aspectratiotable[4] = 4.0; aspectratiotable[5] = 6.0; + aspectratiotable[6] = 10.0; aspectratiotable[7] = 15.0; + aspectratiotable[8] = 25.0; aspectratiotable[9] = 50.0; + aspectratiotable[10] = 100.0; aspectratiotable[11] = 0.0; + + for (i = 0; i < 12; i++) radiustable[i] = 0l; + for (i = 0; i < 12; i++) aspecttable[i] = 0l; + for (i = 0; i < 18; i++) dihedangletable[i] = 0l; + for (i = 0; i < 18; i++) faceangletable[i] = 0l; + + minaltitude = xmax - xmin + ymax - ymin + zmax - zmin; + minaltitude = minaltitude * minaltitude; + shortest = minaltitude; + longest = 0.0; + smallestvolume = minaltitude; + biggestvolume = 0.0; + smallestratio = smallestradiusratio = 1e+16; // minaltitude; + biggestratio = biggestradiusratio = 0.0; + smallestdiangle = smallestfaangle = 180.0; + biggestdiangle = biggestfaangle = 0.0; + + + int attrnum = numelemattrib - 1; + + // Loop all elements, calculate quality parameters for each element. + tetrahedrons->traversalinit(); + tetloop.tet = tetrahedrontraverse(); + + //int tidx = 1; + + while (tetloop.tet != (tetrahedron *) NULL) { + + if (b->convex) { + // Skip tets in the exterior. + if (elemattribute(tetloop.tet, attrnum) == -1.0) { + tetloop.tet = tetrahedrontraverse(); + continue; + } + } + + // Get four vertices: p0, p1, p2, p3. + for (i = 0; i < 4; i++) p[i] = (point) tetloop.tet[4 + i]; + + // Get the tet volume. + tetvol = orient3dfast(p[1], p[0], p[2], p[3]) / 6.0; + total_tet_vol += tetvol; + total_tetprism_vol += tetprismvol(p[0], p[1], p[2], p[3]); + + // Calculate the largest and smallest volume. + if (tetvol < smallestvolume) { + smallestvolume = tetvol; + } + if (tetvol > biggestvolume) { + biggestvolume = tetvol; + biggestvolumetet.tet = tetloop.tet; + } + + // Set the edge vectors: V[0], ..., V[5] + for (i = 0; i < 3; i++) V[0][i] = p[0][i] - p[3][i]; // V[0]: p3->p0. + for (i = 0; i < 3; i++) V[1][i] = p[1][i] - p[3][i]; // V[1]: p3->p1. + for (i = 0; i < 3; i++) V[2][i] = p[2][i] - p[3][i]; // V[2]: p3->p2. + for (i = 0; i < 3; i++) V[3][i] = p[1][i] - p[0][i]; // V[3]: p0->p1. + for (i = 0; i < 3; i++) V[4][i] = p[2][i] - p[1][i]; // V[4]: p1->p2. + for (i = 0; i < 3; i++) V[5][i] = p[0][i] - p[2][i]; // V[5]: p2->p0. + + // Get the squares of the edge lengths. + for (i = 0; i < 6; i++) edgelength[i] = dot(V[i], V[i]); + + // Calculate the longest and shortest edge length. + for (i = 0; i < 6; i++) { + if (i == 0) { + shortlen = longlen = edgelength[i]; + } else { + shortlen = edgelength[i] < shortlen ? edgelength[i] : shortlen; + longlen = edgelength[i] > longlen ? edgelength[i] : longlen; + } + if (edgelength[i] > longest) { + longest = edgelength[i]; + longestedgetet.tet = tetloop.tet; + } + if (edgelength[i] < shortest) { + shortest = edgelength[i]; + } + } + + // Set the matrix A = [V[0], V[1], V[2]]^T. + for (j = 0; j < 3; j++) { + for (i = 0; i < 3; i++) A[j][i] = V[j][i]; + } + + // Decompose A just once. + if (lu_decmp(A, 3, indx, &D, 0)) { + // Get the three faces normals. + for (j = 0; j < 3; j++) { + for (i = 0; i < 3; i++) rhs[i] = 0.0; + rhs[j] = 1.0; // Positive means the inside direction + lu_solve(A, 3, indx, rhs, 0); + for (i = 0; i < 3; i++) N[j][i] = rhs[i]; + } + // Get the fourth face normal by summing up the first three. + for (i = 0; i < 3; i++) N[3][i] = - N[0][i] - N[1][i] - N[2][i]; + // Get the radius of the circumsphere. + for (i = 0; i < 3; i++) rhs[i] = 0.5 * dot(V[i], V[i]); + lu_solve(A, 3, indx, rhs, 0); + cirradius = sqrt(dot(rhs, rhs)); + // Normalize the face normals. + for (i = 0; i < 4; i++) { + // H[i] is the inverse of height of its corresponding face. + H[i] = sqrt(dot(N[i], N[i])); + for (j = 0; j < 3; j++) N[i][j] /= H[i]; + } + // Get the radius of the inscribed sphere. + // insradius = 1.0 / (H[0] + H[1] + H[2] + H[3]); + // Get the biggest H[i] (corresponding to the smallest height). + minheightinv = H[0]; + for (i = 1; i < 4; i++) { + if (H[i] > minheightinv) minheightinv = H[i]; + } + } else { + // A nearly degenerated tet. + if (tetvol <= 0.0) { + printf(" !! Warning: A %s tet (%d,%d,%d,%d).\n", + tetvol < 0 ? "inverted" : "degenerated", pointmark(p[0]), + pointmark(p[1]), pointmark(p[2]), pointmark(p[3])); + // Skip it. + tetloop.tet = tetrahedrontraverse(); + continue; + } + // Calculate the four face normals. + facenormal(p[2], p[1], p[3], N[0], 1, NULL); + facenormal(p[0], p[2], p[3], N[1], 1, NULL); + facenormal(p[1], p[0], p[3], N[2], 1, NULL); + facenormal(p[0], p[1], p[2], N[3], 1, NULL); + // Normalize the face normals. + for (i = 0; i < 4; i++) { + // H[i] is the twice of the area of the face. + H[i] = sqrt(dot(N[i], N[i])); + for (j = 0; j < 3; j++) N[i][j] /= H[i]; + } + // Get the biggest H[i] / tetvol (corresponding to the smallest height). + minheightinv = (H[0] / tetvol); + for (i = 1; i < 4; i++) { + if ((H[i] / tetvol) > minheightinv) minheightinv = (H[i] / tetvol); + } + // Let the circumradius to be the half of its longest edge length. + cirradius = 0.5 * sqrt(longlen); + } + + // Get the dihedrals (in degree) at each edges. + j = 0; + for (i = 1; i < 4; i++) { + alldihed[j] = -dot(N[0], N[i]); // Edge cd, bd, bc. + if (alldihed[j] < -1.0) alldihed[j] = -1; // Rounding. + else if (alldihed[j] > 1.0) alldihed[j] = 1; + alldihed[j] = acos(alldihed[j]) / PI * 180.0; + j++; + } + for (i = 2; i < 4; i++) { + alldihed[j] = -dot(N[1], N[i]); // Edge ad, ac. + if (alldihed[j] < -1.0) alldihed[j] = -1; // Rounding. + else if (alldihed[j] > 1.0) alldihed[j] = 1; + alldihed[j] = acos(alldihed[j]) / PI * 180.0; + j++; + } + alldihed[j] = -dot(N[2], N[3]); // Edge ab. + if (alldihed[j] < -1.0) alldihed[j] = -1; // Rounding. + else if (alldihed[j] > 1.0) alldihed[j] = 1; + alldihed[j] = acos(alldihed[j]) / PI * 180.0; + + // Calculate the largest and smallest dihedral angles. + for (i = 0; i < 6; i++) { + if (i == 0) { + smalldiangle = bigdiangle = alldihed[i]; + } else { + smalldiangle = alldihed[i] < smalldiangle ? alldihed[i] : smalldiangle; + bigdiangle = alldihed[i] > bigdiangle ? alldihed[i] : bigdiangle; + } + if (alldihed[i] < smallestdiangle) { + smallestdiangle = alldihed[i]; + } + if (alldihed[i] > biggestdiangle) { + biggestdiangle = alldihed[i]; + } + // Accumulate the corresponding number in the dihedral angle histogram. + if (alldihed[i] < 5.0) { + tendegree = 0; + } else if (alldihed[i] >= 5.0 && alldihed[i] < 10.0) { + tendegree = 1; + } else if (alldihed[i] >= 80.0 && alldihed[i] < 110.0) { + tendegree = 9; // Angles between 80 to 110 degree are in one entry. + } else if (alldihed[i] >= 170.0 && alldihed[i] < 175.0) { + tendegree = 16; + } else if (alldihed[i] >= 175.0) { + tendegree = 17; + } else { + tendegree = (int) (alldihed[i] / 10.); + if (alldihed[i] < 80.0) { + tendegree++; // In the left column. + } else { + tendegree--; // In the right column. + } + } + dihedangletable[tendegree]++; + } + + + + // Calculate the largest and smallest face angles. + for (tetloop.ver = 0; tetloop.ver < 4; tetloop.ver++) { + fsym(tetloop, neightet); + // Only do the calulation once for a face. + if (((point) neightet.tet[7] == dummypoint) || + (tetloop.tet < neightet.tet)) { + p[0] = org(tetloop); + p[1] = dest(tetloop); + p[2] = apex(tetloop); + faceangle[0] = interiorangle(p[0], p[1], p[2], NULL); + faceangle[1] = interiorangle(p[1], p[2], p[0], NULL); + faceangle[2] = PI - (faceangle[0] + faceangle[1]); + // Translate angles into degrees. + for (i = 0; i < 3; i++) { + faceangle[i] = (faceangle[i] * 180.0) / PI; + } + // Calculate the largest and smallest face angles. + for (i = 0; i < 3; i++) { + if (i == 0) { + smallfaangle = bigfaangle = faceangle[i]; + } else { + smallfaangle = faceangle[i] < smallfaangle ? + faceangle[i] : smallfaangle; + bigfaangle = faceangle[i] > bigfaangle ? faceangle[i] : bigfaangle; + } + if (faceangle[i] < smallestfaangle) { + smallestfaangle = faceangle[i]; + } + if (faceangle[i] > biggestfaangle) { + biggestfaangle = faceangle[i]; + } + tendegree = (int) (faceangle[i] / 10.); + faceangletable[tendegree]++; + } + } + } + + // Calculate aspect ratio and radius-edge ratio for this element. + tetradius = cirradius / sqrt(shortlen); + if (tetradius < smallestradiusratio) { + smallestradiusratio = tetradius; + } + if (tetradius > biggestradiusratio) { + biggestradiusratio = tetradius; + biggestradiusratiotet.tet = tetloop.tet; + } + // tetaspect = sqrt(longlen) / (2.0 * insradius); + tetaspect = sqrt(longlen) * minheightinv; + // Remember the largest and smallest aspect ratio. + if (tetaspect < smallestratio) { + smallestratio = tetaspect; + } + if (tetaspect > biggestratio) { + biggestratio = tetaspect; + } + // Accumulate the corresponding number in the aspect ratio histogram. + aspectindex = 0; + while ((tetaspect > aspectratiotable[aspectindex]) && (aspectindex < 11)) { + aspectindex++; + } + aspecttable[aspectindex]++; + radiusindex = 0; + while ((tetradius > radiusratiotable[radiusindex]) && (radiusindex < 11)) { + radiusindex++; + } + radiustable[radiusindex]++; + + tetloop.tet = tetrahedrontraverse(); + } + + shortest = sqrt(shortest); + longest = sqrt(longest); + minaltitude = sqrt(minaltitude); + + printf(" Smallest volume: %16.5g | Largest volume: %16.5g\n", + smallestvolume, biggestvolume); + printf(" Shortest edge: %16.5g | Longest edge: %16.5g\n", + shortest, longest); + printf(" Smallest asp.ratio: %13.5g | Largest asp.ratio: %13.5g\n", + smallestratio, biggestratio); + sprintf(sbuf, "%.17g", biggestfaangle); + if (strlen(sbuf) > 8) { + sbuf[8] = '\0'; + } + printf(" Smallest facangle: %14.5g | Largest facangle: %s\n", + smallestfaangle, sbuf); + sprintf(sbuf, "%.17g", biggestdiangle); + if (strlen(sbuf) > 8) { + sbuf[8] = '\0'; + } + printf(" Smallest dihedral: %14.5g | Largest dihedral: %s\n\n", + smallestdiangle, sbuf); + + printf(" Aspect ratio histogram:\n"); + printf(" < %-6.6g : %8ld | %6.6g - %-6.6g : %8ld\n", + aspectratiotable[0], aspecttable[0], aspectratiotable[5], + aspectratiotable[6], aspecttable[6]); + for (i = 1; i < 5; i++) { + printf(" %6.6g - %-6.6g : %8ld | %6.6g - %-6.6g : %8ld\n", + aspectratiotable[i - 1], aspectratiotable[i], aspecttable[i], + aspectratiotable[i + 5], aspectratiotable[i + 6], + aspecttable[i + 6]); + } + printf(" %6.6g - %-6.6g : %8ld | %6.6g - : %8ld\n", + aspectratiotable[4], aspectratiotable[5], aspecttable[5], + aspectratiotable[10], aspecttable[11]); + printf(" (A tetrahedron's aspect ratio is its longest edge length"); + printf(" divided by its\n"); + printf(" smallest side height)\n\n"); + + printf(" Face angle histogram:\n"); + for (i = 0; i < 9; i++) { + printf(" %3d - %3d degrees: %8ld | %3d - %3d degrees: %8ld\n", + i * 10, i * 10 + 10, faceangletable[i], + i * 10 + 90, i * 10 + 100, faceangletable[i + 9]); + } + if (minfaceang != PI) { + printf(" Minimum input face angle is %g (degree).\n", + minfaceang / PI * 180.0); + } + printf("\n"); + + printf(" Dihedral angle histogram:\n"); + // Print the three two rows: + printf(" %3d - %2d degrees: %8ld | %3d - %3d degrees: %8ld\n", + 0, 5, dihedangletable[0], 80, 110, dihedangletable[9]); + printf(" %3d - %2d degrees: %8ld | %3d - %3d degrees: %8ld\n", + 5, 10, dihedangletable[1], 110, 120, dihedangletable[10]); + // Print the third to seventh rows. + for (i = 2; i < 7; i++) { + printf(" %3d - %2d degrees: %8ld | %3d - %3d degrees: %8ld\n", + (i - 1) * 10, (i - 1) * 10 + 10, dihedangletable[i], + (i - 1) * 10 + 110, (i - 1) * 10 + 120, dihedangletable[i + 9]); + } + // Print the last two rows. + printf(" %3d - %2d degrees: %8ld | %3d - %3d degrees: %8ld\n", + 60, 70, dihedangletable[7], 170, 175, dihedangletable[16]); + printf(" %3d - %2d degrees: %8ld | %3d - %3d degrees: %8ld\n", + 70, 80, dihedangletable[8], 175, 180, dihedangletable[17]); + if (minfacetdihed != PI) { + printf(" Minimum input dihedral angle is %g (degree).\n", + minfacetdihed / PI * 180.0); + } + printf("\n"); + printf("\n"); +} + + +//============================================================================// +// // +// memorystatistics() Report the memory usage. // +// // +//============================================================================// + +void tetgenmesh::memorystatistics() +{ + printf("Memory usage statistics:\n\n"); + + // Count the number of blocks of tetrahedra. + int tetblocks = 0; + tetrahedrons->pathblock = tetrahedrons->firstblock; + while (tetrahedrons->pathblock != NULL) { + tetblocks++; + tetrahedrons->pathblock = (void **) *(tetrahedrons->pathblock); + } + + // Calculate the total memory (in bytes) used by storing meshes. + unsigned long totalmeshmemory = 0l, totalt2shmemory = 0l; + totalmeshmemory = points->maxitems * points->itembytes + + tetrahedrons->maxitems * tetrahedrons->itembytes; + if (b->plc || b->refine) { + totalmeshmemory += (subfaces->maxitems * subfaces->itembytes + + subsegs->maxitems * subsegs->itembytes); + totalt2shmemory = (tet2subpool->maxitems * tet2subpool->itembytes + + tet2segpool->maxitems * tet2segpool->itembytes); + } + + unsigned long totalalgomemory = 0l; + totalalgomemory = cavetetlist->totalmemory + cavebdrylist->totalmemory + + caveoldtetlist->totalmemory + + flippool->maxitems * flippool->itembytes; + if (b->plc || b->refine) { + totalalgomemory += (subsegstack->totalmemory + subfacstack->totalmemory + + subvertstack->totalmemory + + caveshlist->totalmemory + caveshbdlist->totalmemory + + cavesegshlist->totalmemory + + cavetetshlist->totalmemory + + cavetetseglist->totalmemory + + caveencshlist->totalmemory + + caveencseglist->totalmemory + + cavetetvertlist->totalmemory + + unflipqueue->totalmemory); + } + + printf(" Maximum number of tetrahedra: %ld\n", tetrahedrons->maxitems); + printf(" Maximum number of tet blocks (blocksize = %d): %d\n", + b->tetrahedraperblock, tetblocks); + /* + if (b->plc || b->refine) { + printf(" Approximate memory for tetrahedral mesh (bytes): %ld\n", + totalmeshmemory); + + printf(" Approximate memory for extra pointers (bytes): %ld\n", + totalt2shmemory); + } else { + printf(" Approximate memory for tetrahedralization (bytes): %ld\n", + totalmeshmemory); + } + printf(" Approximate memory for algorithms (bytes): %ld\n", + totalalgomemory); + printf(" Approximate memory for working arrays (bytes): %ld\n", + totalworkmemory); + printf(" Approximate total used memory (bytes): %ld\n", + totalmeshmemory + totalt2shmemory + totalalgomemory + + totalworkmemory); + */ + if (b->plc || b->refine) { + printf(" Approximate memory for tetrahedral mesh (bytes): "); + printfcomma(totalmeshmemory); printf("\n"); + + printf(" Approximate memory for extra pointers (bytes): "); + printfcomma(totalt2shmemory); printf("\n"); + } else { + printf(" Approximate memory for tetrahedralization (bytes): "); + printfcomma(totalmeshmemory); printf("\n"); + } + printf(" Approximate memory for algorithms (bytes): "); + printfcomma(totalalgomemory); printf("\n"); + printf(" Approximate memory for working arrays (bytes): "); + printfcomma(totalworkmemory); printf("\n"); + printf(" Approximate total used memory (bytes): "); + printfcomma(totalmeshmemory + totalt2shmemory + totalalgomemory + + totalworkmemory); + printf("\n"); + + printf("\n"); +} + +//============================================================================// +// // +// statistics() Print all sorts of cool facts. // +// // +//============================================================================// + +void tetgenmesh::statistics() +{ + long tetnumber, facenumber; + + printf("\nStatistics:\n\n"); + printf(" Input points: %d\n", in->numberofpoints); + if (b->refine) { + printf(" Input tetrahedra: %d\n", in->numberoftetrahedra); + if (in->numberoftrifaces > 0) { + printf(" Input triangles: %d\n", in->numberoftrifaces); + } + if (in->numberofedges > 0) { + printf(" Input edges: %d\n", in->numberofedges); + } + } else if (b->plc) { + printf(" Input facets: %d\n", in->numberoffacets); + printf(" Input segments: %ld\n", insegments); + if (in->numberofedges > 0) { + printf(" Input edges: %d\n", in->numberofedges); + } + printf(" Input holes: %d\n", in->numberofholes); + printf(" Input regions: %d\n", in->numberofregions); + } + + tetnumber = tetrahedrons->items - hullsize; + facenumber = (tetnumber * 4l + hullsize) / 2l; + + if (b->weighted) { // -w option + printf("\n Mesh points: %ld\n", points->items - nonregularcount); + } else { + printf("\n Mesh points: %ld\n", points->items); + } + printf(" Mesh tetrahedra: %ld\n", tetnumber); + printf(" Mesh faces: %ld\n", facenumber); + if (meshedges > 0l) { + printf(" Mesh edges: %ld\n", meshedges); + } else { + if (!nonconvex) { + long vsize = points->items - dupverts - unuverts; + if (b->weighted) vsize -= nonregularcount; + meshedges = vsize + facenumber - tetnumber - 1; + printf(" Mesh edges: %ld\n", meshedges); + } + } + + if (b->plc || b->refine) { + printf(" Mesh faces on exterior boundary: %ld\n", hullsize); + if (meshhulledges > 0l) { + printf(" Mesh edges on exterior boundary: %ld\n", meshhulledges); + } + printf(" Mesh faces on input facets: %ld\n", subfaces->items); + printf(" Mesh edges on input segments: %ld\n", subsegs->items); + if (st_facref_count > 0l) { + printf(" Steiner points on input facets: %ld\n", st_facref_count); + } + if (st_segref_count > 0l) { + printf(" Steiner points on input segments: %ld\n", st_segref_count); + } + if (st_volref_count > 0l) { + printf(" Steiner points inside domain: %ld\n", st_volref_count); + } + } else { + printf(" Convex hull faces: %ld\n", hullsize); + if (meshhulledges > 0l) { + printf(" Convex hull edges: %ld\n", meshhulledges); + } + } + if (b->weighted) { // -w option + printf(" Skipped non-regular points: %ld\n", nonregularcount); + } + printf("\n"); + + + if (b->verbose > 0) { + if (b->plc || b->refine) { // -p or -r + if (tetrahedrons->items > 0l) { + qualitystatistics(); + } + } + if (tetrahedrons->items > 0l) { + memorystatistics(); + } + } +} + +// // +// // +//== meshstat_cxx ============================================================// + +//== output_cxx ==============================================================// +// // +// // + +//============================================================================// +// // +// jettisonnodes() Jettison unused or duplicated vertices. // +// // +// Unused points are those input points which are outside the mesh domain or // +// have no connection (isolated) to the mesh. Duplicated points exist for // +// example if the input PLC is read from a .stl mesh file (marked during the // +// Delaunay tetrahedralization step. This routine remove these points from // +// points list. All existing points are reindexed. // +// // +//============================================================================// + +void tetgenmesh::jettisonnodes() +{ + point pointloop; + bool jetflag; + int oldidx, newidx; + int remcount; + + if (!b->quiet) { + printf("Jettisoning redundant points.\n"); + } + + points->traversalinit(); + pointloop = pointtraverse(); + oldidx = newidx = 0; // in->firstnumber; + remcount = 0; + while (pointloop != (point) NULL) { + jetflag = (pointtype(pointloop) == DUPLICATEDVERTEX) || + (pointtype(pointloop) == UNUSEDVERTEX); + if (jetflag) { + // It is a duplicated or unused point, delete it. + pointdealloc(pointloop); + remcount++; + } else { + // Re-index it. + setpointmark(pointloop, newidx + in->firstnumber); + if (in->pointmarkerlist != (int *) NULL) { + if (oldidx < in->numberofpoints) { + // Re-index the point marker as well. + in->pointmarkerlist[newidx] = in->pointmarkerlist[oldidx]; + } + } + newidx++; + } + oldidx++; + pointloop = pointtraverse(); + } + if (b->verbose) { + printf(" %ld duplicated vertices are removed.\n", dupverts); + printf(" %ld unused vertices are removed.\n", unuverts); + } + dupverts = 0l; + unuverts = 0l; + + // The following line ensures that dead items in the pool of nodes cannot + // be allocated for the new created nodes. This ensures that the input + // nodes will occur earlier in the output files, and have lower indices. + points->deaditemstack = (void *) NULL; +} + +//============================================================================// +// // +// highorder() Create extra nodes for quadratic subparametric elements. // +// // +// 'highordertable' is an array (size = numberoftetrahedra * 6) for storing // +// high-order nodes of each tetrahedron. This routine is used only when -o2 // +// switch is used. // +// // +//============================================================================// + +void tetgenmesh::highorder() +{ + triface tetloop, worktet, spintet; + point *extralist, *adjextralist; + point torg, tdest, newpoint; + int highorderindex; + int t1ver; + int i, j; + + if (!b->quiet) { + printf("Adding vertices for second-order tetrahedra.\n"); + } + + // Initialize the 'highordertable'. + point *highordertable = new point[tetrahedrons->items * 6]; + if (highordertable == (point *) NULL) { + terminatetetgen(this, 1); + } + + // This will overwrite the slot for element markers. + highorderindex = 11; + + // The following line ensures that dead items in the pool of nodes cannot + // be allocated for the extra nodes associated with high order elements. + // This ensures that the primary nodes (at the corners of elements) will + // occur earlier in the output files, and have lower indices, than the + // extra nodes. + points->deaditemstack = (void *) NULL; + + // Assign an entry for each tetrahedron to find its extra nodes. At the + // mean while, initialize all extra nodes be NULL. + i = 0; + tetrahedrons->traversalinit(); + tetloop.tet = tetrahedrontraverse(); + while (tetloop.tet != (tetrahedron *) NULL) { + tetloop.tet[highorderindex] = (tetrahedron) &highordertable[i]; + for (j = 0; j < 6; j++) { + highordertable[i + j] = (point) NULL; + } + i += 6; + tetloop.tet = tetrahedrontraverse(); + } + + // To create a unique node on each edge. Loop over all tetrahedra, and + // look at the six edges of each tetrahedron. If the extra node in + // the tetrahedron corresponding to this edge is NULL, create a node + // for this edge, at the same time, set the new node into the extra + // node lists of all other tetrahedra sharing this edge. + tetrahedrons->traversalinit(); + tetloop.tet = tetrahedrontraverse(); + while (tetloop.tet != (tetrahedron *) NULL) { + // Get the list of extra nodes. + extralist = (point *) tetloop.tet[highorderindex]; + worktet.tet = tetloop.tet; + for (i = 0; i < 6; i++) { + if (extralist[i] == (point) NULL) { + // Go to the ith-edge. + worktet.ver = edge2ver[i]; + // Create a new point in the middle of this edge. + torg = org(worktet); + tdest = dest(worktet); + makepoint(&newpoint, FREEVOLVERTEX); + for (j = 0; j < 3 + numpointattrib; j++) { + newpoint[j] = 0.5 * (torg[j] + tdest[j]); + } + // Interpolate its metrics. + for (j = 0; j < in->numberofpointmtrs; j++) { + newpoint[pointmtrindex + j] = + 0.5 * (torg[pointmtrindex + j] + tdest[pointmtrindex + j]); + } + // Set this point into all extra node lists at this edge. + spintet = worktet; + while (1) { + if (!ishulltet(spintet)) { + adjextralist = (point *) spintet.tet[highorderindex]; + adjextralist[ver2edge[spintet.ver]] = newpoint; + } + fnextself(spintet); + if (spintet.tet == worktet.tet) break; + } + } // if (!extralist[i]) + } // i + tetloop.tet = tetrahedrontraverse(); + } + + delete [] highordertable; +} + +//============================================================================// +// // +// indexelements() Index all tetrahedra. // +// // +// Many output functions require that the tetrahedra are indexed. This // +// routine is called when -E option is used. // +// // +//============================================================================// + +void tetgenmesh::indexelements() +{ + triface worktet; + int eindex = b->zeroindex ? 0 : in->firstnumber; // firstindex; + tetrahedrons->traversalinit(); + worktet.tet = tetrahedrontraverse(); + while (worktet.tet != NULL) { + setelemindex(worktet.tet, eindex); + eindex++; + if (b->metric) { // -m option + // Update the point-to-tet map, so that every point is pointing + // to a real tet, not a fictious one. Used by .p2t file. + tetrahedron tptr = encode(worktet); + for (int i = 0; i < 4; i++) { + setpoint2tet((point) (worktet.tet[4 + i]), tptr); + } + } + worktet.tet = tetrahedrontraverse(); + } +} + +//============================================================================// +// // +// numberedges() Count the number of edges, save in "meshedges". // +// // +// This routine is called when '-p' or '-r', and '-E' options are used. The // +// total number of edges depends on the genus of the input surface mesh. // +// // +// NOTE: This routine must be called after outelements(). So all elements // +// have been indexed. // +// // +//============================================================================// + +void tetgenmesh::numberedges() +{ + triface worktet, spintet; + int ishulledge; + int t1ver; + int i; + + meshedges = meshhulledges = 0l; + + tetrahedrons->traversalinit(); + worktet.tet = tetrahedrontraverse(); + while (worktet.tet != NULL) { + for (i = 0; i < 6; i++) { + worktet.ver = edge2ver[i]; + ishulledge = 0; + fnext(worktet, spintet); + do { + if (!ishulltet(spintet)) { + if (elemindex(spintet.tet) < elemindex(worktet.tet)) break; + } else { + ishulledge = 1; + } + fnextself(spintet); + } while (spintet.tet != worktet.tet); + if (spintet.tet == worktet.tet) { + meshedges++; + if (ishulledge) meshhulledges++; + } + } + infect(worktet); + worktet.tet = tetrahedrontraverse(); + } +} + +//============================================================================// +// // +// outnodes() Output the points to a .node file or a tetgenio structure. // +// // +// Note: each point has already been numbered on input (the first index is // +// 'in->firstnumber'). // +// // +//============================================================================// + +void tetgenmesh::outnodes(tetgenio* out) +{ + FILE *outfile = NULL; + char outnodefilename[FILENAMESIZE]; + face parentsh; + point pointloop; + int nextras, bmark, marker = 0, weightDT = 0; + int coordindex, attribindex; + int pointnumber, firstindex; + int index, i; + + if (out == (tetgenio *) NULL) { + strcpy(outnodefilename, b->outfilename); + strcat(outnodefilename, ".node"); + } + + if (!b->quiet) { + if (out == (tetgenio *) NULL) { + printf("Writing %s.\n", outnodefilename); + } else { + printf("Writing nodes.\n"); + } + } + + nextras = numpointattrib; + if (b->weighted) { // -w + if (b->weighted_param == 0) weightDT = 1; // Weighted DT. + } + + bmark = !b->nobound && in->pointmarkerlist; + + if (out == (tetgenio *) NULL) { + outfile = fopen(outnodefilename, "w"); + if (outfile == (FILE *) NULL) { + printf("File I/O Error: Cannot create file %s.\n", outnodefilename); + terminatetetgen(this, 1); + } + // Number of points, number of dimensions, number of point attributes, + // and number of boundary markers (zero or one). + //fprintf(outfile, "%ld %d %d %d\n", points->items, 3, nextras, bmark); + // [2020-01-16] added save flag (for viewing Steiner points). + //fprintf(outfile, "%ld %d %d %d 1\n", points->items, 3, nextras, bmark); + fprintf(outfile, "%ld %d %d %d\n", points->items, 3, nextras, bmark); + } else { + // Allocate space for 'pointlist'; + out->pointlist = new REAL[points->items * 3]; + if (out->pointlist == (REAL *) NULL) { + printf("Error: Out of memory.\n"); + terminatetetgen(this, 1); + } + // Allocate space for 'pointattributelist' if necessary; + if (nextras > 0) { + out->pointattributelist = new REAL[points->items * nextras]; + if (out->pointattributelist == (REAL *) NULL) { + printf("Error: Out of memory.\n"); + terminatetetgen(this, 1); + } + } + // Allocate space for 'pointmarkerlist' if necessary; + if (bmark) { + out->pointmarkerlist = new int[points->items]; + if (out->pointmarkerlist == (int *) NULL) { + printf("Error: Out of memory.\n"); + terminatetetgen(this, 1); + } + } + if (b->psc) { + out->pointparamlist = new tetgenio::pointparam[points->items]; + if (out->pointparamlist == NULL) { + printf("Error: Out of memory.\n"); + terminatetetgen(this, 1); + } + } + out->numberofpoints = points->items; + out->numberofpointattributes = nextras; + coordindex = 0; + attribindex = 0; + } + + // Determine the first index (0 or 1). + firstindex = b->zeroindex ? 0 : in->firstnumber; + + points->traversalinit(); + pointloop = pointtraverse(); + pointnumber = firstindex; // in->firstnumber; + index = 0; + while (pointloop != (point) NULL) { + if (bmark) { + // Default the vertex has a zero marker. + marker = 0; + // Is it an input vertex? + if (index < in->numberofpoints) { + // Input point's marker is directly copied to output. + marker = in->pointmarkerlist[index]; + } else { + if ((pointtype(pointloop) == FREESEGVERTEX) || + (pointtype(pointloop) == FREEFACETVERTEX)) { + sdecode(point2sh(pointloop), parentsh); + if (parentsh.sh != NULL) { + marker = shellmark(parentsh); + } + } // if (pointtype(...)) + } + } + if (out == (tetgenio *) NULL) { + // Point number, x, y and z coordinates. + fprintf(outfile, "%4d %.17g %.17g %.17g", pointnumber, + pointloop[0], pointloop[1], pointloop[2]); + for (i = 0; i < nextras; i++) { + // Write an attribute. + if ((i == 0) && weightDT) { + fprintf(outfile, " %.17g", pointloop[0] * pointloop[0] + + pointloop[1] * pointloop[1] + pointloop[2] * pointloop[2] + - pointloop[3 + i]); + } else { + fprintf(outfile, " %.17g", pointloop[3 + i]); + } + } + if (bmark) { + // Write the boundary marker. + fprintf(outfile, " %d", marker); + } + if (b->psc) { + fprintf(outfile, " %.8g %.8g %d", pointgeomuv(pointloop, 0), + pointgeomuv(pointloop, 1), pointgeomtag(pointloop)); + if (pointtype(pointloop) == RIDGEVERTEX) { + fprintf(outfile, " 0"); + //} else if (pointtype(pointloop) == ACUTEVERTEX) { + // fprintf(outfile, " 0"); + } else if (pointtype(pointloop) == FREESEGVERTEX) { + fprintf(outfile, " 1"); + } else if (pointtype(pointloop) == FREEFACETVERTEX) { + fprintf(outfile, " 2"); + } else if (pointtype(pointloop) == FREEVOLVERTEX) { + fprintf(outfile, " 3"); + } else { + fprintf(outfile, " -1"); // Unknown type. + } + } + // // [2020-01-16] Write vertex flags + // if (pointnumber > in->numberofpoints) { + // fprintf(outfile, " 16"); // A Steiner point. + // } else { + // fprintf(outfile, " 0"); + // } + fprintf(outfile, "\n"); + } else { + // X, y, and z coordinates. + out->pointlist[coordindex++] = pointloop[0]; + out->pointlist[coordindex++] = pointloop[1]; + out->pointlist[coordindex++] = pointloop[2]; + // Point attributes. + for (i = 0; i < nextras; i++) { + // Output an attribute. + if ((i == 0) && weightDT) { + out->pointattributelist[attribindex++] = + pointloop[0] * pointloop[0] + pointloop[1] * pointloop[1] + + pointloop[2] * pointloop[2] - pointloop[3 + i]; + } else { + out->pointattributelist[attribindex++] = pointloop[3 + i]; + } + } + if (bmark) { + // Output the boundary marker. + out->pointmarkerlist[index] = marker; + } + if (b->psc) { + out->pointparamlist[index].uv[0] = pointgeomuv(pointloop, 0); + out->pointparamlist[index].uv[1] = pointgeomuv(pointloop, 1); + out->pointparamlist[index].tag = pointgeomtag(pointloop); + if (pointtype(pointloop) == RIDGEVERTEX) { + out->pointparamlist[index].type = 0; + //} else if (pointtype(pointloop) == ACUTEVERTEX) { + // out->pointparamlist[index].type = 0; + } else if (pointtype(pointloop) == FREESEGVERTEX) { + out->pointparamlist[index].type = 1; + } else if (pointtype(pointloop) == FREEFACETVERTEX) { + out->pointparamlist[index].type = 2; + } else if (pointtype(pointloop) == FREEVOLVERTEX) { + out->pointparamlist[index].type = 3; + } else { + out->pointparamlist[index].type = -1; // Unknown type. + } + } + } + pointloop = pointtraverse(); + pointnumber++; + index++; + } + + if (out == (tetgenio *) NULL) { + fprintf(outfile, "# Generated by %s\n", b->commandline); + fclose(outfile); + } +} + +//============================================================================// +// // +// outmetrics() Output the metric to a file (*.mtr) or a tetgenio obj. // +// // +//============================================================================// + +void tetgenmesh::outmetrics(tetgenio* out) +{ + FILE *outfile = NULL; + char outmtrfilename[FILENAMESIZE]; + point ptloop; + int mtrindex = 0; + int i; + int msize = (sizeoftensor - useinsertradius); + if (msize == 0) { + return; + } + + if (out == (tetgenio *) NULL) { + strcpy(outmtrfilename, b->outfilename); + strcat(outmtrfilename, ".mtr"); + } + + if (!b->quiet) { + if (out == (tetgenio *) NULL) { + printf("Writing %s.\n", outmtrfilename); + } else { + printf("Writing metrics.\n"); + } + } + + if (out == (tetgenio *) NULL) { + outfile = fopen(outmtrfilename, "w"); + if (outfile == (FILE *) NULL) { + printf("File I/O Error: Cannot create file %s.\n", outmtrfilename); + terminatetetgen(this, 3); + } + // Number of points, number of point metrices, + fprintf(outfile, "%ld %d\n", points->items, msize); + } else { + // Allocate space for 'pointmtrlist'. + out->numberofpointmtrs = msize; + out->pointmtrlist = new REAL[points->items * msize]; + if (out->pointmtrlist == (REAL *) NULL) { + terminatetetgen(this, 1); + } + } + + points->traversalinit(); + ptloop = pointtraverse(); + while (ptloop != (point) NULL) { + if (out == (tetgenio *) NULL) { + for (i = 0; i < msize; i++) { + fprintf(outfile, " %-16.8e", ptloop[pointmtrindex + i]); + } + fprintf(outfile, "\n"); + } else { + for (i = 0; i < msize; i++) { + out->pointmtrlist[mtrindex++] = ptloop[pointmtrindex + i]; + } + } + ptloop = pointtraverse(); + } + + // Output the point-to-tet map. + if (out == (tetgenio *) NULL) { + strcpy(outmtrfilename, b->outfilename); + strcat(outmtrfilename, ".p2t"); + } + + if (!b->quiet) { + if (out == (tetgenio *) NULL) { + printf("Writing %s.\n", outmtrfilename); + } else { + printf("Writing point-to-tet map.\n"); + } + } + + if (out == (tetgenio *) NULL) { + outfile = fopen(outmtrfilename, "w"); + if (outfile == (FILE *) NULL) { + printf("File I/O Error: Cannot create file %s.\n", outmtrfilename); + terminatetetgen(this, 3); + } + // Number of points, + //fprintf(outfile, "%ld\n", points->items); + } else { + // Allocate space for 'point2tetlist'. + out->point2tetlist = new int[points->items]; + if (out->point2tetlist == (int *) NULL) { + terminatetetgen(this, 1); + } + } + + // The list of tetrahedra must be indexed. + if (bgm != NULL) { + bgm->indexelements(); + } + // Determine the first index (0 or 1). + int firstindex = b->zeroindex ? 0 : in->firstnumber; + int pointindex = firstindex; + i = 0; + + triface parenttet; + points->traversalinit(); + ptloop = pointtraverse(); + while (ptloop != (point) NULL) { + if (bgm != NULL) { + bgm->decode(point2bgmtet(ptloop), parenttet); + } else { + decode(point2tet(ptloop), parenttet); + } + if (out == (tetgenio *) NULL) { + fprintf(outfile, "%d %d\n", pointindex, elemindex(parenttet.tet)); + } else { + out->point2tetlist[i] = elemindex(parenttet.tet); + } + pointindex++; + i++; + ptloop = pointtraverse(); + } + + if (out == (tetgenio *) NULL) { + fprintf(outfile, "# Generated by %s\n", b->commandline); + fclose(outfile); + } +} + +//============================================================================// +// // +// outelements() Output the tetrahedra to an .ele file or a tetgenio // +// structure. // +// // +// This routine also indexes all tetrahedra (exclusing hull tets) (from in-> // +// firstnumber). The total number of mesh edges is counted in 'meshedges'. // +// // +//============================================================================// + +void tetgenmesh::outelements(tetgenio* out) +{ + FILE *outfile = NULL; + char outelefilename[FILENAMESIZE]; + tetrahedron* tptr; + point p1, p2, p3, p4; + point *extralist; + REAL *talist = NULL; + int *tlist = NULL; + long ntets; + int firstindex, shift; + int pointindex, attribindex; + int highorderindex = 11; + int elementnumber; + int eextras; + int i; + + if (out == (tetgenio *) NULL) { + strcpy(outelefilename, b->outfilename); + strcat(outelefilename, ".ele"); + } + + if (!b->quiet) { + if (out == (tetgenio *) NULL) { + printf("Writing %s.\n", outelefilename); + } else { + printf("Writing elements.\n"); + } + } + + // The number of tets excluding hull tets. + ntets = tetrahedrons->items - hullsize; + + eextras = numelemattrib; + if (out == (tetgenio *) NULL) { + outfile = fopen(outelefilename, "w"); + if (outfile == (FILE *) NULL) { + printf("File I/O Error: Cannot create file %s.\n", outelefilename); + terminatetetgen(this, 1); + } + // Number of tetras, points per tetra, attributes per tetra. + fprintf(outfile, "%ld %d %d\n", ntets, b->order == 1 ? 4 : 10, eextras); + } else { + // Allocate memory for output tetrahedra. + out->tetrahedronlist = new int[ntets * (b->order == 1 ? 4 : 10)]; + if (out->tetrahedronlist == (int *) NULL) { + printf("Error: Out of memory.\n"); + terminatetetgen(this, 1); + } + // Allocate memory for output tetrahedron attributes if necessary. + if (eextras > 0) { + out->tetrahedronattributelist = new REAL[ntets * eextras]; + if (out->tetrahedronattributelist == (REAL *) NULL) { + printf("Error: Out of memory.\n"); + terminatetetgen(this, 1); + } + } + out->numberoftetrahedra = ntets; + out->numberofcorners = b->order == 1 ? 4 : 10; + out->numberoftetrahedronattributes = eextras; + tlist = out->tetrahedronlist; + talist = out->tetrahedronattributelist; + pointindex = 0; + attribindex = 0; + } + + // Determine the first index (0 or 1). + firstindex = b->zeroindex ? 0 : in->firstnumber; + shift = 0; // Default no shift. + if ((in->firstnumber == 1) && (firstindex == 0)) { + shift = 1; // Shift the output indices by 1. + } + + tetrahedrons->traversalinit(); + tptr = tetrahedrontraverse(); + elementnumber = firstindex; // in->firstnumber; + while (tptr != (tetrahedron *) NULL) { + if (!b->reversetetori) { + p1 = (point) tptr[4]; + p2 = (point) tptr[5]; + } else { + p1 = (point) tptr[5]; + p2 = (point) tptr[4]; + } + p3 = (point) tptr[6]; + p4 = (point) tptr[7]; + if (out == (tetgenio *) NULL) { + // Tetrahedron number, indices for four points. + fprintf(outfile, "%5d %5d %5d %5d %5d", elementnumber, + pointmark(p1) - shift, pointmark(p2) - shift, + pointmark(p3) - shift, pointmark(p4) - shift); + if (b->order == 2) { + extralist = (point *) tptr[highorderindex]; + // indices for six extra points. + fprintf(outfile, " %5d %5d %5d %5d %5d %5d", + pointmark(extralist[0]) - shift, pointmark(extralist[1]) - shift, + pointmark(extralist[2]) - shift, pointmark(extralist[3]) - shift, + pointmark(extralist[4]) - shift, pointmark(extralist[5]) - shift); + } + for (i = 0; i < eextras; i++) { + fprintf(outfile, " %.17g", elemattribute(tptr, i)); + } + fprintf(outfile, "\n"); + } else { + tlist[pointindex++] = pointmark(p1) - shift; + tlist[pointindex++] = pointmark(p2) - shift; + tlist[pointindex++] = pointmark(p3) - shift; + tlist[pointindex++] = pointmark(p4) - shift; + if (b->order == 2) { + extralist = (point *) tptr[highorderindex]; + tlist[pointindex++] = pointmark(extralist[0]) - shift; + tlist[pointindex++] = pointmark(extralist[1]) - shift; + tlist[pointindex++] = pointmark(extralist[2]) - shift; + tlist[pointindex++] = pointmark(extralist[3]) - shift; + tlist[pointindex++] = pointmark(extralist[4]) - shift; + tlist[pointindex++] = pointmark(extralist[5]) - shift; + } + for (i = 0; i < eextras; i++) { + talist[attribindex++] = elemattribute(tptr, i); + } + } + // Remember the index of this element (for counting edges). + setelemindex(tptr, elementnumber); + if (b->metric) { // -m option + // Update the point-to-tet map, so that every point is pointing + // to a real tet, not a fictious one. Used by .p2t file. + for (int i = 0; i < 4; i++) { + setpoint2tet((point) (tptr[4 + i]), (tetrahedron) tptr); + } + } + tptr = tetrahedrontraverse(); + elementnumber++; + } + + + if (out == (tetgenio *) NULL) { + fprintf(outfile, "# Generated by %s\n", b->commandline); + fclose(outfile); + } +} + +//============================================================================// +// // +// outfaces() Output all faces to a .face file or a tetgenio object. // +// // +// The total number of faces f can be calculated as following: Let t be the // +// total number of tets. Since each tet has 4 faces, the number t * 4 counts // +// each interior face twice and each hull face once. So f = (t * 4 + h) / 2, // +// where h is the total number of hull faces (which is known). // +// // +//============================================================================// + +void tetgenmesh::outfaces(tetgenio* out) +{ + FILE *outfile = NULL; + char facefilename[FILENAMESIZE]; + triface tface, tsymface; + face checkmark; + point torg, tdest, tapex; + long ntets, faces; + int *elist = NULL, *emlist = NULL; + int neigh1 = 0, neigh2 = 0; + int marker = 0; + int firstindex, shift; + int facenumber; + int index = 0; + + // For -o2 option. + triface workface; + point *extralist, pp[3] = {0,0,0}; + int highorderindex = 11; + int o2index = 0, i; + + // For -nn option. + int *tet2facelist = NULL; + int tidx; + + if (out == (tetgenio *) NULL) { + strcpy(facefilename, b->outfilename); + strcat(facefilename, ".face"); + } + + if (!b->quiet) { + if (out == (tetgenio *) NULL) { + printf("Writing %s.\n", facefilename); + } else { + printf("Writing faces.\n"); + } + } + + ntets = tetrahedrons->items - hullsize; + faces = (ntets * 4l + hullsize) / 2l; + + if (out == (tetgenio *) NULL) { + outfile = fopen(facefilename, "w"); + if (outfile == (FILE *) NULL) { + printf("File I/O Error: Cannot create file %s.\n", facefilename); + terminatetetgen(this, 1); + } + fprintf(outfile, "%ld %d\n", faces, !b->nobound); + } else { + // Allocate memory for 'trifacelist'. + out->trifacelist = new int[faces * 3]; + if (out->trifacelist == (int *) NULL) { + printf("Error: Out of memory.\n"); + terminatetetgen(this, 1); + } + if (b->order == 2) { + out->o2facelist = new int[faces * 3]; + } + // Allocate memory for 'trifacemarkerlist' if necessary. + if (!b->nobound) { + out->trifacemarkerlist = new int[faces]; + if (out->trifacemarkerlist == (int *) NULL) { + printf("Error: Out of memory.\n"); + terminatetetgen(this, 1); + } + } + if (b->neighout > 1) { + // '-nn' switch. + out->face2tetlist = new int[faces * 2]; + if (out->face2tetlist == (int *) NULL) { + printf("Error: Out of memory.\n"); + terminatetetgen(this, 1); + } + } + out->numberoftrifaces = faces; + elist = out->trifacelist; + emlist = out->trifacemarkerlist; + } + + if (b->neighout > 1) { // -nn option + // Output the tetrahedron-to-face map. + tet2facelist = new int[ntets * 4]; + } + + // Determine the first index (0 or 1). + firstindex = b->zeroindex ? 0 : in->firstnumber; + shift = 0; // Default no shiftment. + if ((in->firstnumber == 1) && (firstindex == 0)) { + shift = 1; // Shift the output indices by 1. + } + + tetrahedrons->traversalinit(); + tface.tet = tetrahedrontraverse(); + facenumber = firstindex; // in->firstnumber; + // To loop over the set of faces, loop over all tetrahedra, and look at + // the four faces of each one. If its adjacent tet is a hull tet, + // operate on the face, otherwise, operate on the face only if the + // current tet has a smaller index than its neighbor. + while (tface.tet != (tetrahedron *) NULL) { + for (tface.ver = 0; tface.ver < 4; tface.ver ++) { + fsym(tface, tsymface); + if (ishulltet(tsymface) || + (elemindex(tface.tet) < elemindex(tsymface.tet))) { + torg = org(tface); + tdest = dest(tface); + tapex = apex(tface); + if (b->order == 2) { // -o2 + // Get the three extra vertices on edges. + extralist = (point *) (tface.tet[highorderindex]); + // The extra vertices are on edges opposite the corners. + enext(tface, workface); + for (i = 0; i < 3; i++) { + pp[i] = extralist[ver2edge[workface.ver]]; + enextself(workface); + } + } + if (!b->nobound) { + // Get the boundary marker of this face. + if (b->plc || b->refine) { + // Shell face is used. + tspivot(tface, checkmark); + if (checkmark.sh == NULL) { + marker = 0; // It is an inner face. It's marker is 0. + } else { + marker = shellmark(checkmark); + } + } else { + // Shell face is not used, only distinguish outer and inner face. + marker = (int) ishulltet(tsymface); + } + } + if (b->neighout > 1) { + // '-nn' switch. Output adjacent tets indices. + if (!ishulltet(tface)) { + neigh1 = elemindex(tface.tet); + } else { + neigh1 = -1; + } + if (!ishulltet(tsymface)) { + neigh2 = elemindex(tsymface.tet); + } else { + neigh2 = -1; + } + // Fill the tetrahedron-to-face map. + tidx = elemindex(tface.tet) - firstindex; + tet2facelist[tidx * 4 + tface.ver] = facenumber; + if (!ishulltet(tsymface)) { + tidx = elemindex(tsymface.tet) - firstindex; + tet2facelist[tidx * 4 + (tsymface.ver & 3)] = facenumber; + } + } + if (out == (tetgenio *) NULL) { + // Face number, indices of three vertices. + fprintf(outfile, "%5d %4d %4d %4d", facenumber, + pointmark(torg) - shift, pointmark(tdest) - shift, + pointmark(tapex) - shift); + if (b->order == 2) { // -o2 + fprintf(outfile, " %4d %4d %4d", pointmark(pp[0]) - shift, + pointmark(pp[1]) - shift, pointmark(pp[2]) - shift); + } + if (!b->nobound) { + // Output a boundary marker. + fprintf(outfile, " %d", marker); + } + if (b->neighout > 1) { + fprintf(outfile, " %5d %5d", neigh1, neigh2); + } + fprintf(outfile, "\n"); + } else { + // Output indices of three vertices. + elist[index++] = pointmark(torg) - shift; + elist[index++] = pointmark(tdest) - shift; + elist[index++] = pointmark(tapex) - shift; + if (b->order == 2) { // -o2 + out->o2facelist[o2index++] = pointmark(pp[0]) - shift; + out->o2facelist[o2index++] = pointmark(pp[1]) - shift; + out->o2facelist[o2index++] = pointmark(pp[2]) - shift; + } + if (!b->nobound) { + emlist[facenumber - in->firstnumber] = marker; + } + if (b->neighout > 1) { + out->face2tetlist[(facenumber - in->firstnumber) * 2] = neigh1; + out->face2tetlist[(facenumber - in->firstnumber) * 2 + 1] = neigh2; + } + } + facenumber++; + } + } + tface.tet = tetrahedrontraverse(); + } + + if (out == (tetgenio *) NULL) { + fprintf(outfile, "# Generated by %s\n", b->commandline); + fclose(outfile); + } + + if (b->neighout > 1) { // -nn option + // Output the tetrahedron-to-face map. + if (out == (tetgenio *) NULL) { + strcpy(facefilename, b->outfilename); + strcat(facefilename, ".t2f"); + } + if (!b->quiet) { + if (out == (tetgenio *) NULL) { + printf("Writing %s.\n", facefilename); + } else { + printf("Writing tetrahedron-to-face map.\n"); + } + } + if (out == (tetgenio *) NULL) { + outfile = fopen(facefilename, "w"); + for (tidx = 0; tidx < ntets; tidx++) { + index = tidx * 4; + fprintf(outfile, "%4d %d %d %d %d\n", tidx + in->firstnumber, + tet2facelist[index], tet2facelist[index+1], + tet2facelist[index+2], tet2facelist[index+3]); + } + fclose(outfile); + delete [] tet2facelist; + } else { + // Simply copy the address of the list to the output. + out->tet2facelist = tet2facelist; + } + } +} + +//============================================================================// +// // +// outhullfaces() Output hull faces to a .face file or a tetgenio object. // +// // +// The normal of each face is pointing to the outside of the domain. // +// // +//============================================================================// + +void tetgenmesh::outhullfaces(tetgenio* out) +{ + FILE *outfile = NULL; + char facefilename[FILENAMESIZE]; + triface hulltet; + point torg, tdest, tapex; + int *elist = NULL; + int firstindex, shift; + int facenumber; + int index; + + if (out == (tetgenio *) NULL) { + strcpy(facefilename, b->outfilename); + strcat(facefilename, ".face"); + } + + if (!b->quiet) { + if (out == (tetgenio *) NULL) { + printf("Writing %s.\n", facefilename); + } else { + printf("Writing faces.\n"); + } + } + + if (out == (tetgenio *) NULL) { + outfile = fopen(facefilename, "w"); + if (outfile == (FILE *) NULL) { + printf("File I/O Error: Cannot create file %s.\n", facefilename); + terminatetetgen(this, 1); + } + fprintf(outfile, "%ld 0\n", hullsize); + } else { + // Allocate memory for 'trifacelist'. + out->trifacelist = new int[hullsize * 3]; + if (out->trifacelist == (int *) NULL) { + printf("Error: Out of memory.\n"); + terminatetetgen(this, 1); + } + out->numberoftrifaces = hullsize; + elist = out->trifacelist; + index = 0; + } + + // Determine the first index (0 or 1). + firstindex = b->zeroindex ? 0 : in->firstnumber; + shift = 0; // Default no shiftment. + if ((in->firstnumber == 1) && (firstindex == 0)) { + shift = 1; // Shift the output indices by 1. + } + + tetrahedrons->traversalinit(); + hulltet.tet = alltetrahedrontraverse(); + facenumber = firstindex; + while (hulltet.tet != (tetrahedron *) NULL) { + if (ishulltet(hulltet)) { + torg = (point) hulltet.tet[4]; + tdest = (point) hulltet.tet[5]; + tapex = (point) hulltet.tet[6]; + if (out == (tetgenio *) NULL) { + // Face number, indices of three vertices. + fprintf(outfile, "%5d %4d %4d %4d", facenumber, + pointmark(torg) - shift, pointmark(tdest) - shift, + pointmark(tapex) - shift); + fprintf(outfile, "\n"); + } else { + // Output indices of three vertices. + elist[index++] = pointmark(torg) - shift; + elist[index++] = pointmark(tdest) - shift; + elist[index++] = pointmark(tapex) - shift; + } + facenumber++; + } + hulltet.tet = alltetrahedrontraverse(); + } + + if (out == (tetgenio *) NULL) { + fprintf(outfile, "# Generated by %s\n", b->commandline); + fclose(outfile); + } +} + +//============================================================================// +// // +// outsubfaces() Output subfaces (i.e. boundary faces) to a .face file or // +// a tetgenio structure. // +// // +// The boundary faces are found in 'subfaces'. For listing triangle vertices // +// in the same sense for all triangles in the mesh, the direction determined // +// by right-hand rule is pointer to the inside of the volume. // +// // +//============================================================================// + +void tetgenmesh::outsubfaces(tetgenio* out) +{ + FILE *outfile = NULL; + char facefilename[FILENAMESIZE]; + int *elist = NULL; + int *emlist = NULL; + int index = 0, index1 = 0, index2 = 0; + triface abuttingtet; + face faceloop; + point torg, tdest, tapex; + int marker = 0; + int firstindex, shift; + int neigh1 = 0, neigh2 = 0; + int facenumber; + + // For -o2 option. + triface workface; + point *extralist, pp[3] = {0,0,0}; + int highorderindex = 11; + int o2index = 0, i; + + int t1ver; // used by fsymself() + + if (out == (tetgenio *) NULL) { + strcpy(facefilename, b->outfilename); + strcat(facefilename, ".face"); + } + + if (!b->quiet) { + if (out == (tetgenio *) NULL) { + printf("Writing %s.\n", facefilename); + } else { + printf("Writing faces.\n"); + } + } + + if (out == (tetgenio *) NULL) { + outfile = fopen(facefilename, "w"); + if (outfile == (FILE *) NULL) { + printf("File I/O Error: Cannot create file %s.\n", facefilename); + terminatetetgen(this, 3); + } + // Number of subfaces. + fprintf(outfile, "%ld %d\n", subfaces->items, !b->nobound); + } else { + // Allocate memory for 'trifacelist'. + out->trifacelist = new int[subfaces->items * 3]; + if (out->trifacelist == (int *) NULL) { + terminatetetgen(this, 1); + } + if (b->order == 2) { + out->o2facelist = new int[subfaces->items * 3]; + } + if (!b->nobound) { + // Allocate memory for 'trifacemarkerlist'. + out->trifacemarkerlist = new int[subfaces->items]; + if (out->trifacemarkerlist == (int *) NULL) { + terminatetetgen(this, 1); + } + } + if (b->neighout > 1) { + // '-nn' switch. + out->face2tetlist = new int[subfaces->items * 2]; + if (out->face2tetlist == (int *) NULL) { + terminatetetgen(this, 1); + } + } + out->numberoftrifaces = subfaces->items; + elist = out->trifacelist; + emlist = out->trifacemarkerlist; + } + + // Determine the first index (0 or 1). + firstindex = b->zeroindex ? 0 : in->firstnumber; + shift = 0; // Default no shiftment. + if ((in->firstnumber == 1) && (firstindex == 0)) { + shift = 1; // Shift the output indices by 1. + } + + subfaces->traversalinit(); + faceloop.sh = shellfacetraverse(subfaces); + facenumber = firstindex; // in->firstnumber; + while (faceloop.sh != (shellface *) NULL) { + stpivot(faceloop, abuttingtet); + // If there is a tetrahedron containing this subface, orient it so + // that the normal of this face points to inside of the volume by + // right-hand rule. + if (abuttingtet.tet != NULL) { + if (ishulltet(abuttingtet)) { + fsymself(abuttingtet); + } + } + if (abuttingtet.tet != NULL) { + torg = org(abuttingtet); + tdest = dest(abuttingtet); + tapex = apex(abuttingtet); + if (b->order == 2) { // -o2 + // Get the three extra vertices on edges. + extralist = (point *) (abuttingtet.tet[highorderindex]); + workface = abuttingtet; + for (i = 0; i < 3; i++) { + pp[i] = extralist[ver2edge[workface.ver]]; + enextself(workface); + } + } + } else { + // This may happen when only a surface mesh be generated. + torg = sorg(faceloop); + tdest = sdest(faceloop); + tapex = sapex(faceloop); + if (b->order == 2) { // -o2 + // There is no extra node list available. + pp[0] = torg; + pp[1] = tdest; + pp[2] = tapex; + } + } + if (!b->nobound) { + marker = shellmark(faceloop); + } + if (b->neighout > 1) { + // '-nn' switch. Output adjacent tets indices. + neigh1 = -1; + neigh2 = -1; + stpivot(faceloop, abuttingtet); + if (abuttingtet.tet != NULL) { + if (!ishulltet(abuttingtet)) { + neigh1 = elemindex(abuttingtet.tet); + } + fsymself(abuttingtet); + if (!ishulltet(abuttingtet)) { + neigh2 = elemindex(abuttingtet.tet); + } + } + } + if (out == (tetgenio *) NULL) { + fprintf(outfile, "%5d %4d %4d %4d", facenumber, + pointmark(torg) - shift, pointmark(tdest) - shift, + pointmark(tapex) - shift); + if (b->order == 2) { // -o2 + fprintf(outfile, " %4d %4d %4d", pointmark(pp[0]) - shift, + pointmark(pp[1]) - shift, pointmark(pp[2]) - shift); + } + if (!b->nobound) { + fprintf(outfile, " %d", marker); + } + if (b->neighout > 1) { + fprintf(outfile, " %5d %5d", neigh1, neigh2); + } + fprintf(outfile, "\n"); + } else { + // Output three vertices of this face; + elist[index++] = pointmark(torg) - shift; + elist[index++] = pointmark(tdest) - shift; + elist[index++] = pointmark(tapex) - shift; + if (b->order == 2) { // -o2 + out->o2facelist[o2index++] = pointmark(pp[0]) - shift; + out->o2facelist[o2index++] = pointmark(pp[1]) - shift; + out->o2facelist[o2index++] = pointmark(pp[2]) - shift; + } + if (!b->nobound) { + emlist[index1++] = marker; + } + if (b->neighout > 1) { + out->face2tetlist[index2++] = neigh1; + out->face2tetlist[index2++] = neigh2; + } + } + facenumber++; + faceloop.sh = shellfacetraverse(subfaces); + } + + if (out == (tetgenio *) NULL) { + fprintf(outfile, "# Generated by %s\n", b->commandline); + fclose(outfile); + } +} + +//============================================================================// +// // +// outedges() Output all edges to a .edge file or a tetgenio object. // +// // +// Note: This routine must be called after outelements(), so that the total // +// number of edges 'meshedges' has been counted. // +// // +//============================================================================// + +void tetgenmesh::outedges(tetgenio* out) +{ + FILE *outfile = NULL; + char edgefilename[FILENAMESIZE]; + triface tetloop, worktet, spintet; + face checkseg; + point torg, tdest; + int ishulledge; + int firstindex, shift; + int edgenumber, marker; + int index = 0, index1 = 0, index2 = 0; + int t1ver; + int i; + + // For -o2 option. + point *extralist, pp = NULL; + int highorderindex = 11; + int o2index = 0; + + // For -nn option. + int *tet2edgelist = NULL; + int tidx; + + if (out == (tetgenio *) NULL) { + strcpy(edgefilename, b->outfilename); + strcat(edgefilename, ".edge"); + } + + if (!b->quiet) { + if (out == (tetgenio *) NULL) { + printf("Writing %s.\n", edgefilename); + } else { + printf("Writing edges.\n"); + } + } + + if (meshedges == 0l) { + if (nonconvex) { + numberedges(); // Count the edges. + } else { + // Use Euler's characteristic to get the numbe of edges. + // It states V - E + F - C = 1, hence E = V + F - C - 1. + long tsize = tetrahedrons->items - hullsize; + long fsize = (tsize * 4l + hullsize) / 2l; + long vsize = points->items - dupverts - unuverts; + if (b->weighted) vsize -= nonregularcount; + meshedges = vsize + fsize - tsize - 1; + } + } + meshhulledges = 0l; // It will be counted. + + if (out == (tetgenio *) NULL) { + outfile = fopen(edgefilename, "w"); + if (outfile == (FILE *) NULL) { + printf("File I/O Error: Cannot create file %s.\n", edgefilename); + terminatetetgen(this, 1); + } + // Write the number of edges, boundary markers (0 or 1). + fprintf(outfile, "%ld %d\n", meshedges, !b->nobound); + } else { + // Allocate memory for 'edgelist'. + out->numberofedges = meshedges; + out->edgelist = new int[meshedges * 2]; + if (out->edgelist == (int *) NULL) { + printf("Error: Out of memory.\n"); + terminatetetgen(this, 1); + } + if (b->order == 2) { // -o2 switch + out->o2edgelist = new int[meshedges]; + } + if (!b->nobound) { + out->edgemarkerlist = new int[meshedges]; + } + if (b->neighout > 1) { // '-nn' switch. + out->edge2tetlist = new int[meshedges]; + } + } + + if (b->neighout > 1) { // -nn option + // Output the tetrahedron-to-edge map. + long tsize = tetrahedrons->items - hullsize; + tet2edgelist = new int[tsize * 6]; + } + + // Determine the first index (0 or 1). + firstindex = b->zeroindex ? 0 : in->firstnumber; + shift = 0; // Default no shiftment. + if ((in->firstnumber == 1) && (firstindex == 0)) { + shift = 1; // Shift (reduce) the output indices by 1. + } + + tetrahedrons->traversalinit(); + tetloop.tet = tetrahedrontraverse(); + edgenumber = firstindex; // in->firstnumber; + while (tetloop.tet != (tetrahedron *) NULL) { + // Count the number of Voronoi faces. + worktet.tet = tetloop.tet; + for (i = 0; i < 6; i++) { + worktet.ver = edge2ver[i]; + ishulledge = 0; + fnext(worktet, spintet); + do { + if (!ishulltet(spintet)) { + if (elemindex(spintet.tet) < elemindex(worktet.tet)) break; + } else { + ishulledge = 1; + } + fnextself(spintet); + } while (spintet.tet != worktet.tet); + if (spintet.tet == worktet.tet) { + // Found a new edge. + if (ishulledge) meshhulledges++; + torg = org(worktet); + tdest = dest(worktet); + if (b->order == 2) { // -o2 + // Get the extra vertex on this edge. + extralist = (point *) worktet.tet[highorderindex]; + pp = extralist[ver2edge[worktet.ver]]; + } + if (out == (tetgenio *) NULL) { + fprintf(outfile, "%5d %4d %4d", edgenumber, + pointmark(torg) - shift, pointmark(tdest) - shift); + if (b->order == 2) { // -o2 + fprintf(outfile, " %4d", pointmark(pp) - shift); + } + } else { + // Output three vertices of this face; + out->edgelist[index++] = pointmark(torg) - shift; + out->edgelist[index++] = pointmark(tdest) - shift; + if (b->order == 2) { // -o2 + out->o2edgelist[o2index++] = pointmark(pp) - shift; + } + } + if (!b->nobound) { + if (b->plc || b->refine) { + // Check if the edge is a segment. + tsspivot1(worktet, checkseg); + if (checkseg.sh != NULL) { + marker = shellmark(checkseg); + } else { + marker = 0; // It's not a segment. + } + } else { + // Mark it if it is a hull edge. + marker = ishulledge ? 1 : 0; + } + if (out == (tetgenio *) NULL) { + fprintf(outfile, " %d", marker); + } else { + out->edgemarkerlist[index1++] = marker; + } + } + if (b->neighout > 1) { // '-nn' switch. + if (out == (tetgenio *) NULL) { + fprintf(outfile, " %d", elemindex(tetloop.tet)); + } else { + out->edge2tetlist[index2++] = elemindex(tetloop.tet); + } + // Fill the tetrahedron-to-edge map. + spintet = worktet; + while (1) { + if (!ishulltet(spintet)) { + tidx = elemindex(spintet.tet) - firstindex; + tet2edgelist[tidx * 6 + ver2edge[spintet.ver]] = edgenumber; + } + fnextself(spintet); + if (spintet.tet == worktet.tet) break; + } + } + if (out == (tetgenio *) NULL) { + fprintf(outfile, "\n"); + } + edgenumber++; + } + } + tetloop.tet = tetrahedrontraverse(); + } + + if (out == (tetgenio *) NULL) { + fprintf(outfile, "# Generated by %s\n", b->commandline); + fclose(outfile); + } + + if (b->neighout > 1) { // -nn option + long tsize = tetrahedrons->items - hullsize; + + if (b->facesout) { // -f option + // Build the face-to-edge map (use the tet-to-edge map). + long fsize = (tsize * 4l + hullsize) / 2l; + int *face2edgelist = new int[fsize * 3]; + + tetrahedrons->traversalinit(); + tetloop.tet = tetrahedrontraverse(); + int facenumber = 0; // firstindex; // in->firstnumber; + while (tetloop.tet != (tetrahedron *) NULL) { + for (tetloop.ver = 0; tetloop.ver < 4; tetloop.ver++) { + fsym(tetloop, spintet); + if (ishulltet(spintet) || + (elemindex(tetloop.tet) < elemindex(spintet.tet))) { + // The three edges of this face are ordered such that the + // first edge is opposite to the first vertex of this face + // that appears in the .face file, and so on. + tidx = elemindex(tetloop.tet) - firstindex; + worktet = tetloop; + for (i = 0; i < 3; i++) { + enextself(worktet); // The edge opposite to vertex i. + int eidx = tet2edgelist[tidx * 6 + ver2edge[worktet.ver]]; + face2edgelist[facenumber * 3 + i] = eidx; + } + facenumber++; + } + } + tetloop.tet = tetrahedrontraverse(); + } + + // Output the face-to-edge map. + if (out == (tetgenio *) NULL) { + strcpy(edgefilename, b->outfilename); + strcat(edgefilename, ".f2e"); + } + if (!b->quiet) { + if (out == (tetgenio *) NULL) { + printf("Writing %s.\n", edgefilename); + } else { + printf("Writing face-to-edge map.\n"); + } + } + if (out == (tetgenio *) NULL) { + outfile = fopen(edgefilename, "w"); + for (tidx = 0; tidx < fsize; tidx++) { // Re-use `tidx' + i = tidx * 3; + fprintf(outfile, "%4d %d %d %d\n", tidx + in->firstnumber, + face2edgelist[i], face2edgelist[i+1], face2edgelist[i+2]); + } + fclose(outfile); + delete [] face2edgelist; + } else { + // Simply copy the address of the list to the output. + out->face2edgelist = face2edgelist; + } + } // if (b->facesout) + + // Output the tetrahedron-to-edge map. + if (out == (tetgenio *) NULL) { + strcpy(edgefilename, b->outfilename); + strcat(edgefilename, ".t2e"); + } + if (!b->quiet) { + if (out == (tetgenio *) NULL) { + printf("Writing %s.\n", edgefilename); + } else { + printf("Writing tetrahedron-to-edge map.\n"); + } + } + if (out == (tetgenio *) NULL) { + outfile = fopen(edgefilename, "w"); + for (tidx = 0; tidx < tsize; tidx++) { + i = tidx * 6; + fprintf(outfile, "%4d %d %d %d %d %d %d\n", tidx + in->firstnumber, + tet2edgelist[i], tet2edgelist[i+1], tet2edgelist[i+2], + tet2edgelist[i+3], tet2edgelist[i+4], tet2edgelist[i+5]); + } + fclose(outfile); + delete [] tet2edgelist; + } else { + // Simply copy the address of the list to the output. + out->tet2edgelist = tet2edgelist; + } + } +} + +//============================================================================// +// // +// outsubsegments() Output segments to a .edge file or a structure. // +// // +//============================================================================// + +void tetgenmesh::outsubsegments(tetgenio* out) +{ + FILE *outfile = NULL; + char edgefilename[FILENAMESIZE]; + int *elist = NULL; + int index, i; + face edgeloop; + point torg, tdest; + int firstindex, shift; + int marker; + int edgenumber; + + // For -o2 option. + triface workface, spintet; + point *extralist, pp = NULL; + int highorderindex = 11; + int o2index = 0; + + // For -nn option. + int neigh = -1; + int index2 = 0; + + int t1ver; // used by fsymself() + + if (out == (tetgenio *) NULL) { + strcpy(edgefilename, b->outfilename); + strcat(edgefilename, ".edge"); + } + + if (!b->quiet) { + if (out == (tetgenio *) NULL) { + printf("Writing %s.\n", edgefilename); + } else { + printf("Writing edges.\n"); + } + } + + if (out == (tetgenio *) NULL) { + outfile = fopen(edgefilename, "w"); + if (outfile == (FILE *) NULL) { + printf("File I/O Error: Cannot create file %s.\n", edgefilename); + terminatetetgen(this, 3); + } + // Number of subsegments. + fprintf(outfile, "%ld 1\n", subsegs->items); + } else { + // Allocate memory for 'edgelist'. + out->edgelist = new int[subsegs->items * (b->order == 1 ? 2 : 3)]; + if (out->edgelist == (int *) NULL) { + terminatetetgen(this, 1); + } + if (b->order == 2) { + out->o2edgelist = new int[subsegs->items]; + } + out->edgemarkerlist = new int[subsegs->items]; + if (out->edgemarkerlist == (int *) NULL) { + terminatetetgen(this, 1); + } + if (b->neighout > 1) { + out->edge2tetlist = new int[subsegs->items]; + } + out->numberofedges = subsegs->items; + elist = out->edgelist; + } + + // Determine the first index (0 or 1). + firstindex = b->zeroindex ? 0 : in->firstnumber; + shift = 0; // Default no shiftment. + if ((in->firstnumber == 1) && (firstindex == 0)) { + shift = 1; // Shift the output indices by 1. + } + index = 0; + i = 0; + + subsegs->traversalinit(); + edgeloop.sh = shellfacetraverse(subsegs); + edgenumber = firstindex; // in->firstnumber; + while (edgeloop.sh != (shellface *) NULL) { + torg = sorg(edgeloop); + tdest = sdest(edgeloop); + if ((b->order == 2) || (b->neighout > 1)) { + sstpivot1(edgeloop, workface); + if (workface.tet != NULL) { + // We must find a non-hull tet. + if (ishulltet(workface)) { + spintet = workface; + while (1) { + fnextself(spintet); + if (!ishulltet(spintet)) break; + if (spintet.tet == workface.tet) break; + } + workface = spintet; + } + } + } + if (b->order == 2) { // -o2 + // Get the extra vertex on this edge. + if (workface.tet != NULL) { + extralist = (point *) workface.tet[highorderindex]; + pp = extralist[ver2edge[workface.ver]]; + } else { + pp = torg; // There is no extra node available. + } + } + if (b->neighout > 1) { // -nn + if (workface.tet != NULL) { + neigh = elemindex(workface.tet); + } else { + neigh = -1; + } + } + marker = shellmark(edgeloop); + if (marker == 0) { + marker = 1; // Default marker of a boundary edge is 1. + } + if (out == (tetgenio *) NULL) { + fprintf(outfile, "%5d %4d %4d", edgenumber, + pointmark(torg) - shift, pointmark(tdest) - shift); + if (b->order == 2) { // -o2 + fprintf(outfile, " %4d", pointmark(pp) - shift); + } + fprintf(outfile, " %d", marker); + if (b->neighout > 1) { // -nn + fprintf(outfile, " %4d", neigh); + } + fprintf(outfile, "\n"); + } else { + // Output three vertices of this face; + elist[index++] = pointmark(torg) - shift; + elist[index++] = pointmark(tdest) - shift; + if (b->order == 2) { // -o2 + out->o2edgelist[o2index++] = pointmark(pp) - shift; + } + out->edgemarkerlist[i++] = marker; + if (b->neighout > 1) { // -nn + out->edge2tetlist[index2++] = neigh; + } + } + edgenumber++; + edgeloop.sh = shellfacetraverse(subsegs); + } + + if (out == (tetgenio *) NULL) { + fprintf(outfile, "# Generated by %s\n", b->commandline); + fclose(outfile); + } +} + +//============================================================================// +// // +// outneighbors() Output tet neighbors to a .neigh file or a structure. // +// // +//============================================================================// + +void tetgenmesh::outneighbors(tetgenio* out) +{ + FILE *outfile = NULL; + char neighborfilename[FILENAMESIZE]; + int *nlist = NULL; + int index = 0; + triface tetloop, tetsym; + int neighbori[4]; + int firstindex; + int elementnumber; + long ntets; + + if (out == (tetgenio *) NULL) { + strcpy(neighborfilename, b->outfilename); + strcat(neighborfilename, ".neigh"); + } + + if (!b->quiet) { + if (out == (tetgenio *) NULL) { + printf("Writing %s.\n", neighborfilename); + } else { + printf("Writing neighbors.\n"); + } + } + + ntets = tetrahedrons->items - hullsize; + + if (out == (tetgenio *) NULL) { + outfile = fopen(neighborfilename, "w"); + if (outfile == (FILE *) NULL) { + printf("File I/O Error: Cannot create file %s.\n", neighborfilename); + terminatetetgen(this, 1); + } + // Number of tetrahedra, four faces per tetrahedron. + fprintf(outfile, "%ld %d\n", ntets, 4); + } else { + // Allocate memory for 'neighborlist'. + out->neighborlist = new int[ntets * 4]; + if (out->neighborlist == (int *) NULL) { + printf("Error: Out of memory.\n"); + terminatetetgen(this, 1); + } + nlist = out->neighborlist; + } + + // Determine the first index (0 or 1). + firstindex = b->zeroindex ? 0 : in->firstnumber; + + tetrahedrons->traversalinit(); + tetloop.tet = tetrahedrontraverse(); + elementnumber = firstindex; // in->firstnumber; + while (tetloop.tet != (tetrahedron *) NULL) { + for (tetloop.ver = 0; tetloop.ver < 4; tetloop.ver++) { + fsym(tetloop, tetsym); + if (!ishulltet(tetsym)) { + neighbori[tetloop.ver] = elemindex(tetsym.tet); + } else { + neighbori[tetloop.ver] = -1; + } + } + if (out == (tetgenio *) NULL) { + // Tetrahedra number, neighboring tetrahedron numbers. + fprintf(outfile, "%4d %4d %4d %4d %4d\n", elementnumber, + neighbori[0], neighbori[1], neighbori[2], neighbori[3]); + } else { + nlist[index++] = neighbori[0]; + nlist[index++] = neighbori[1]; + nlist[index++] = neighbori[2]; + nlist[index++] = neighbori[3]; + } + tetloop.tet = tetrahedrontraverse(); + elementnumber++; + } + + if (out == (tetgenio *) NULL) { + fprintf(outfile, "# Generated by %s\n", b->commandline); + fclose(outfile); + } +} + +//============================================================================// +// // +// outvoronoi() Output the Voronoi diagram to .v.node, .v.edge, v.face, // +// and .v.cell. // +// // +// The Voronoi diagram is the geometric dual of the Delaunay triangulation. // +// The Voronoi vertices are the circumcenters of Delaunay tetrahedra. Each // +// Voronoi edge connects two Voronoi vertices at two sides of a common Dela- // +// unay face. At a face of convex hull, it becomes a ray (goto the infinity). // +// A Voronoi face is the convex hull of all Voronoi vertices around a common // +// Delaunay edge. It is a closed polygon for any internal Delaunay edge. At a // +// ridge, it is unbounded. Each Voronoi cell is the convex hull of all Vor- // +// onoi vertices around a common Delaunay vertex. It is a polytope for any // +// internal Delaunay vertex. It is an unbounded polyhedron for a Delaunay // +// vertex belonging to the convex hull. // +// // +// NOTE: This routine is only used when the input is only a set of point. // +// Comment: Special thanks to Victor Liu for finding and fixing few bugs. // +// // +//============================================================================// + +void tetgenmesh::outvoronoi(tetgenio* out) +{ + FILE *outfile = NULL; + char outfilename[FILENAMESIZE]; + tetgenio::voroedge *vedge = NULL; + tetgenio::vorofacet *vfacet = NULL; + arraypool *tetlist, *ptlist; + triface tetloop, worktet, spintet, firsttet; + point pt[4], ploop, neipt; + REAL ccent[3], infvec[3], vec1[3], vec2[3], L; + long ntets, faces, edges; + int *indexarray, *fidxs, *eidxs; + int arraysize, *vertarray = NULL; + int vpointcount, vedgecount, vfacecount, tcount; + int ishullvert, ishullface; + int index, shift, end1, end2; + int i, j; + + int t1ver; // used by fsymself() + + // Output Voronoi vertices to .v.node file. + if (out == (tetgenio *) NULL) { + strcpy(outfilename, b->outfilename); + strcat(outfilename, ".v.node"); + } + + if (!b->quiet) { + if (out == (tetgenio *) NULL) { + printf("Writing %s.\n", outfilename); + } else { + printf("Writing Voronoi vertices.\n"); + } + } + + // Determine the first index (0 or 1). + shift = (b->zeroindex ? 0 : in->firstnumber); + + // Each face and edge of the tetrahedral mesh will be indexed for indexing + // the Voronoi edges and facets. Indices of faces and edges are saved in + // each tetrahedron (including hull tets). + + // Allocate the total space once. + indexarray = new int[tetrahedrons->items * 10]; + + // Allocate space (10 integers) into each tetrahedron. It re-uses the slot + // for element markers, flags. + i = 0; + tetrahedrons->traversalinit(); + tetloop.tet = alltetrahedrontraverse(); + while (tetloop.tet != NULL) { + tetloop.tet[11] = (tetrahedron) &(indexarray[i * 10]); + i++; + tetloop.tet = alltetrahedrontraverse(); + } + + // The number of tetrahedra (excluding hull tets) (Voronoi vertices). + ntets = tetrahedrons->items - hullsize; + // The number of Delaunay faces (Voronoi edges). + faces = (4l * ntets + hullsize) / 2l; + // The number of Delaunay edges (Voronoi faces). + long vsize = points->items - dupverts - unuverts; + if (b->weighted) vsize -= nonregularcount; + if (!nonconvex) { + edges = vsize + faces - ntets - 1; + } else { + if (meshedges == 0l) { + numberedges(); // Count edges. + } + edges = meshedges; + } + + if (out == (tetgenio *) NULL) { + outfile = fopen(outfilename, "w"); + if (outfile == (FILE *) NULL) { + printf("File I/O Error: Cannot create file %s.\n", outfilename); + terminatetetgen(this, 3); + } + // Number of voronoi points, 3 dim, no attributes, no marker. + fprintf(outfile, "%ld 3 0 0\n", ntets); + } else { + // Allocate space for 'vpointlist'. + out->numberofvpoints = (int) ntets; + out->vpointlist = new REAL[out->numberofvpoints * 3]; + if (out->vpointlist == (REAL *) NULL) { + terminatetetgen(this, 1); + } + } + + // Output Voronoi vertices (the circumcenters of tetrahedra). + tetrahedrons->traversalinit(); + tetloop.tet = tetrahedrontraverse(); + vpointcount = 0; // The (internal) v-index always starts from 0. + index = 0; + while (tetloop.tet != (tetrahedron *) NULL) { + for (i = 0; i < 4; i++) { + pt[i] = (point) tetloop.tet[4 + i]; + setpoint2tet(pt[i], encode(tetloop)); + } + if (b->weighted) { + orthosphere(pt[0], pt[1], pt[2], pt[3], pt[0][3], pt[1][3], pt[2][3], + pt[3][3], ccent, NULL); + } else { + circumsphere(pt[0], pt[1], pt[2], pt[3], ccent, NULL); + } + if (out == (tetgenio *) NULL) { + fprintf(outfile, "%4d %16.8e %16.8e %16.8e\n", vpointcount + shift, + ccent[0], ccent[1], ccent[2]); + } else { + out->vpointlist[index++] = ccent[0]; + out->vpointlist[index++] = ccent[1]; + out->vpointlist[index++] = ccent[2]; + } + setelemindex(tetloop.tet, vpointcount); + vpointcount++; + tetloop.tet = tetrahedrontraverse(); + } + + if (out == (tetgenio *) NULL) { + fprintf(outfile, "# Generated by %s\n", b->commandline); + fclose(outfile); + } + + // Output Voronoi edges to .v.edge file. + if (out == (tetgenio *) NULL) { + strcpy(outfilename, b->outfilename); + strcat(outfilename, ".v.edge"); + } + + if (!b->quiet) { + if (out == (tetgenio *) NULL) { + printf("Writing %s.\n", outfilename); + } else { + printf("Writing Voronoi edges.\n"); + } + } + + if (out == (tetgenio *) NULL) { + outfile = fopen(outfilename, "w"); + if (outfile == (FILE *) NULL) { + printf("File I/O Error: Cannot create file %s.\n", outfilename); + terminatetetgen(this, 3); + } + // Number of Voronoi edges, no marker. + fprintf(outfile, "%ld 0\n", faces); + } else { + // Allocate space for 'vpointlist'. + out->numberofvedges = (int) faces; + out->vedgelist = new tetgenio::voroedge[out->numberofvedges]; + } + + // Output the Voronoi edges. + tetrahedrons->traversalinit(); + tetloop.tet = tetrahedrontraverse(); + vedgecount = 0; // D-Face (V-edge) index (from zero). + index = 0; // The Delaunay-face index. + while (tetloop.tet != (tetrahedron *) NULL) { + // Count the number of Voronoi edges. Look at the four faces of each + // tetrahedron. Count the face if the tetrahedron's index is + // smaller than its neighbor's or the neighbor is outside. + end1 = elemindex(tetloop.tet); + for (tetloop.ver = 0; tetloop.ver < 4; tetloop.ver++) { + fsym(tetloop, worktet); + if (ishulltet(worktet) || + (elemindex(tetloop.tet) < elemindex(worktet.tet))) { + // Found a Voronoi edge. Operate on it. + if (out == (tetgenio *) NULL) { + fprintf(outfile, "%4d %4d", vedgecount + shift, end1 + shift); + } else { + vedge = &(out->vedgelist[index++]); + vedge->v1 = end1 + shift; + } + if (!ishulltet(worktet)) { + end2 = elemindex(worktet.tet); + } else { + end2 = -1; + } + // Note that end2 may be -1 (worktet.tet is outside). + if (end2 == -1) { + // Calculate the out normal of this hull face. + pt[0] = dest(worktet); + pt[1] = org(worktet); + pt[2] = apex(worktet); + for (j = 0; j < 3; j++) vec1[j] = pt[1][j] - pt[0][j]; + for (j = 0; j < 3; j++) vec2[j] = pt[2][j] - pt[0][j]; + cross(vec1, vec2, infvec); + // Normalize it. + L = sqrt(infvec[0] * infvec[0] + infvec[1] * infvec[1] + + infvec[2] * infvec[2]); + if (L > 0) for (j = 0; j < 3; j++) infvec[j] /= L; + if (out == (tetgenio *) NULL) { + fprintf(outfile, " -1"); + fprintf(outfile, " %g %g %g\n", infvec[0], infvec[1], infvec[2]); + } else { + vedge->v2 = -1; + vedge->vnormal[0] = infvec[0]; + vedge->vnormal[1] = infvec[1]; + vedge->vnormal[2] = infvec[2]; + } + } else { + if (out == (tetgenio *) NULL) { + fprintf(outfile, " %4d\n", end2 + shift); + } else { + vedge->v2 = end2 + shift; + vedge->vnormal[0] = 0.0; + vedge->vnormal[1] = 0.0; + vedge->vnormal[2] = 0.0; + } + } + // Save the V-edge index in this tet and its neighbor. + fidxs = (int *) (tetloop.tet[11]); + fidxs[tetloop.ver] = vedgecount; + fidxs = (int *) (worktet.tet[11]); + fidxs[worktet.ver & 3] = vedgecount; + vedgecount++; + } + } // tetloop.ver + tetloop.tet = tetrahedrontraverse(); + } + + if (out == (tetgenio *) NULL) { + fprintf(outfile, "# Generated by %s\n", b->commandline); + fclose(outfile); + } + + // Output Voronoi faces to .v.face file. + if (out == (tetgenio *) NULL) { + strcpy(outfilename, b->outfilename); + strcat(outfilename, ".v.face"); + } + + if (!b->quiet) { + if (out == (tetgenio *) NULL) { + printf("Writing %s.\n", outfilename); + } else { + printf("Writing Voronoi faces.\n"); + } + } + + if (out == (tetgenio *) NULL) { + outfile = fopen(outfilename, "w"); + if (outfile == (FILE *) NULL) { + printf("File I/O Error: Cannot create file %s.\n", outfilename); + terminatetetgen(this, 3); + } + // Number of Voronoi faces. + fprintf(outfile, "%ld 0\n", edges); + } else { + out->numberofvfacets = edges; + out->vfacetlist = new tetgenio::vorofacet[out->numberofvfacets]; + if (out->vfacetlist == (tetgenio::vorofacet *) NULL) { + terminatetetgen(this, 1); + } + } + + // Output the Voronoi facets. + tetrahedrons->traversalinit(); + tetloop.tet = tetrahedrontraverse(); + vfacecount = 0; // D-edge (V-facet) index (from zero). + while (tetloop.tet != (tetrahedron *) NULL) { + // Count the number of Voronoi faces. Look at the six edges of each + // tetrahedron. Count the edge only if the tetrahedron's index is + // smaller than those of all other tetrahedra that share the edge. + worktet.tet = tetloop.tet; + for (i = 0; i < 6; i++) { + worktet.ver = edge2ver[i]; + // Count the number of faces at this edge. If the edge is a hull edge, + // the face containing dummypoint is also counted. + //ishulledge = 0; // Is it a hull edge. + tcount = 0; + firsttet = worktet; + spintet = worktet; + while (1) { + tcount++; + fnextself(spintet); + if (spintet.tet == worktet.tet) break; + if (!ishulltet(spintet)) { + if (elemindex(spintet.tet) < elemindex(worktet.tet)) break; + } else { + //ishulledge = 1; + if (apex(spintet) == dummypoint) { + // We make this V-edge appear in the end of the edge list. + fnext(spintet, firsttet); + } + } + } // while (1) + if (spintet.tet == worktet.tet) { + // Found a Voronoi facet. Operate on it. + pt[0] = org(worktet); + pt[1] = dest(worktet); + end1 = pointmark(pt[0]) - in->firstnumber; // V-cell index + end2 = pointmark(pt[1]) - in->firstnumber; + if (out == (tetgenio *) NULL) { + fprintf(outfile, "%4d %4d %4d %-2d ", vfacecount + shift, + end1 + shift, end2 + shift, tcount); + } else { + vfacet = &(out->vfacetlist[vfacecount]); + vfacet->c1 = end1 + shift; + vfacet->c2 = end2 + shift; + vfacet->elist = new int[tcount + 1]; + vfacet->elist[0] = tcount; + index = 1; + } + // Output V-edges of this V-facet. + spintet = firsttet; //worktet; + while (1) { + fidxs = (int *) (spintet.tet[11]); + if (apex(spintet) != dummypoint) { + vedgecount = fidxs[spintet.ver & 3]; + ishullface = 0; + } else { + ishullface = 1; // It's not a real face. + } + if (out == (tetgenio *) NULL) { + fprintf(outfile, " %d", !ishullface ? (vedgecount + shift) : -1); + } else { + vfacet->elist[index++] = !ishullface ? (vedgecount + shift) : -1; + } + // Save the V-facet index in this tet at this edge. + eidxs = &(fidxs[4]); + eidxs[ver2edge[spintet.ver]] = vfacecount; + // Go to the next face. + fnextself(spintet); + if (spintet.tet == firsttet.tet) break; + } // while (1) + if (out == (tetgenio *) NULL) { + fprintf(outfile, "\n"); + } + vfacecount++; + } // if (spintet.tet == worktet.tet) + } // if (i = 0; i < 6; i++) + tetloop.tet = tetrahedrontraverse(); + } + + if (out == (tetgenio *) NULL) { + fprintf(outfile, "# Generated by %s\n", b->commandline); + fclose(outfile); + } + + // Output Voronoi cells to .v.cell file. + if (out == (tetgenio *) NULL) { + strcpy(outfilename, b->outfilename); + strcat(outfilename, ".v.cell"); + } + + if (!b->quiet) { + if (out == (tetgenio *) NULL) { + printf("Writing %s.\n", outfilename); + } else { + printf("Writing Voronoi cells.\n"); + } + } + + if (out == (tetgenio *) NULL) { + outfile = fopen(outfilename, "w"); + if (outfile == (FILE *) NULL) { + printf("File I/O Error: Cannot create file %s.\n", outfilename); + terminatetetgen(this, 3); + } + // Number of Voronoi cells. + fprintf(outfile, "%ld\n", points->items - unuverts - dupverts); + } else { + out->numberofvcells = points->items - unuverts - dupverts; + out->vcelllist = new int*[out->numberofvcells]; + if (out->vcelllist == (int **) NULL) { + terminatetetgen(this, 1); + } + } + + // Output Voronoi cells. + tetlist = cavetetlist; + ptlist = cavetetvertlist; + points->traversalinit(); + ploop = pointtraverse(); + vpointcount = 0; + while (ploop != (point) NULL) { + if ((pointtype(ploop) != UNUSEDVERTEX) && + (pointtype(ploop) != DUPLICATEDVERTEX) && + (pointtype(ploop) != NREGULARVERTEX)) { + getvertexstar(1, ploop, tetlist, ptlist, NULL); + // Mark all vertices. Check if it is a hull vertex. + ishullvert = 0; + for (i = 0; i < ptlist->objects; i++) { + neipt = * (point *) fastlookup(ptlist, i); + if (neipt != dummypoint) { + pinfect(neipt); + } else { + ishullvert = 1; + } + } + tcount = (int) ptlist->objects; + if (out == (tetgenio *) NULL) { + fprintf(outfile, "%4d %-2d ", vpointcount + shift, tcount); + } else { + arraysize = tcount; + vertarray = new int[arraysize + 1]; + out->vcelllist[vpointcount] = vertarray; + vertarray[0] = tcount; + index = 1; + } + // List Voronoi facets bounding this cell. + for (i = 0; i < tetlist->objects; i++) { + worktet = * (triface *) fastlookup(tetlist, i); + // Let 'worktet' be [a,b,c,d] where d = ploop. + for (j = 0; j < 3; j++) { + neipt = org(worktet); // neipt is a, or b, or c + // Skip the dummypoint. + if (neipt != dummypoint) { + if (pinfected(neipt)) { + // It's not processed yet. + puninfect(neipt); + // Go to the DT edge [a,d], or [b,d], or [c,d]. + esym(worktet, spintet); + enextself(spintet); + // Get the V-face dual to this edge. + eidxs = (int *) spintet.tet[11]; + vfacecount = eidxs[4 + ver2edge[spintet.ver]]; + if (out == (tetgenio *) NULL) { + fprintf(outfile, " %d", vfacecount + shift); + } else { + vertarray[index++] = vfacecount + shift; + } + } + } + enextself(worktet); + } // j + } // i + if (ishullvert) { + // Add a hull facet (-1) to the facet list. + if (out == (tetgenio *) NULL) { + fprintf(outfile, " -1"); + } else { + vertarray[index++] = -1; + } + } + if (out == (tetgenio *) NULL) { + fprintf(outfile, "\n"); + } + tetlist->restart(); + ptlist->restart(); + vpointcount++; + } + ploop = pointtraverse(); + } + + // Delete the space for face/edge indices. + delete [] indexarray; + + if (out == (tetgenio *) NULL) { + fprintf(outfile, "# Generated by %s\n", b->commandline); + fclose(outfile); + } +} + +//============================================================================// +// // +// outsmesh() Write surface mesh to a .smesh file, which can be read and // +// tetrahedralized by TetGen. // +// // +// You can specify a filename (without suffix) in 'smfilename'. If you don't // +// supply a filename (let smfilename be NULL), the default name stored in // +// 'tetgenbehavior' will be used. // +// // +//============================================================================// + +void tetgenmesh::outsmesh(char* smfilename) +{ + FILE *outfile; + char nodfilename[FILENAMESIZE]; + char smefilename[FILENAMESIZE]; + face faceloop; + point p1, p2, p3; + int firstindex, shift; + int bmark; + int marker; + int i; + + if (smfilename != (char *) NULL && smfilename[0] != '\0') { + strcpy(smefilename, smfilename); + } else if (b->outfilename[0] != '\0') { + strcpy(smefilename, b->outfilename); + } else { + strcpy(smefilename, "unnamed"); + } + strcpy(nodfilename, smefilename); + strcat(smefilename, ".smesh"); + strcat(nodfilename, ".node"); + + if (!b->quiet) { + printf("Writing %s.\n", smefilename); + } + outfile = fopen(smefilename, "w"); + if (outfile == (FILE *) NULL) { + printf("File I/O Error: Cannot create file %s.\n", smefilename); + return; + } + + // Determine the first index (0 or 1). + firstindex = b->zeroindex ? 0 : in->firstnumber; + shift = 0; // Default no shiftment. + if ((in->firstnumber == 1) && (firstindex == 0)) { + shift = 1; // Shift the output indices by 1. + } + + fprintf(outfile, "# %s. TetGen's input file.\n", smefilename); + fprintf(outfile, "\n# part 1: node list.\n"); + fprintf(outfile, "0 3 0 0 # nodes are found in %s.\n", nodfilename); + + marker = 0; // avoid compile warning. + bmark = !b->nobound && (in->facetmarkerlist || in->trifacemarkerlist); + + fprintf(outfile, "\n# part 2: facet list.\n"); + // Number of facets, boundary marker. + fprintf(outfile, "%ld %d\n", subfaces->items, bmark); + + subfaces->traversalinit(); + faceloop.sh = shellfacetraverse(subfaces); + while (faceloop.sh != (shellface *) NULL) { + p1 = sorg(faceloop); + p2 = sdest(faceloop); + p3 = sapex(faceloop); + if (bmark) { + marker = shellmark(faceloop); + } + fprintf(outfile, "3 %4d %4d %4d", pointmark(p1) - shift, + pointmark(p2) - shift, pointmark(p3) - shift); + if (bmark) { + fprintf(outfile, " %d", marker); + } + fprintf(outfile, "\n"); + faceloop.sh = shellfacetraverse(subfaces); + } + + // Copy input holelist. + fprintf(outfile, "\n# part 3: hole list.\n"); + fprintf(outfile, "%d\n", in->numberofholes); + for (i = 0; i < in->numberofholes; i++) { + fprintf(outfile, "%d %g %g %g\n", i + in->firstnumber, + in->holelist[i * 3], in->holelist[i * 3 + 1], + in->holelist[i * 3 + 2]); + } + + // Copy input regionlist. + fprintf(outfile, "\n# part 4: region list.\n"); + fprintf(outfile, "%d\n", in->numberofregions); + for (i = 0; i < in->numberofregions; i++) { + fprintf(outfile, "%d %g %g %g %d %g\n", i + in->firstnumber, + in->regionlist[i * 5], in->regionlist[i * 5 + 1], + in->regionlist[i * 5 + 2], (int) in->regionlist[i * 5 + 3], + in->regionlist[i * 5 + 4]); + } + + fprintf(outfile, "# Generated by %s\n", b->commandline); + fclose(outfile); +} + +//============================================================================// +// // +// outmesh2medit() Write mesh to a .mesh file, which can be read and // +// rendered by Medit (a free mesh viewer from INRIA). // +// // +// You can specify a filename (without suffix) in 'mfilename'. If you don't // +// supply a filename (let mfilename be NULL), the default name stored in // +// 'tetgenbehavior' will be used. The output file will have the suffix .mesh. // +// // +//============================================================================// + +void tetgenmesh::outmesh2medit(char* mfilename) +{ + FILE *outfile; + char mefilename[FILENAMESIZE]; + tetrahedron* tetptr; + triface tface, tsymface; + face faceloop, segloop, checkmark; + point ptloop, p1, p2, p3, p4; + long ntets, faces; + int pointnumber; + int marker; + int i; + + if (mfilename != (char *) NULL && mfilename[0] != '\0') { + strcpy(mefilename, mfilename); + } else if (b->outfilename[0] != '\0') { + strcpy(mefilename, b->outfilename); + } else { + strcpy(mefilename, "unnamed"); + } + strcat(mefilename, ".mesh"); + + int *subdomains_facets = NULL; + int *subdomains_facets_ori = NULL; + int sub_count = 0; // Count the number of indexed subdomains. + if (subdomains > 0) { + subdomains_facets = new int[subdomains]; + subdomains_facets_ori = new int[subdomains]; + for (i = 0; i < subdomains; i++) { + subdomains_facets_ori[i] = 0; // initialise + } + } + + if (!b->quiet) { + printf("Writing %s.\n", mefilename); + } + outfile = fopen(mefilename, "w"); + if (outfile == (FILE *) NULL) { + printf("File I/O Error: Cannot create file %s.\n", mefilename); + return; + } + + fprintf(outfile, "MeshVersionFormatted 1\n"); + fprintf(outfile, "\n"); + fprintf(outfile, "Dimension\n"); + fprintf(outfile, "3\n"); + fprintf(outfile, "\n"); + + fprintf(outfile, "\n# Set of mesh vertices\n"); + fprintf(outfile, "Vertices\n"); + fprintf(outfile, "%ld\n", points->items); + + points->traversalinit(); + ptloop = pointtraverse(); + pointnumber = 1; // Medit need start number form 1. + while (ptloop != (point) NULL) { + // Point coordinates. + fprintf(outfile, "%.17g %.17g %.17g", ptloop[0], ptloop[1], ptloop[2]); + if (in->numberofpointattributes > 0) { + // Write an attribute, ignore others if more than one. + fprintf(outfile, " %.17g\n", ptloop[3]); + } else { + fprintf(outfile, " 0\n"); + } + setpointmark(ptloop, pointnumber); + ptloop = pointtraverse(); + pointnumber++; + } + + if (b->plc || b->refine) { + fprintf(outfile, "\nEdges\n"); + fprintf(outfile, "%ld\n", subsegs->items); + + subsegs->traversalinit(); + segloop.sh = shellfacetraverse(subsegs); + while (segloop.sh != (shellface *) NULL) { + p1 = sorg(segloop); + p2 = sdest(segloop); + fprintf(outfile, "%5d %5d", pointmark(p1), pointmark(p2)); + marker = shellmark(segloop); + fprintf(outfile, " %d\n", marker); + segloop.sh = shellfacetraverse(subsegs); + } + } + + ntets = tetrahedrons->items - hullsize; + + faces = subfaces->items; + triface abuttingtet; + int t1ver; + + fprintf(outfile, "\n# Set of Triangles\n"); + fprintf(outfile, "Triangles\n"); + fprintf(outfile, "%ld\n", faces); + + subfaces->traversalinit(); + faceloop.sh = shellfacetraverse(subfaces); + int facidx = 1; // Index facets for subdomains. + while (faceloop.sh != (shellface *) NULL) { + stpivot(faceloop, abuttingtet); + if (abuttingtet.tet != NULL) { + if (ishulltet(abuttingtet)) { + fsymself(abuttingtet); + } + } + if (abuttingtet.tet != NULL) { + p1 = org (abuttingtet); + p2 = dest(abuttingtet); + p3 = apex(abuttingtet); + if (subdomains) { + int attr = elemattribute(abuttingtet.tet, 0); + int idx = attr - 1; + if (subdomain_markers[idx] != attr) { + // search it. + } + if (subdomains_facets_ori[idx] == 0) { + subdomains_facets[idx] = facidx; + subdomains_facets_ori[idx] = 1; + sub_count++; + fsym(abuttingtet, tsymface); + if ((tsymface.tet != NULL) && !ishulltet(tsymface)) { + attr = elemattribute(tsymface.tet, 0); + idx = attr - 1; + if (subdomain_markers[idx] != attr) { + // search it. + } + if (subdomains_facets_ori[idx] == 0) { + subdomains_facets[idx] = facidx; + subdomains_facets_ori[idx] = -1; + sub_count++; + } + } + } + } + } else { + // A dangling subfacet. + p1 = sorg(faceloop); + p2 = sdest(faceloop); + p3 = sapex(faceloop); + } + marker = shellmark(faceloop); + fprintf(outfile, "%5d %5d %5d %d\n", + pointmark(p1), pointmark(p2), pointmark(p3), marker); + //setelemindex(faceloop.sh, facidx); + facidx++; + faceloop.sh = shellfacetraverse(subfaces); + } + + + fprintf(outfile, "\n# Set of Tetrahedra\n"); + fprintf(outfile, "Tetrahedra\n"); + fprintf(outfile, "%ld\n", ntets); + + tetrahedrons->traversalinit(); + tetptr = tetrahedrontraverse(); + while (tetptr != (tetrahedron *) NULL) { + if (!b->reversetetori) { + p1 = (point) tetptr[4]; + p2 = (point) tetptr[5]; + } else { + p1 = (point) tetptr[5]; + p2 = (point) tetptr[4]; + } + p3 = (point) tetptr[6]; + p4 = (point) tetptr[7]; + fprintf(outfile, "%5d %5d %5d %5d", + pointmark(p1), pointmark(p2), pointmark(p3), pointmark(p4)); + if (numelemattrib > 0) { + fprintf(outfile, " %.17g", elemattribute(tetptr, 0)); + } else { + fprintf(outfile, " 0"); + } + fprintf(outfile, "\n"); + tetptr = tetrahedrontraverse(); + } + + //fprintf(outfile, "\nCorners\n"); + //fprintf(outfile, "%d\n", in->numberofpoints); + //for (i = 0; i < in->numberofpoints; i++) { + // fprintf(outfile, "%4d\n", i + 1); + //} + + if (subdomains > 0) { + fprintf(outfile, "\nSubDomainFromGeom\n"); + fprintf(outfile, "%d\n", subdomains); + for (i = 0; i < subdomains; i++) { + fprintf(outfile, "3 %d %d %d\n", + subdomains_facets[i], + subdomains_facets_ori[i], + subdomain_markers[i]); + } + delete [] subdomains_facets; + delete [] subdomains_facets_ori; + } + + fprintf(outfile, "\nEnd\n"); + fclose(outfile); +} + + + + + +//============================================================================// +// // +// outmesh2vtk() Save mesh to file in VTK Legacy format. // +// // +// This function was contributed by Bryn Llyod from ETH, 2007. // +// // +//============================================================================// + +void tetgenmesh::outmesh2vtk(char* ofilename, int mesh_idx) +{ + FILE *outfile; + char vtkfilename[FILENAMESIZE]; + point pointloop, p1, p2, p3, p4; + tetrahedron* tptr; + double x, y, z; + int n1, n2, n3, n4; + int nnodes = 4; + int celltype = 10; + + if (b->order == 2) { + printf(" Write VTK not implemented for order 2 elements \n"); + return; + } + + int NEL = tetrahedrons->items - hullsize; + int NN = points->items; + + if (ofilename != (char *) NULL && ofilename[0] != '\0') { + //strcpy(vtkfilename, ofilename); + sprintf(vtkfilename, "%s.%d.vtk", ofilename, mesh_idx); + } else if (b->outfilename[0] != '\0') { + strcpy(vtkfilename, b->outfilename); + strcat(vtkfilename, ".vtk"); + } else { + strcpy(vtkfilename, "noname.vtk"); + } + + if (!b->quiet) { + printf("Writing %s.\n", vtkfilename); + } + outfile = fopen(vtkfilename, "w"); + if (outfile == (FILE *) NULL) { + printf("File I/O Error: Cannot create file %s.\n", vtkfilename); + return; + } + + //always write big endian + //bool ImALittleEndian = !testIsBigEndian(); + + fprintf(outfile, "# vtk DataFile Version 2.0\n"); + fprintf(outfile, "Unstructured Grid\n"); + fprintf(outfile, "ASCII\n"); // BINARY + fprintf(outfile, "DATASET UNSTRUCTURED_GRID\n"); + fprintf(outfile, "POINTS %d double\n", NN); + + points->traversalinit(); + pointloop = pointtraverse(); + for(int id=0; idtraversalinit(); + tptr = tetrahedrontraverse(); + //elementnumber = firstindex; // in->firstnumber; + while (tptr != (tetrahedron *) NULL) { + if (!b->reversetetori) { + p1 = (point) tptr[4]; + p2 = (point) tptr[5]; + } else { + p1 = (point) tptr[5]; + p2 = (point) tptr[4]; + } + p3 = (point) tptr[6]; + p4 = (point) tptr[7]; + n1 = pointmark(p1) - in->firstnumber; + n2 = pointmark(p2) - in->firstnumber; + n3 = pointmark(p3) - in->firstnumber; + n4 = pointmark(p4) - in->firstnumber; + fprintf(outfile, "%d %4d %4d %4d %4d\n", nnodes, n1, n2, n3, n4); + tptr = tetrahedrontraverse(); + } + fprintf(outfile, "\n"); + + fprintf(outfile, "CELL_TYPES %d\n", NEL); + for(int tid=0; tid 0) { + // Output tetrahedra region attributes. + fprintf(outfile, "CELL_DATA %d\n", NEL); + fprintf(outfile, "SCALARS cell_scalars int 1\n"); + fprintf(outfile, "LOOKUP_TABLE default\n"); + tetrahedrons->traversalinit(); + tptr = tetrahedrontraverse(); + while (tptr != (tetrahedron *) NULL) { + fprintf(outfile, "%d\n", (int) elemattribute(tptr, numelemattrib - 1)); + tptr = tetrahedrontraverse(); + } + fprintf(outfile, "\n"); + } + + fclose(outfile); +} + +void tetgenmesh::out_surfmesh_vtk(char* ofilename, int mesh_idx) +{ + FILE *outfile; + char vtkfilename[FILENAMESIZE]; + triface abuttingtet; + face faceloop; + point pointloop, torg, tdest, tapex; + double x, y, z; + int n1, n2, n3; + int nnodes = 3; + int celltype = 5; // triangle + + int t1ver; + + if (b->order == 2) { + printf(" Write VTK not implemented for order 2 elements \n"); + return; + } + + int NEL = subfaces->items; //tetrahedrons->items - hullsize; + int NN = points->items; + + if (ofilename != (char *) NULL && ofilename[0] != '\0') { + //strcpy(vtkfilename, ofilename); + sprintf(vtkfilename, "%s.%d.vtk", ofilename, mesh_idx); + } else if (b->outfilename[0] != '\0') { + strcpy(vtkfilename, b->outfilename); + strcat(vtkfilename, ".surf.vtk"); + } else { + strcpy(vtkfilename, "noname.surf.vtk"); + } + + if (!b->quiet) { + printf("Writing %s.\n", vtkfilename); + } + outfile = fopen(vtkfilename, "w"); + if (outfile == (FILE *) NULL) { + printf("File I/O Error: Cannot create file %s.\n", vtkfilename); + return; + } + + //always write big endian + //bool ImALittleEndian = !testIsBigEndian(); + + fprintf(outfile, "# vtk DataFile Version 2.0\n"); + fprintf(outfile, "Unstructured Grid\n"); + fprintf(outfile, "ASCII\n"); // BINARY + fprintf(outfile, "DATASET UNSTRUCTURED_GRID\n"); + fprintf(outfile, "POINTS %d double\n", NN); + + points->traversalinit(); + pointloop = pointtraverse(); + for(int id=0; idtraversalinit(); + faceloop.sh = shellfacetraverse(subfaces); + //facenumber = firstindex; // in->firstnumber; + while (faceloop.sh != (shellface *) NULL) { + stpivot(faceloop, abuttingtet); + // If there is a tetrahedron containing this subface, orient it so + // that the normal of this face points to inside of the volume by + // right-hand rule. + if (abuttingtet.tet != NULL) { + if (ishulltet(abuttingtet)) { + fsymself(abuttingtet); + } + } + if (abuttingtet.tet != NULL) { + torg = org(abuttingtet); + tdest = dest(abuttingtet); + tapex = apex(abuttingtet); + } else { + // This may happen when only a surface mesh be generated. + torg = sorg(faceloop); + tdest = sdest(faceloop); + tapex = sapex(faceloop); + } + + n1 = pointmark(torg) - in->firstnumber; + n2 = pointmark(tdest) - in->firstnumber; + n3 = pointmark(tapex) - in->firstnumber; + + fprintf(outfile, "%d %4d %4d %4d\n", nnodes, n1, n2, n3); + //facenumber++; + faceloop.sh = shellfacetraverse(subfaces); + } + fprintf(outfile, "\n"); + + fprintf(outfile, "CELL_TYPES %d\n", NEL); + for(int tid=0; tidfacetmarkerlist != NULL) { //if (numelemattrib > 0) { + // Output tetrahedra region attributes. + fprintf(outfile, "CELL_DATA %d\n", NEL); + fprintf(outfile, "SCALARS cell_scalars int 1\n"); + fprintf(outfile, "LOOKUP_TABLE default\n"); + + subfaces->traversalinit(); + faceloop.sh = shellfacetraverse(subfaces); + //facenumber = firstindex; // in->firstnumber; + while (faceloop.sh != (shellface *) NULL) { + fprintf(outfile, "%d\n", (int) shellmark(faceloop)); + //facenumber++; + faceloop.sh = shellfacetraverse(subfaces); + } + fprintf(outfile, "\n"); + } + + fclose(outfile); +} + +//============================================================================// +// // +// out_intersected_facets() Save skipped subfaces. // +// // +//============================================================================// + +void tetgenmesh::out_intersected_facets() +{ + char FileName[1024], *sptr; + + strcpy(FileName, b->outfilename); + sptr = strrchr(b->outfilename, '.'); + if (sptr != NULL) *sptr = '\0'; + strcat(b->outfilename, "_skipped"); + + outnodes(NULL); + + strcpy(b->outfilename, FileName); // Restore the original file name. + + strcpy(FileName, b->outfilename); + sptr = strrchr(FileName, '.'); + if (sptr != NULL) *sptr = '\0'; + strcat(FileName, "_skipped.face"); + FILE *fout = fopen(FileName, "w"); + + if (!b->quiet) { + printf("Writing %s\n", FileName); + } + + // Determine the first index (0 or 1). + int firstindex = b->zeroindex ? 0 : in->firstnumber; + int shift = 0; // Default no shiftment. + if ((in->firstnumber == 1) && (firstindex == 0)) { + shift = 1; // Shift the output indices by 1. + } + + int facenumber = firstindex; // in->firstnumber; + + fprintf(fout, "%ld 1\n", skipped_facet_list->objects); + + for (int i = 0; i < (int) skipped_facet_list->objects; i++) { + badface *bf = (badface *) fastlookup(skipped_facet_list, i); + fprintf(fout, "%d %d %d %d %d\n", facenumber, + pointmark(bf->forg) - shift, + pointmark(bf->fdest) - shift, + pointmark(bf->fapex) - shift, + (int) bf->key); + // remove it from the pool of subfaces (do not output them to .face). + shellfacedealloc(subfaces, bf->ss.sh); + facenumber++; + } + + fclose(fout); +} + + +// // +// // +//== output_cxx ==============================================================// + +//== main_cxx ================================================================// +// // +// // + +//============================================================================// +// // +// tetrahedralize() The interface for users using TetGen library to // +// generate tetrahedral meshes with all features. // +// // +// The sequence is roughly as follows. Many of these steps can be skipped, // +// depending on the command line switches. // +// // +// - Initialize constants and parse the command line. // +// - Read the vertices from a file and either // +// - tetrahedralize them (no -r), or // +// - read an old mesh from files and reconstruct it (-r). // +// - Insert the boundary segments and facets (-p or -Y). // +// - Read the holes (-p), regional attributes (-pA), and regional volume // +// constraints (-pa). Carve the holes and concavities, and spread the // +// regional attributes and volume constraints. // +// - Enforce the constraints on minimum quality bound (-q) and maximum // +// volume (-a), and a mesh size function (-m). // +// - Optimize the mesh wrt. specified quality measures (-O and -o). // +// - Write the output files and print the statistics. // +// - Check the consistency of the mesh (-C). // +// // +//============================================================================// + +void tetrahedralize(tetgenbehavior *b, tetgenio *in, tetgenio *out, + tetgenio *addin, tetgenio *bgmin) +{ + tetgenmesh m; + clock_t tv[13], ts[6]; // Timing informations (defined in time.h) + REAL cps = (REAL) CLOCKS_PER_SEC; + + tv[0] = clock(); + + m.b = b; + m.in = in; + m.addin = addin; + + if (b->metric && bgmin && (bgmin->numberofpoints > 0)) { + m.bgm = new tetgenmesh(); // Create an empty background mesh. + m.bgm->b = b; + m.bgm->in = bgmin; + } + + m.initializepools(); + m.transfernodes(); + + + tv[1] = clock(); + + if (b->refine) { // -r + m.reconstructmesh(); + } else { // -p + m.incrementaldelaunay(ts[0]); + } + + tv[2] = clock(); + + if (!b->quiet) { + if (b->refine) { + printf("Mesh reconstruction seconds: %g\n", ((REAL)(tv[2]-tv[1])) / cps); + } else { + printf("Delaunay seconds: %g\n", ((REAL)(tv[2]-tv[1])) / cps); + if (b->verbose) { + printf(" Point sorting seconds: %g\n", ((REAL)(ts[0]-tv[1])) / cps); + + } + } + } + + if (b->plc && !b->refine) { // -p + m.meshsurface(); + + ts[0] = clock(); + + if (!b->quiet) { + printf("Surface mesh seconds: %g\n", ((REAL)(ts[0]-tv[2])) / cps); + } + } + + + tv[3] = clock(); + + if ((b->metric) && (m.bgm != NULL)) { // -m + m.bgm->initializepools(); + m.bgm->transfernodes(); + m.bgm->reconstructmesh(); + + ts[0] = clock(); + + if (!b->quiet) { + printf("Background mesh reconstruct seconds: %g\n", + ((REAL)(ts[0] - tv[3])) / cps); + } + + if (b->metric) { // -m + m.interpolatemeshsize(); + + ts[1] = clock(); + + if (!b->quiet) { + printf("Size interpolating seconds: %g\n",((REAL)(ts[1]-ts[0])) / cps); + } + } + } + + tv[4] = clock(); + + if (b->plc && !b->refine) { // -p + if (!b->cdt) { // no -D + m.recoverboundary(ts[0]); + } else { + m.constraineddelaunay(ts[0]); + } + + ts[1] = clock(); + + if (!b->quiet) { + if (!b->cdt) { // no -D + printf("Boundary recovery "); + } else { + printf("Constrained Delaunay "); + } + printf("seconds: %g\n", ((REAL)(ts[1] - tv[4])) / cps); + if (b->verbose) { + printf(" Segment recovery seconds: %g\n",((REAL)(ts[0]-tv[4]))/ cps); + printf(" Facet recovery seconds: %g\n", ((REAL)(ts[1]-ts[0])) / cps); + } + } + + if (m.skipped_facet_list != NULL) { + if (!b->quiet) { + printf("\n!!! %ld input triangles are skipped due to self-intersections.\n", + m.skipped_facet_list->objects); + } + + if (!b->nofacewritten) m.out_intersected_facets(); + delete m.skipped_facet_list; + m.skipped_facet_list = NULL; + + if (!b->nonodewritten) m.outnodes(out); + if (!b->noelewritten) m.outelements(out); + if (!b->nofacewritten) m.outsubfaces(out); + if (!b->nofacewritten) m.outsubsegments(out); + + terminatetetgen(NULL, 3); // This is not a normal exit. + } + + if (b->diagnose) { // -d + if (!b->quiet) { + printf("\nThe input surface mesh is correct.\n"); + } + return; + } + + m.carveholes(); + + ts[2] = clock(); + + if (!b->quiet) { + printf("Exterior tets removal seconds: %g\n",((REAL)(ts[2]-ts[1]))/cps); + } + + ts[3] = clock(); + + if ((!b->cdt || b->nobisect) && (b->supsteiner_level > 0)) { // no -D, -Y/1 + if (m.subvertstack->objects > 0l) { + m.suppresssteinerpoints(); + if (!b->quiet) { + printf("Steiner suppression seconds: %g\n", ((REAL)(ts[3]-ts[2]))/cps); + } + } + } + + if ((b->nobisect > 1)) { // -YY + if ((m.st_segref_count > 0) || (m.st_facref_count > 0)) { + if (!b->nonodewritten) m.outnodes(out); + if (!b->noelewritten) m.outelements(out); + if (!b->nofacewritten) m.outsubfaces(out); + if (!b->nofacewritten) m.outsubsegments(out); + printf("!! Boundary contains Steiner points (-YY option). Program stopped.\n"); + terminatetetgen(&m, 200); + } + } + } + + tv[5] = clock(); + + if (b->metric || b->coarsen) { // -m or -R + m.meshcoarsening(); + } + + tv[6] = clock(); + + if (!b->quiet) { + if (b->metric || b->coarsen) { + printf("Mesh coarsening seconds: %g\n", ((REAL)(tv[6] - tv[5])) / cps); + } + } + + if (b->plc || (b->refine && b->quality && (in->refine_elem_list == NULL))) { + if (!b->quiet) { + printf("Recovering Delaunayness...\n"); + } + m.recoverdelaunay(); + } + + tv[7] = clock(); + + if (b->plc || (b->refine && b->quality && (in->refine_elem_list == NULL))) { + if (!b->quiet) { + printf("Delaunay recovery seconds: %g\n", ((REAL)(tv[7] - tv[6]))/cps); + } + } + + if ((b->plc || b->refine) && b->insertaddpoints) { // -i + if ((addin != NULL) && (addin->numberofpoints > 0)) { + m.insertconstrainedpoints(addin); + } + } + + tv[8] = clock(); + + if (!b->quiet) { + if ((b->plc || b->refine) && b->insertaddpoints) { // -i + if ((addin != NULL) && (addin->numberofpoints > 0)) { + printf("Constrained points seconds: %g\n", ((REAL)(tv[8]-tv[7]))/cps); + } + } + } + if (b->quality) { // -q + m.delaunayrefinement(); + } + + tv[9] = clock(); + + if (!b->quiet) { + if (b->quality) { + printf("Refinement seconds: %g\n", ((REAL)(tv[9] - tv[8])) / cps); + } + } + + if ((b->plc || b->quality) && + (b->smooth_maxiter > 0) && + ((m.st_volref_count > 0) || (m.st_facref_count > 0))) { + m.smooth_vertices(); // m.optimizemesh(ts[0]); + } + + tv[10] = clock(); + + if (!b->quiet) { + if ((b->plc || b->quality) && + (b->smooth_maxiter > 0) && + ((m.st_volref_count > 0) || (m.st_facref_count > 0))) { + printf("Mesh smoothing seconds: %g\n", ((REAL)(tv[10] - tv[9])) / cps); + } + } + + if (b->plc || b->quality) { + m.improve_mesh(); + } + + tv[11] = clock(); + + if (!b->quiet) { + if (b->plc || b->quality) { + printf("Mesh improvement seconds: %g\n", ((REAL)(tv[11] - tv[10])) / cps); + } + } + + if (!b->nojettison && ((m.dupverts > 0) || (m.unuverts > 0) + || (b->refine && (in->numberofcorners == 10)))) { + m.jettisonnodes(); + } + + + if ((b->order == 2) && !b->convex) { + m.highorder(); + } + + if (!b->quiet) { + printf("\n"); + } + + if (out != (tetgenio *) NULL) { + out->firstnumber = in->firstnumber; + out->mesh_dim = in->mesh_dim; + } + + if (b->nonodewritten || b->noiterationnum) { + if (!b->quiet) { + printf("NOT writing a .node file.\n"); + } + } else { + m.outnodes(out); + } + + if (b->noelewritten) { + if (!b->quiet) { + printf("NOT writing an .ele file.\n"); + } + m.indexelements(); + } else { + if (m.tetrahedrons->items > 0l) { + m.outelements(out); + } + } + + if (b->nofacewritten) { + if (!b->quiet) { + printf("NOT writing an .face file.\n"); + } + } else { + if (b->facesout) { + if (m.tetrahedrons->items > 0l) { + m.outfaces(out); // Output all faces. + } + } else { + if (b->plc || b->refine) { + if (m.subfaces->items > 0l) { + m.outsubfaces(out); // Output boundary faces. + } + } else { + if (m.tetrahedrons->items > 0l) { + m.outhullfaces(out); // Output convex hull faces. + } + } + } + } + + + if (b->nofacewritten) { + if (!b->quiet) { + printf("NOT writing an .edge file.\n"); + } + } else { + if (b->edgesout) { // -e + m.outedges(out); // output all mesh edges. + } else { + if (b->plc || b->refine) { + m.outsubsegments(out); // output subsegments. + } + } + } + + if ((b->plc || b->refine) && b->metric) { // -m + m.outmetrics(out); + } + + if (!out && b->plc && + ((b->object == tetgenbehavior::OFF) || + (b->object == tetgenbehavior::PLY) || + (b->object == tetgenbehavior::STL))) { + m.outsmesh(b->outfilename); + } + + if (!out && b->meditview) { + m.outmesh2medit(b->outfilename); + } + + + if (!out && b->vtkview) { + m.outmesh2vtk(NULL, 0); // b->outfilename + } + + if (!out && b->vtksurfview) { + m.out_surfmesh_vtk(NULL, 0); + } + + if (b->neighout) { + m.outneighbors(out); + } + + if (b->voroout) { + m.outvoronoi(out); + } + + + tv[12] = clock(); + + if (!b->quiet) { + printf("\nOutput seconds: %g\n", ((REAL)(tv[12] - tv[11])) / cps); + printf("Total running seconds: %g\n", ((REAL)(tv[12] - tv[0])) / cps); + } + + if (b->docheck) { + m.check_mesh(0); + if (b->plc || b->refine) { + m.check_shells(); + m.check_segments(); + } + if (b->docheck > 1) { + m.check_delaunay(); + } + } + + if (!b->quiet) { + m.statistics(); + } +} + +#ifndef TETLIBRARY + +//============================================================================// +// // +// main() The command line interface of TetGen. // +// // +//============================================================================// + +//int tetgen_main(int argc, char *argv[]) +int main(int argc, char *argv[]) + +#else // with TETLIBRARY + +//============================================================================// +// // +// tetrahedralize() The library interface of TetGen. // +// // +//============================================================================// + +void tetrahedralize(char *switches, tetgenio *in, tetgenio *out, + tetgenio *addin, tetgenio *bgmin) + +#endif // not TETLIBRARY + +{ + tetgenbehavior b; + +#ifndef TETLIBRARY + + tetgenio in, addin, bgmin; + + if (!b.parse_commandline(argc, argv)) { + terminatetetgen(NULL, 10); + } + + // Read input files. + if (b.refine) { // -r + if (!in.load_tetmesh(b.infilename, (int) b.object)) { + terminatetetgen(NULL, 10); + } + } else { // -p + if (!in.load_plc(b.infilename, (int) b.object)) { + terminatetetgen(NULL, 10); + } + } + if (b.insertaddpoints) { // -i + // Try to read a .a.node file. + addin.load_node(b.addinfilename); + } + if (b.metric) { // -m + // Try to read a background mesh in files .b.node, .b.ele. + bgmin.load_tetmesh(b.bgmeshfilename, (int) b.object); + } + + tetrahedralize(&b, &in, NULL, &addin, &bgmin); + + return 0; + +#else // with TETLIBRARY + + if (!b.parse_commandline(switches)) { + terminatetetgen(NULL, 10); + } + tetrahedralize(&b, in, out, addin, bgmin); + +#endif // not TETLIBRARY +} + +// // +// // +//== main_cxx ================================================================// + diff --git a/SKIRT/tetgen/tetgen.h b/SKIRT/tetgen/tetgen.h new file mode 100644 index 00000000..0e34818a --- /dev/null +++ b/SKIRT/tetgen/tetgen.h @@ -0,0 +1,3613 @@ +//============================================================================// +// // +// TetGen // +// // +// A Quality Tetrahedral Mesh Generator and A 3D Delaunay Triangulator // +// // +// Version 1.6.0 // +// August 31, 2020 // +// // +// Copyright (C) 2002--2020 // +// // +// Hang Si // +// Research Group: Numerical Mathematics and Scientific Computing // +// Weierstrass Institute for Applied Analysis and Stochastics (WIAS) // +// Mohrenstr. 39, 10117 Berlin, Germany // +// si@wias-berlin.de // +// // +// TetGen is a tetrahedral mesh generator. It creates 3d triangulations of // +// polyhedral domains. It generates meshes with well-shaped elements whose // +// sizes are adapted to the geometric features or user-provided sizing // +// functions. It has applications in various applications in scientific // +// computing, such as computer graphics (CG), computer-aided design (CAD), // +// geometry processing (parametrizations and computer animation), and // +// physical simulations (finite element analysis). // +// // +// TetGen computes (weighted) Delaunay triangulations for three-dimensional // +// (weighted) point sets, and constrained Delaunay triangulations for // +// three-dimensional polyhedral domains. In the latter case, input edges // +// and triangles can be completely preserved in the output meshes. TetGen // +// can refine or coarsen an existing mesh to result in good quality and // +// size-adapted mesh according to the geometric features and user-defined // +// mesh sizing functions. // +// // +// TetGen implements theoretically proven algorithms for computing the // +// Delaunay and constrained Delaunay tetrahedralizations. TetGen achieves // +// robustness and efficiency by using advanced techniques in computational // +// geometry. A technical paper describes the algorithms and methods // +// implemented in TetGen is available in ACM-TOMS, Hang Si ``TetGen, a // +// Delaunay-Based Quality Tetrahedral Mesh Generator", ACM Transactions on // +// Mathematical Software, February 2015, https://doi.org/10.1145/2629697. // +// // +// TetGen is freely available through the website: http://www.tetgen.org. // +// It may be copied, modified, and redistributed for non-commercial use. // +// Please consult the file LICENSE for the detailed copyright notices. // +// // +//============================================================================// + + +#ifndef tetgenH +#define tetgenH + +// To compile TetGen as a library instead of an executable program, define +// the TETLIBRARY symbol. + +#define TETLIBRARY + + +// TetGen default uses the double-precision (64 bit) for a real number. +// Alternatively, one can use the single-precision (32 bit) 'float' if the +// memory is limited. + +#define REAL double // #define REAL float + +// The maximum number of characters in a file name (including the null). + +#define FILENAMESIZE 1024 + +// The maximum number of chars in a line read from a file (including the null). + +#define INPUTLINESIZE 2048 + +// C standard libraries to perform Input/output operations, general utililities, +// manipulate strings and arrays, compute common mathematical operations, +// get date and time information. + +#include +#include +#include +#include +#include + +// The types 'intptr_t' and 'uintptr_t' are signed and unsigned integer types, +// respectively. They are guaranteed to be the same width as a pointer. +// They are defined in by the C99 Standard. + +#include + +//============================================================================// +// // +// tetgenio // +// // +// A structure for transferring input/output data between the user and // +// TetGen's internal data structure (class tetgenmesh). // +// // +// This data structure contains a collection of arrays, i.e., points, facets, // +// tetrahedra. It contains functions to read input data from files (.node, // +// .poly, .face, .edge, .ele) as well as write output data into files. // +// // +// Once an object of tetgenio is declared, no array is created. One has to // +// allocate enough memory for them. On the deletion of this object, the // +// memory occupied by these arrays needs to be freed. The routine // +// deinitialize() will be automatically called. It frees the memory for // +// an array if it is not a NULL. Note that it assumes that the memory is // +// allocated by the C++ "new" operator. Otherwise, the user is responsible // +// to free them and all pointers must be NULL. // +// // +//============================================================================// + +class tetgenio { + +public: + + // A "polygon" describes a simple polygon (no holes). It is not necessarily + // convex. Each polygon contains a number of corners (points) and the same + // number of sides (edges). The points of the polygon must be given in + // either counterclockwise or clockwise order and they form a ring, so + // every two consecutive points forms an edge of the polygon. + typedef struct { + int *vertexlist; + int numberofvertices; + } polygon; + + // A "facet" describes a polygonal region possibly with holes, edges, and + // points floating in it. Each facet consists of a list of polygons and + // a list of hole points (which lie strictly inside holes). + typedef struct { + polygon *polygonlist; + int numberofpolygons; + REAL *holelist; + int numberofholes; + } facet; + + // A "voroedge" is an edge of the Voronoi diagram. It corresponds to a + // Delaunay face. Each voroedge is either a line segment connecting + // two Voronoi vertices or a ray starting from a Voronoi vertex to an + // "infinite vertex". 'v1' and 'v2' are two indices pointing to the + // list of Voronoi vertices. 'v1' must be non-negative, while 'v2' may + // be -1 if it is a ray, in this case, the unit normal of this ray is + // given in 'vnormal'. + typedef struct { + int v1, v2; + REAL vnormal[3]; + } voroedge; + + // A "vorofacet" is an facet of the Voronoi diagram. It corresponds to a + // Delaunay edge. Each Voronoi facet is a convex polygon formed by a + // list of Voronoi edges, it may not be closed. 'c1' and 'c2' are two + // indices pointing into the list of Voronoi cells, i.e., the two cells + // share this facet. 'elist' is an array of indices pointing into the + // list of Voronoi edges, 'elist[0]' saves the number of Voronoi edges + // (including rays) of this facet. + typedef struct { + int c1, c2; + int *elist; + } vorofacet; + + + // Additional parameters associated with an input (or mesh) vertex. + // These informations are provided by CAD libraries. + typedef struct { + REAL uv[2]; + int tag; + int type; // 0, 1, or 2. + } pointparam; + + // Callback functions for meshing PSCs. + typedef REAL (* GetVertexParamOnEdge)(void*, int, int); + typedef void (* GetSteinerOnEdge)(void*, int, REAL, REAL*); + typedef void (* GetVertexParamOnFace)(void*, int, int, REAL*); + typedef void (* GetEdgeSteinerParamOnFace)(void*, int, REAL, int, REAL*); + typedef void (* GetSteinerOnFace)(void*, int, REAL*, REAL*); + + // A callback function for mesh refinement. + typedef bool (* TetSizeFunc)(REAL*, REAL*, REAL*, REAL*, REAL*, REAL); + + // Items are numbered starting from 'firstnumber' (0 or 1), default is 0. + int firstnumber; + + // Dimension of the mesh (2 or 3), default is 3. + int mesh_dim; + + // Does the lines in .node file contain index or not, default is 1. + int useindex; + + // 'pointlist': An array of point coordinates. The first point's x + // coordinate is at index [0] and its y coordinate at index [1], its + // z coordinate is at index [2], followed by the coordinates of the + // remaining points. Each point occupies three REALs. + // 'pointattributelist': An array of point attributes. Each point's + // attributes occupy 'numberofpointattributes' REALs. + // 'pointmtrlist': An array of metric tensors at points. Each point's + // tensor occupies 'numberofpointmtr' REALs. + // 'pointmarkerlist': An array of point markers; one integer per point. + // 'point2tetlist': An array of tetrahedra indices; one integer per point. + REAL *pointlist; + REAL *pointattributelist; + REAL *pointmtrlist; + int *pointmarkerlist; + int *point2tetlist; + pointparam *pointparamlist; + int numberofpoints; + int numberofpointattributes; + int numberofpointmtrs; + + // 'tetrahedronlist': An array of tetrahedron corners. The first + // tetrahedron's first corner is at index [0], followed by its other + // corners, followed by six nodes on the edges of the tetrahedron if the + // second order option (-o2) is applied. Each tetrahedron occupies + // 'numberofcorners' ints. The second order nodes are ouput only. + // 'tetrahedronattributelist': An array of tetrahedron attributes. Each + // tetrahedron's attributes occupy 'numberoftetrahedronattributes' REALs. + // 'tetrahedronvolumelist': An array of constraints, i.e. tetrahedron's + // volume; one REAL per element. Input only. + // 'neighborlist': An array of tetrahedron neighbors; 4 ints per element. + // 'tet2facelist': An array of tetrahedron face indices; 4 ints per element. + // 'tet2edgelist': An array of tetrahedron edge indices; 6 ints per element. + int *tetrahedronlist; + REAL *tetrahedronattributelist; + REAL *tetrahedronvolumelist; + int *neighborlist; + int *tet2facelist; + int *tet2edgelist; + int numberoftetrahedra; + int numberofcorners; + int numberoftetrahedronattributes; + + // 'facetlist': An array of facets. Each entry is a structure of facet. + // 'facetmarkerlist': An array of facet markers; one int per facet. + facet *facetlist; + int *facetmarkerlist; + int numberoffacets; + + // 'holelist': An array of holes (in volume). Each hole is given by a + // seed (point) which lies strictly inside it. The first seed's x, y and z + // coordinates are at indices [0], [1] and [2], followed by the + // remaining seeds. Three REALs per hole. + REAL *holelist; + int numberofholes; + + // 'regionlist': An array of regions (subdomains). Each region is given by + // a seed (point) which lies strictly inside it. The first seed's x, y and + // z coordinates are at indices [0], [1] and [2], followed by the regional + // attribute at index [3], followed by the maximum volume at index [4]. + // Five REALs per region. + // Note that each regional attribute is used only if you select the 'A' + // switch, and each volume constraint is used only if you select the + // 'a' switch (with no number following). + REAL *regionlist; + int numberofregions; + + // 'refine_elem_list': An array of tetrahedra to be refined. The first + // tetrahedron's first corner is at index [0], followed by its other + // corners. Four integers per element. + // 'refine_elem_vol_list': An array of constraints, i.e. tetrahedron's + // volume; one REAL per element. + int *refine_elem_list; + REAL *refine_elem_vol_list; + int numberofrefineelems; + + // 'facetconstraintlist': An array of facet constraints. Each constraint + // specifies a maximum area bound on the subfaces of that facet. The + // first facet constraint is given by a facet marker at index [0] and its + // maximum area bound at index [1], followed by the remaining facet con- + // straints. Two REALs per facet constraint. Note: the facet marker is + // actually an integer. + REAL *facetconstraintlist; + int numberoffacetconstraints; + + // 'segmentconstraintlist': An array of segment constraints. Each constraint + // specifies a maximum length bound on the subsegments of that segment. + // The first constraint is given by the two endpoints of the segment at + // index [0] and [1], and the maximum length bound at index [2], followed + // by the remaining segment constraints. Three REALs per constraint. + // Note the segment endpoints are actually integers. + REAL *segmentconstraintlist; + int numberofsegmentconstraints; + + + // 'trifacelist': An array of face (triangle) corners. The first face's + // three corners are at indices [0], [1] and [2], followed by the remaining + // faces. Three ints per face. + // 'trifacemarkerlist': An array of face markers; one int per face. + // 'o2facelist': An array of second order nodes (on the edges) of the face. + // It is output only if the second order option (-o2) is applied. The + // first face's three second order nodes are at [0], [1], and [2], + // followed by the remaining faces. Three ints per face. + // 'face2tetlist': An array of tetrahedra indices; 2 ints per face. + // 'face2edgelist': An array of edge indices; 3 ints per face. + int *trifacelist; + int *trifacemarkerlist; + int *o2facelist; + int *face2tetlist; + int *face2edgelist; + int numberoftrifaces; + + // 'edgelist': An array of edge endpoints. The first edge's endpoints + // are at indices [0] and [1], followed by the remaining edges. + // Two ints per edge. + // 'edgemarkerlist': An array of edge markers; one int per edge. + // 'o2edgelist': An array of midpoints of edges. It is output only if the + // second order option (-o2) is applied. One int per edge. + // 'edge2tetlist': An array of tetrahedra indices. One int per edge. + int *edgelist; + int *edgemarkerlist; + int *o2edgelist; + int *edge2tetlist; + int numberofedges; + + // 'vpointlist': An array of Voronoi vertex coordinates (like pointlist). + // 'vedgelist': An array of Voronoi edges. Each entry is a 'voroedge'. + // 'vfacetlist': An array of Voronoi facets. Each entry is a 'vorofacet'. + // 'vcelllist': An array of Voronoi cells. Each entry is an array of + // indices pointing into 'vfacetlist'. The 0th entry is used to store + // the length of this array. + REAL *vpointlist; + voroedge *vedgelist; + vorofacet *vfacetlist; + int **vcelllist; + int numberofvpoints; + int numberofvedges; + int numberofvfacets; + int numberofvcells; + + + // Variable (and callback functions) for meshing PSCs. + void *geomhandle; + GetVertexParamOnEdge getvertexparamonedge; + GetSteinerOnEdge getsteineronedge; + GetVertexParamOnFace getvertexparamonface; + GetEdgeSteinerParamOnFace getedgesteinerparamonface; + GetSteinerOnFace getsteineronface; + + // A callback function. + TetSizeFunc tetunsuitable; + + // Input & output routines. + bool load_node_call(FILE* infile, int markers, int uvflag, char*); + bool load_node(char*); + bool load_edge(char*); + bool load_face(char*); + bool load_tet(char*); + bool load_vol(char*); + bool load_var(char*); + bool load_mtr(char*); + bool load_elem(char*); + bool load_poly(char*); + bool load_off(char*); + bool load_ply(char*); + bool load_stl(char*); + bool load_vtk(char*); + bool load_medit(char*, int); + bool load_neumesh(char*, int); + bool load_plc(char*, int); + bool load_tetmesh(char*, int); + void save_nodes(const char*); + void save_elements(const char*); + void save_faces(const char*); + void save_edges(char*); + void save_neighbors(char*); + void save_poly(const char*); + void save_faces2smesh(char*); + + // Read line and parse string functions. + char *readline(char* string, FILE* infile, int *linenumber); + char *findnextfield(char* string); + char *readnumberline(char* string, FILE* infile, char* infilename); + char *findnextnumber(char* string); + + static void init(polygon* p) { + p->vertexlist = (int *) NULL; + p->numberofvertices = 0; + } + + static void init(facet* f) { + f->polygonlist = (polygon *) NULL; + f->numberofpolygons = 0; + f->holelist = (REAL *) NULL; + f->numberofholes = 0; + } + + // Initialize routine. + void initialize() + { + firstnumber = 0; + mesh_dim = 3; + useindex = 1; + + pointlist = (REAL *) NULL; + pointattributelist = (REAL *) NULL; + pointmtrlist = (REAL *) NULL; + pointmarkerlist = (int *) NULL; + point2tetlist = (int *) NULL; + pointparamlist = (pointparam *) NULL; + numberofpoints = 0; + numberofpointattributes = 0; + numberofpointmtrs = 0; + + tetrahedronlist = (int *) NULL; + tetrahedronattributelist = (REAL *) NULL; + tetrahedronvolumelist = (REAL *) NULL; + neighborlist = (int *) NULL; + tet2facelist = (int *) NULL; + tet2edgelist = (int *) NULL; + numberoftetrahedra = 0; + numberofcorners = 4; + numberoftetrahedronattributes = 0; + + trifacelist = (int *) NULL; + trifacemarkerlist = (int *) NULL; + o2facelist = (int *) NULL; + face2tetlist = (int *) NULL; + face2edgelist = (int *) NULL; + numberoftrifaces = 0; + + edgelist = (int *) NULL; + edgemarkerlist = (int *) NULL; + o2edgelist = (int *) NULL; + edge2tetlist = (int *) NULL; + numberofedges = 0; + + facetlist = (facet *) NULL; + facetmarkerlist = (int *) NULL; + numberoffacets = 0; + + holelist = (REAL *) NULL; + numberofholes = 0; + + regionlist = (REAL *) NULL; + numberofregions = 0; + + refine_elem_list = (int *) NULL; + refine_elem_vol_list = (REAL *) NULL; + numberofrefineelems = 0; + + facetconstraintlist = (REAL *) NULL; + numberoffacetconstraints = 0; + segmentconstraintlist = (REAL *) NULL; + numberofsegmentconstraints = 0; + + + vpointlist = (REAL *) NULL; + vedgelist = (voroedge *) NULL; + vfacetlist = (vorofacet *) NULL; + vcelllist = (int **) NULL; + numberofvpoints = 0; + numberofvedges = 0; + numberofvfacets = 0; + numberofvcells = 0; + + + tetunsuitable = NULL; + + geomhandle = NULL; + getvertexparamonedge = NULL; + getsteineronedge = NULL; + getvertexparamonface = NULL; + getedgesteinerparamonface = NULL; + getsteineronface = NULL; + } + + // Free the memory allocated in 'tetgenio'. Note that it assumes that the + // memory was allocated by the "new" operator (C++). + void clean_memory() + { + int i, j; + + if (pointlist != (REAL *) NULL) { + delete [] pointlist; + } + if (pointattributelist != (REAL *) NULL) { + delete [] pointattributelist; + } + if (pointmtrlist != (REAL *) NULL) { + delete [] pointmtrlist; + } + if (pointmarkerlist != (int *) NULL) { + delete [] pointmarkerlist; + } + if (point2tetlist != (int *) NULL) { + delete [] point2tetlist; + } + if (pointparamlist != (pointparam *) NULL) { + delete [] pointparamlist; + } + + if (tetrahedronlist != (int *) NULL) { + delete [] tetrahedronlist; + } + if (tetrahedronattributelist != (REAL *) NULL) { + delete [] tetrahedronattributelist; + } + if (tetrahedronvolumelist != (REAL *) NULL) { + delete [] tetrahedronvolumelist; + } + if (neighborlist != (int *) NULL) { + delete [] neighborlist; + } + if (tet2facelist != (int *) NULL) { + delete [] tet2facelist; + } + if (tet2edgelist != (int *) NULL) { + delete [] tet2edgelist; + } + + if (trifacelist != (int *) NULL) { + delete [] trifacelist; + } + if (trifacemarkerlist != (int *) NULL) { + delete [] trifacemarkerlist; + } + if (o2facelist != (int *) NULL) { + delete [] o2facelist; + } + if (face2tetlist != (int *) NULL) { + delete [] face2tetlist; + } + if (face2edgelist != (int *) NULL) { + delete [] face2edgelist; + } + + if (edgelist != (int *) NULL) { + delete [] edgelist; + } + if (edgemarkerlist != (int *) NULL) { + delete [] edgemarkerlist; + } + if (o2edgelist != (int *) NULL) { + delete [] o2edgelist; + } + if (edge2tetlist != (int *) NULL) { + delete [] edge2tetlist; + } + + if (facetlist != (facet *) NULL) { + facet *f; + polygon *p; + for (i = 0; i < numberoffacets; i++) { + f = &facetlist[i]; + for (j = 0; j < f->numberofpolygons; j++) { + p = &f->polygonlist[j]; + delete [] p->vertexlist; + } + delete [] f->polygonlist; + if (f->holelist != (REAL *) NULL) { + delete [] f->holelist; + } + } + delete [] facetlist; + } + if (facetmarkerlist != (int *) NULL) { + delete [] facetmarkerlist; + } + + if (holelist != (REAL *) NULL) { + delete [] holelist; + } + if (regionlist != (REAL *) NULL) { + delete [] regionlist; + } + if (refine_elem_list != (int *) NULL) { + delete [] refine_elem_list; + if (refine_elem_vol_list != (REAL *) NULL) { + delete [] refine_elem_vol_list; + } + } + if (facetconstraintlist != (REAL *) NULL) { + delete [] facetconstraintlist; + } + if (segmentconstraintlist != (REAL *) NULL) { + delete [] segmentconstraintlist; + } + if (vpointlist != (REAL *) NULL) { + delete [] vpointlist; + } + if (vedgelist != (voroedge *) NULL) { + delete [] vedgelist; + } + if (vfacetlist != (vorofacet *) NULL) { + for (i = 0; i < numberofvfacets; i++) { + delete [] vfacetlist[i].elist; + } + delete [] vfacetlist; + } + if (vcelllist != (int **) NULL) { + for (i = 0; i < numberofvcells; i++) { + delete [] vcelllist[i]; + } + delete [] vcelllist; + } + } + + // Constructor & destructor. + tetgenio() {initialize();} + ~tetgenio() {clean_memory();} + +}; // class tetgenio + +//============================================================================// +// // +// tetgenbehavior // +// // +// A structure for maintaining the switches and parameters used by TetGen's // +// internal data structure and algorithms. // +// // +// All switches and parameters are initialized with default values. They are // +// set by the command line arguments (argc, argv). // +// // +// NOTE: Some switches are incompatible with others. While some may depend // +// on other switches. The routine parse_commandline() sets the switches from // +// the command line (a list of strings) and checks the consistency of the // +// applied switches. // +// // +//============================================================================// + +class tetgenbehavior { + +public: + + // Switches of TetGen. + int plc; // '-p', 0. + int psc; // '-s', 0. + int refine; // '-r', 0. + int quality; // '-q', 0. + int nobisect; // '-Y', 0. + int cdt; // '-D', 0. + int cdtrefine; // '-D#', 7. + int coarsen; // '-R', 0. + int weighted; // '-w', 0. + int brio_hilbert; // '-b', 1. + int flipinsert; // '-L', 0. + int metric; // '-m', 0. + int varvolume; // '-a', 0. + int fixedvolume; // '-a', 0. + int regionattrib; // '-A', 0. + int insertaddpoints; // '-i', 0. + int diagnose; // '-d', 0. + int convex; // '-c', 0. + int nomergefacet; // '-M', 0. + int nomergevertex; // '-M', 0. + int noexact; // '-X', 0. + int nostaticfilter; // '-X', 0. + int zeroindex; // '-z', 0. + int facesout; // '-f', 0. + int edgesout; // '-e', 0. + int neighout; // '-n', 0. + int voroout; // '-v', 0. + int meditview; // '-g', 0. + int vtkview; // '-k', 0. + int vtksurfview; // '-k', 0. + int nobound; // '-B', 0. + int nonodewritten; // '-N', 0. + int noelewritten; // '-E', 0. + int nofacewritten; // '-F', 0. + int noiterationnum; // '-I', 0. + int nojettison; // '-J', 0. + int docheck; // '-C', 0. + int quiet; // '-Q', 0. + int nowarning; // '-W', 0. + int verbose; // '-V', 0. + + // Parameters of TetGen. + int vertexperblock; // '-x', 4092. + int tetrahedraperblock; // '-x', 8188. + int shellfaceperblock; // '-x', 2044. + int supsteiner_level; // '-Y/', 2. + int addsteiner_algo; // '-Y//', 1. + int coarsen_param; // '-R', 0. + int weighted_param; // '-w', 0. + int fliplinklevel; // -1. + int flipstarsize; // -1. + int fliplinklevelinc; // 1. + int opt_max_flip_level; // '-O', 3. + int opt_scheme; // '-O/#', 7. + int opt_iterations; // -O//#, 3. + int smooth_cirterion; // -s, 1. + int smooth_maxiter; // -s, 7. + int delmaxfliplevel; // 1. + int order; // '-o', 1. + int reversetetori; // '-o/', 0. + int steinerleft; // '-S', 0. + int unflip_queue_limit; // '-U#', 1000. + int no_sort; // 0. + int hilbert_order; // '-b///', 52. + int hilbert_limit; // '-b//' 8. + int brio_threshold; // '-b' 64. + REAL brio_ratio; // '-b/' 0.125. + REAL epsilon; // '-T', 1.0e-8. + REAL facet_separate_ang_tol; // '-p', 179.9. + REAL collinear_ang_tol; // '-p/', 179.9. + REAL facet_small_ang_tol; // '-p//', 15.0. + REAL maxvolume; // '-a', -1.0. + REAL maxvolume_length; // '-a', -1.0. + REAL minratio; // '-q', 0.0. + REAL opt_max_asp_ratio; // 1000.0. + REAL opt_max_edge_ratio; // 100.0. + REAL mindihedral; // '-q', 5.0. + REAL optmaxdihedral; // -o/# 177.0. + REAL metric_scale; // -m#, 1.0. + REAL smooth_alpha; // '-s', 0.3. + REAL coarsen_percent; // -R1/#, 1.0. + REAL elem_growth_ratio; // Growth ratio of # elements, -r#, 0.0. + REAL refine_progress_ratio; // -r/#, 0.333. + + // Strings of command line arguments and input/output file names. + char commandline[1024]; + char infilename[1024]; + char outfilename[1024]; + char addinfilename[1024]; + char bgmeshfilename[1024]; + + // Read an additional tetrahedral mesh and treat it as holes [2018-07-30]. + int hole_mesh; // '-H', 0. + char hole_mesh_filename[1024]; + + // The input object of TetGen. They are recognized by either the input + // file extensions or by the specified options. + // Currently the following objects are supported: + // - NODES, a list of nodes (.node); + // - POLY, a piecewise linear complex (.poly or .smesh); + // - OFF, a polyhedron (.off, Geomview's file format); + // - PLY, a polyhedron (.ply, file format from gatech, only ASCII); + // - STL, a surface mesh (.stl, stereolithography format); + // - MEDIT, a surface mesh (.mesh, Medit's file format); + // - MESH, a tetrahedral mesh (.ele). + // If no extension is available, the imposed command line switch + // (-p or -r) implies the object. + enum objecttype {NODES, POLY, OFF, PLY, STL, MEDIT, VTK, MESH, NEU_MESH} object; + + + void syntax(); + void usage(); + + // Command line parse routine. + bool parse_commandline(int argc, char **argv); + bool parse_commandline(char *switches) { + return parse_commandline(0, &switches); + } + + // Initialize all variables. + tetgenbehavior() + { + plc = 0; + psc = 0; + refine = 0; + quality = 0; + nobisect = 0; + cdt = 0; // set by -D (without a number following it) + cdtrefine = 7; // default, set by -D# + coarsen = 0; + metric = 0; + weighted = 0; + brio_hilbert = 1; + flipinsert = 0; + varvolume = 0; + fixedvolume = 0; + noexact = 0; + nostaticfilter = 0; + insertaddpoints = 0; + regionattrib = 0; + diagnose = 0; + convex = 0; + zeroindex = 0; + facesout = 0; + edgesout = 0; + neighout = 0; + voroout = 0; + meditview = 0; + vtkview = 0; + vtksurfview = 0; + nobound = 0; + nonodewritten = 0; + noelewritten = 0; + nofacewritten = 0; + noiterationnum = 0; + nomergefacet = 0; + nomergevertex = 0; + nojettison = 0; + docheck = 0; + quiet = 0; + nowarning = 0; + verbose = 0; + + vertexperblock = 4092; + tetrahedraperblock = 8188; + shellfaceperblock = 4092; + supsteiner_level = 2; + addsteiner_algo = 1; + coarsen_param = 0; + weighted_param = 0; + fliplinklevel = -1; + flipstarsize = -1; + fliplinklevelinc = 1; + opt_scheme = 7; + opt_max_flip_level = 3; + opt_iterations = 3; + delmaxfliplevel = 1; + order = 1; + reversetetori = 0; + steinerleft = -1; + unflip_queue_limit = 1000; + no_sort = 0; + hilbert_order = 52; //-1; + hilbert_limit = 8; + brio_threshold = 64; + brio_ratio = 0.125; + facet_separate_ang_tol = 179.9; + collinear_ang_tol = 179.9; + facet_small_ang_tol = 15.0; + maxvolume = -1.0; + maxvolume_length = -1.0; + minratio = 2.0; + opt_max_asp_ratio = 1000.; + opt_max_edge_ratio = 100.; + mindihedral = 3.5; + optmaxdihedral = 177.00; + epsilon = 1.0e-8; + coarsen_percent = 1.0; + metric_scale = 1.0; // -m# + elem_growth_ratio = 0.0; // -r# + refine_progress_ratio = 0.333; // -r/# + object = NODES; + + smooth_cirterion = 3; // -s# default smooth surface and volume vertices. + smooth_maxiter = 7; // set by -s#/7 + smooth_alpha = 0.3; // relax parameter, set by -s#/#/0.3 + + commandline[0] = '\0'; + infilename[0] = '\0'; + outfilename[0] = '\0'; + addinfilename[0] = '\0'; + bgmeshfilename[0] = '\0'; + + hole_mesh = 0; + hole_mesh_filename[0] = '\0'; + + } + +}; // class tetgenbehavior + +//============================================================================// +// // +// Robust Geometric predicates // +// // +// The following routines are the robust geometric predicates for orientation // +// test and point-in-sphere test implemented by Jonathan Shewchuk. // +// He generously provided the source code in the public domain, // +// http://www.cs.cmu.edu/~quake/robust.html. // +// predicates.cxx is a C++ version of the original C code. // +// // +// The original predicates of Shewchuk only use "dynamic filters", i.e., it // +// computes the error at runtime step by step. TetGen first uses a "static // +// filter" in each predicate. It estimates the maximal possible error in all // +// cases. It safely and quickly "filters" many easy cases. // +// // +//============================================================================// + +void exactinit(int, int, int, REAL, REAL, REAL); + +REAL orient3d(REAL *pa, REAL *pb, REAL *pc, REAL *pd); +REAL insphere(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL *pe); +REAL orient4d(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL *pe, + REAL ah, REAL bh, REAL ch, REAL dh, REAL eh); + +REAL orient2dexact(REAL *pa, REAL *pb, REAL *pc); +REAL orient3dexact(REAL *pa, REAL *pb, REAL *pc, REAL *pd); +REAL orient4dexact(REAL* pa, REAL* pb, REAL* pc, REAL* pd, REAL* pe, + REAL ah, REAL bh, REAL ch, REAL dh, REAL eh); + + +//============================================================================// +// // +// tetgenmesh TetGen's internal mesh data structure. // +// // +// It uses a tetrahedron-based mesh data structure. It implements elementary // +// flip operations to locally modify the mesh. It implements basic meshing // +// algorithms to create Delaunay tetrahedraliations, to perform boundary // +// recovery, to place Steiner points in the mesh domain, and to optimize the // +// quality of the mesh. // +// // +//============================================================================// + +class tetgenmesh { + +public: + +//============================================================================// +// // +// Mesh data structure // +// // +// A tetrahedral mesh T of a 3D piecewise linear complex (PLC) X is a 3D // +// simplicial complex whose underlying space is equal to the space of X. T // +// contains a 2D subcomplex S which is a triangular mesh of the boundary of // +// X. S contains a 1D subcomplex L which is a linear mesh of the boundary of // +// S. Faces and edges in S and L are respectively called subfaces and segme- // +// nts to distinguish them from others in T. // +// // +// TetGen uses a tetrahedron-based data structure. It stores tetrahedra and // +// vertices. This data structure is pointer-based. Each tetrahedron contains // +// pointers to its vertices and adjacent tetrahedra. Each vertex holds its x-,// +// y-, z-coordinates, and a pointer to one of the tetrahedra having it. Both // +// tetrahedra and vertices may contain user data. // +// // +// Let T be a tetrahedralization. Each triangular face of T belongs to either // +// two or one tetrahedron. In the latter case, it is an exterior boundary // +// face of T. TetGen attaches tetrahedra (one-to-one) to such faces. All such // +// tetrahedra contain an "infinite vertex" (which has no geometric coordinates// +// ). One can imagine such a vertex lies in 4D space and is visible by all // +// exterior boundary faces simultaneously. This extended set of tetrahedra // +// (including the infinite vertex) becomes a tetrahedralization of a 3-sphere // +// that has no boundary in 3d. It has the nice property that every triangular // +// face is shared by exactly two tetrahedra. // +// // +// The current version of TetGen stores explicitly the subfaces and segments // +// (which are in surface mesh S and the linear mesh L), respectively. Extra // +// pointers are allocated in tetrahedra and subfaces to point each other. // +// // +//============================================================================// + + // The tetrahedron data structure. It includes the following fields: + // - a list of four adjoining tetrahedra; + // - a list of four vertices; + // - a pointer to a list of four subfaces (optional, for -p switch); + // - a pointer to a list of six segments (optional, for -p switch); + // - a list of user-defined floating-point attributes (optional); + // - a volume constraint (optional, for -a switch); + // - an integer of element marker (and flags); + // The structure of a tetrahedron is an array of pointers. Its actual size + // (the length of the array) is determined at runtime. + + typedef REAL **tetrahedron; + + // The subface data structure. It includes the following fields: + // - a list of three adjoining subfaces; + // - a list of three vertices; + // - a list of three adjoining segments; + // - two adjoining tetrahedra; + // - an area constraint (optional, for -q switch); + // - an integer for boundary marker; + // - an integer for type, flags, etc. + + typedef REAL **shellface; + + // The point data structure. It includes the following fields: + // - x, y and z coordinates; + // - a list of user-defined point attributes (optional); + // - u, v coordinates (optional, for -s switch); + // - a metric tensor (optional, for -q or -m switch); + // - a pointer to an adjacent tetrahedron; + // - a pointer to a parent (or a duplicate) point; + // - a pointer to an adjacent subface or segment (optional, -p switch); + // - a pointer to a tet in background mesh (optional, for -m switch); + // - an integer for boundary marker (point index); + // - an integer for point type (and flags). + // - an integer for geometry tag (optional, for -s switch). + // The structure of a point is an array of REALs. Its acutal size is + // determined at the runtime. + + typedef REAL *point; + +//============================================================================// +// // +// Handles // +// // +// Navigation and manipulation in a tetrahedralization are accomplished by // +// operating on structures referred as ``handles". A handle is a pair (t,v), // +// where t is a pointer to a tetrahedron, and v is a 4-bit integer, in the // +// range from 0 to 11. v is called the ``version'' of a tetrahedron, it rep- // +// resents a directed edge of a specific face of the tetrahedron. // +// // +// There are 12 even permutations of the four vertices, each of them corres- // +// ponds to a directed edge (a version) of the tetrahedron. The 12 versions // +// can be grouped into 4 distinct ``edge rings'' in 4 ``oriented faces'' of // +// this tetrahedron. One can encode each version (a directed edge) into a // +// 4-bit integer such that the two upper bits encode the index (from 0 to 2) // +// of this edge in the edge ring, and the two lower bits encode the index ( // +// from 0 to 3) of the oriented face which contains this edge. // +// // +// The four vertices of a tetrahedron are indexed from 0 to 3 (according to // +// their storage in the data structure). Give each face the same index as // +// the node opposite it in the tetrahedron. Denote the edge connecting face // +// i to face j as i/j. We number the twelve versions as follows: // +// // +// | edge 0 edge 1 edge 2 // +// --------|-------------------------------- // +// face 0 | 0 (0/1) 4 (0/3) 8 (0/2) // +// face 1 | 1 (1/2) 5 (1/3) 9 (1/0) // +// face 2 | 2 (2/3) 6 (2/1) 10 (2/0) // +// face 3 | 3 (3/0) 7 (3/1) 11 (3/2) // +// // +// Similarly, navigation and manipulation in a (boundary) triangulation are // +// done by using handles of triangles. Each handle is a pair (s, v), where s // +// is a pointer to a triangle, and v is a version in the range from 0 to 5. // +// Each version corresponds to a directed edge of this triangle. // +// // +// Number the three vertices of a triangle from 0 to 2 (according to their // +// storage in the data structure). Give each edge the same index as the node // +// opposite it in the triangle. The six versions of a triangle are: // +// // +// | edge 0 edge 1 edge 2 // +// ---------------|-------------------------- // +// ccw orieation | 0 2 4 // +// cw orieation | 1 3 5 // +// // +// In the following, a 'triface' is a handle of tetrahedron, and a 'face' is // +// a handle of a triangle. // +// // +//============================================================================// + + class triface { + public: + tetrahedron *tet; + int ver; // Range from 0 to 11. + triface() : tet(0), ver(0) {} + triface& operator=(const triface& t) { + tet = t.tet; ver = t.ver; + return *this; + } + }; + + class face { + public: + shellface *sh; + int shver; // Range from 0 to 5. + face() : sh(0), shver(0) {} + face& operator=(const face& s) { + sh = s.sh; shver = s.shver; + return *this; + } + }; + +//============================================================================// +// // +// Arraypool // +// // +// A dynamic linear array. (It is written by J. Shewchuk) // +// // +// Each arraypool contains an array of pointers to a number of blocks. Each // +// block contains the same fixed number of objects. Each index of the array // +// addresses a particular object in the pool. The most significant bits add- // +// ress the index of the block containing the object. The less significant // +// bits address this object within the block. // +// // +// 'objectbytes' is the size of one object in blocks; 'log2objectsperblock' // +// is the base-2 logarithm of 'objectsperblock'; 'objects' counts the number // +// of allocated objects; 'totalmemory' is the total memory in bytes. // +// // +//============================================================================// + + class arraypool { + + public: + + int objectbytes; + int objectsperblock; + int log2objectsperblock; + int objectsperblockmark; + int toparraylen; + char **toparray; + long objects; + unsigned long totalmemory; + + void restart(); + void poolinit(int sizeofobject, int log2objperblk); + char* getblock(int objectindex); + void* lookup(int objectindex); + int newindex(void **newptr); + + arraypool(int sizeofobject, int log2objperblk); + ~arraypool(); + }; + +// fastlookup() -- A fast, unsafe operation. Return the pointer to the object +// with a given index. Note: The object's block must have been allocated, +// i.e., by the function newindex(). + +#define fastlookup(pool, index) \ + (void *) ((pool)->toparray[(index) >> (pool)->log2objectsperblock] + \ + ((index) & (pool)->objectsperblockmark) * (pool)->objectbytes) + +//============================================================================// +// // +// Memorypool // +// // +// A structure for memory allocation. (It is written by J. Shewchuk) // +// // +// firstblock is the first block of items. nowblock is the block from which // +// items are currently being allocated. nextitem points to the next slab // +// of free memory for an item. deaditemstack is the head of a linked list // +// (stack) of deallocated items that can be recycled. unallocateditems is // +// the number of items that remain to be allocated from nowblock. // +// // +// Traversal is the process of walking through the entire list of items, and // +// is separate from allocation. Note that a traversal will visit items on // +// the "deaditemstack" stack as well as live items. pathblock points to // +// the block currently being traversed. pathitem points to the next item // +// to be traversed. pathitemsleft is the number of items that remain to // +// be traversed in pathblock. // +// // +//============================================================================// + + class memorypool { + + public: + + void **firstblock, **nowblock; + void *nextitem; + void *deaditemstack; + void **pathblock; + void *pathitem; + int alignbytes; + int itembytes, itemwords; + int itemsperblock; + long items, maxitems; + int unallocateditems; + int pathitemsleft; + + memorypool(); + memorypool(int, int, int, int); + ~memorypool(); + + void poolinit(int, int, int, int); + void restart(); + void *alloc(); + void dealloc(void*); + void traversalinit(); + void *traverse(); + }; + +//============================================================================// +// // +// badface // +// // +// Despite of its name, a 'badface' can be used to represent one of the // +// following objects: // +// - a face of a tetrahedron which is (possibly) non-Delaunay; // +// - an encroached subsegment or subface; // +// - a bad-quality tetrahedron, i.e, has too large radius-edge ratio; // +// - a sliver, i.e., has good radius-edge ratio but nearly zero volume; // +// - a recently flipped face (saved for undoing the flip later). // +// // +//============================================================================// + + class badface { + public: + triface tt; + face ss; + REAL key, cent[6]; // circumcenter or cos(dihedral angles) at 6 edges. + point forg, fdest, fapex, foppo, noppo; + badface *nextitem; + badface() : key(0), forg(0), fdest(0), fapex(0), foppo(0), noppo(0), + nextitem(0) {} + void init() { + key = 0.; + for (int k = 0; k < 6; k++) cent[k] = 0.; + tt.tet = NULL; tt.ver = 0; + ss.sh = NULL; ss.shver = 0; + forg = fdest = fapex = foppo = noppo = NULL; + nextitem = NULL; + } + }; + +//============================================================================// +// // +// insertvertexflags // +// // +// A collection of flags that pass to the routine insertvertex(). // +// // +//============================================================================// + + class insertvertexflags { + + public: + + int iloc; // input/output. + int bowywat, lawson; + int splitbdflag, validflag, respectbdflag; + int rejflag, chkencflag, cdtflag; + int assignmeshsize; + int sloc, sbowywat; + + // Used by Delaunay refinement. + int collect_inial_cavity_flag; + int ignore_near_vertex; + int check_insert_radius; + int refineflag; // 0, 1, 2, 3 + triface refinetet; + face refinesh; + int smlenflag; // for useinsertradius. + REAL smlen; // for useinsertradius. + point parentpt; + + void init() { + iloc = bowywat = lawson = 0; + splitbdflag = validflag = respectbdflag = 0; + rejflag = chkencflag = cdtflag = 0; + assignmeshsize = 0; + sloc = sbowywat = 0; + + collect_inial_cavity_flag = 0; + ignore_near_vertex = 0; + check_insert_radius = 0; + refineflag = 0; + refinetet.tet = NULL; + refinesh.sh = NULL; + smlenflag = 0; + smlen = 0.0; + parentpt = NULL; + } + + insertvertexflags() { + init(); + } + }; + +//============================================================================// +// // +// flipconstraints // +// // +// A structure of a collection of data (options and parameters) which pass // +// to the edge flip function flipnm(). // +// // +//============================================================================// + + class flipconstraints { + + public: + + // Elementary flip flags. + int enqflag; // (= flipflag) + int chkencflag; + + // Control flags + int unflip; // Undo the performed flips. + int collectnewtets; // Collect the new tets created by flips. + int collectencsegflag; + + // Optimization flags. + int noflip_in_surface; // do not flip edges (not segment) in surface. + int remove_ndelaunay_edge; // Remove a non-Delaunay edge. + REAL bak_tetprism_vol; // The value to be minimized. + REAL tetprism_vol_sum; + int remove_large_angle; // Remove a large dihedral angle at edge. + REAL cosdihed_in; // The input cosine of the dihedral angle (> 0). + REAL cosdihed_out; // The improved cosine of the dihedral angle. + REAL max_asp_out; // Max asp ratio after the improvement of dihedral angle. + + // Boundary recovery flags. + int checkflipeligibility; + point seg[2]; // A constraining edge to be recovered. + point fac[3]; // A constraining face to be recovered. + point remvert; // A vertex to be removed. + + + flipconstraints() { + enqflag = 0; + chkencflag = 0; + + unflip = 0; + collectnewtets = 0; + collectencsegflag = 0; + + noflip_in_surface = 0; + remove_ndelaunay_edge = 0; + bak_tetprism_vol = 0.0; + tetprism_vol_sum = 0.0; + remove_large_angle = 0; + cosdihed_in = 0.0; + cosdihed_out = 0.0; + max_asp_out = 0.0; + + checkflipeligibility = 0; + seg[0] = NULL; + fac[0] = NULL; + remvert = NULL; + } + }; + +//============================================================================// +// // +// optparameters // +// // +// Optimization options and parameters. // +// // +//============================================================================// + + class optparameters { + + public: + + // The one of goals of optimization. + int max_min_volume; // Maximize the minimum volume. + int min_max_aspectratio; // Minimize the maximum aspect ratio. + int min_max_dihedangle; // Minimize the maximum dihedral angle. + + // The initial and improved value. + REAL initval, imprval; + + int numofsearchdirs; + REAL searchstep; + int maxiter; // Maximum smoothing iterations (disabled by -1). + int smthiter; // Performed iterations. + + + optparameters() { + max_min_volume = 0; + min_max_aspectratio = 0; + min_max_dihedangle = 0; + + initval = imprval = 0.0; + + numofsearchdirs = 10; + searchstep = 0.01; + maxiter = -1; // Unlimited smoothing iterations. + smthiter = 0; + + } + }; + + +//============================================================================// +// // +// Labels (enumeration declarations) used by TetGen. // +// // +//============================================================================// + + // Labels that signify the type of a vertex. + enum verttype {UNUSEDVERTEX, DUPLICATEDVERTEX, RIDGEVERTEX, /*ACUTEVERTEX,*/ + FACETVERTEX, VOLVERTEX, FREESEGVERTEX, FREEFACETVERTEX, + FREEVOLVERTEX, NREGULARVERTEX, DEADVERTEX}; + + // Labels that signify the result of triangle-triangle intersection test. + enum interresult {DISJOINT, INTERSECT, SHAREVERT, SHAREEDGE, SHAREFACE, + TOUCHEDGE, TOUCHFACE, ACROSSVERT, ACROSSEDGE, ACROSSFACE, + SELF_INTERSECT}; + + // Labels that signify the result of point location. + enum locateresult {UNKNOWN, OUTSIDE, INTETRAHEDRON, ONFACE, ONEDGE, ONVERTEX, + ENCVERTEX, ENCSEGMENT, ENCSUBFACE, NEARVERTEX, NONREGULAR, + INSTAR, BADELEMENT, NULLCAVITY, SHARPCORNER, FENSEDIN, + NONCOPLANAR, SELF_ENCROACH}; + +//============================================================================// +// // +// Variables of TetGen // +// // +//============================================================================// + + // Pointer to the input data (a set of nodes, a PLC, or a mesh). + tetgenio *in, *addin; + + // Pointer to the switches and parameters. + tetgenbehavior *b; + + // Pointer to a background mesh (contains size specification map). + tetgenmesh *bgm; + + // Memorypools to store mesh elements (points, tetrahedra, subfaces, and + // segments) and extra pointers between tetrahedra, subfaces, and segments. + memorypool *tetrahedrons, *subfaces, *subsegs, *points; + memorypool *tet2subpool, *tet2segpool; + + // Memorypools to store bad-quality (or encroached) elements. + memorypool *badtetrahedrons, *badsubfacs, *badsubsegs; + memorypool *split_subfaces_pool, *split_segments_pool; + arraypool *unsplit_badtets, *unsplit_subfaces, *unsplit_segments; + arraypool *check_tets_list; + + badface *stack_enc_segments, *stack_enc_subfaces; + + // Bad quality subfaces are ordered by priority queues. + badface *queuefront[64]; + badface *queuetail[64]; + int nextnonemptyq[64]; + int firstnonemptyq, recentq; + + // Bad quality tetrahedra are ordered by priority queues. + memorypool *badqual_tets_pool; + badface *bt_queuefront[64]; + badface *bt_queuetail[64]; + int bt_nextnonemptyq[64]; + int bt_firstnonemptyq, bt_recentq; + + // A memorypool to store faces to be flipped. + memorypool *flippool; + arraypool *later_unflip_queue, *unflipqueue; + badface *flipstack, *unflip_queue_front, *unflip_queue_tail; + + // Arrays used for point insertion (the Bowyer-Watson algorithm). + arraypool *cavetetlist, *cavebdrylist, *caveoldtetlist; + arraypool *cave_oldtet_list; // only tetrahedron's + arraypool *cavetetshlist, *cavetetseglist, *cavetetvertlist; + arraypool *caveencshlist, *caveencseglist; + arraypool *caveshlist, *caveshbdlist, *cavesegshlist; + triface _bw_faces[4096]; // _bw_faces[64][64]; + + // Stacks used for CDT construction and boundary recovery. + arraypool *subsegstack, *subfacstack, *subvertstack; + arraypool *skipped_segment_list, *skipped_facet_list; + + // Arrays of encroached segments and subfaces (for mesh refinement). + arraypool *encseglist, *encshlist; + + // The map between facets to their vertices (for mesh refinement). + int number_of_facets; + int *idx2facetlist; + point *facetverticeslist; + int *idx_segment_facet_list; // segment-to-facet map. + int *segment_facet_list; + int *idx_ridge_vertex_facet_list; // vertex-to-facet map. + int *ridge_vertex_facet_list; + + // The map between segments to their endpoints (for mesh refinement). + int segmentendpointslist_length; + point *segmentendpointslist; + double *segment_info_list; + int *idx_segment_ridge_vertex_list; // are two ridge vertices form a segment? + point *segment_ridge_vertex_list; + + // The infinite vertex. + point dummypoint; + // The recently visited tetrahedron, subface. + triface recenttet; + face recentsh; + + // PI is the ratio of a circle's circumference to its diameter. + static REAL PI; + + // The list of subdomains. (-A option). + int subdomains; // Number of subdomains. + int *subdomain_markers; + + // Various variables. + int numpointattrib; // Number of point attributes. + int numelemattrib; // Number of tetrahedron attributes. + int sizeoftensor; // Number of REALs per metric tensor. + int pointmtrindex; // Index to find the metric tensor of a point. + int pointparamindex; // Index to find the u,v coordinates of a point. + int point2simindex; // Index to find a simplex adjacent to a point. + int pointmarkindex; // Index to find boundary marker of a point. + int pointinsradiusindex; // Index to find the insertion radius of a point. + int elemattribindex; // Index to find attributes of a tetrahedron. + int polarindex; // Index to find the polar plane parameters. + int volumeboundindex; // Index to find volume bound of a tetrahedron. + int elemmarkerindex; // Index to find marker of a tetrahedron. + int shmarkindex; // Index to find boundary marker of a subface. + int areaboundindex; // Index to find area bound of a subface. + int checksubsegflag; // Are there segments in the tetrahedralization yet? + int checksubfaceflag; // Are there subfaces in the tetrahedralization yet? + int boundary_recovery_flag; + int checkconstraints; // Are there variant (node, seg, facet) constraints? + int nonconvex; // Is current mesh non-convex? + int autofliplinklevel; // The increase of link levels, default is 1. + int useinsertradius; // Save the insertion radius for Steiner points. + long samples; // Number of random samples for point location. + unsigned long randomseed; // Current random number seed. + REAL cosmaxdihed, cosmindihed; // The cosine values of max/min dihedral. + REAL cossmtdihed; // The cosine value of a bad dihedral to be smoothed. + REAL cosslidihed; // The cosine value of the max dihedral of a sliver. + REAL cos_large_dihed; // The cosine value of large dihedral (135 degree). + REAL opt_max_sliver_asp_ratio; // = 10 x b->opt_max_asp_ratio. + REAL minfaceang, minfacetdihed; // The minimum input (dihedral) angles. + REAL cos_facet_separate_ang_tol; + REAL cos_collinear_ang_tol; + REAL tetprism_vol_sum; // The total volume of tetrahedral-prisms (in 4D). + REAL longest; // The longest possible edge length. + REAL minedgelength; // = longest * b->epsion. + REAL xmax, xmin, ymax, ymin, zmax, zmin; // Bounding box of points. + + // Options for mesh refinement. + REAL big_radius_edge_ratio; // calculated by qualitystatistics(). + REAL smallest_insradius; // Save the smallest insertion radius. + long elem_limit; + long insert_point_count; // number of attempted insertions. + long report_refine_progress; // the next report event. + long last_point_count; // number of points after last report event. + long last_insertion_count; // number of insertions after last report event. + + // Counters. + long insegments; // Number of input segments. + long hullsize; // Number of exterior boundary faces. + long meshedges; // Number of mesh edges. + long meshhulledges; // Number of boundary mesh edges. + long steinerleft; // Number of Steiner points not yet used. + long dupverts; // Are there duplicated vertices? + long unuverts; // Are there unused vertices? + long duplicated_facets_count; // Are there duplicated facets.? + long nonregularcount; // Are there non-regular vertices? + long st_segref_count, st_facref_count, st_volref_count; // Steiner points. + long fillregioncount, cavitycount, cavityexpcount; + long flip14count, flip26count, flipn2ncount; + long flip23count, flip32count, flip44count, flip41count; + long flip31count, flip22count; + long opt_flips_count, opt_collapse_count, opt_smooth_count; + long recover_delaunay_count; + unsigned long totalworkmemory; // Total memory used by working arrays. + + +//============================================================================// +// // +// Mesh manipulation primitives // +// // +//============================================================================// + + // Fast lookup tables for mesh manipulation primitives. + static int bondtbl[12][12], fsymtbl[12][12]; + static int esymtbl[12], enexttbl[12], eprevtbl[12]; + static int enextesymtbl[12], eprevesymtbl[12]; + static int eorgoppotbl[12], edestoppotbl[12]; + static int facepivot1[12], facepivot2[12][12]; + static int orgpivot[12], destpivot[12], apexpivot[12], oppopivot[12]; + static int tsbondtbl[12][6], stbondtbl[12][6]; + static int tspivottbl[12][6], stpivottbl[12][6]; + static int ver2edge[12], edge2ver[6], epivot[12]; + static int sorgpivot [6], sdestpivot[6], sapexpivot[6]; + static int snextpivot[6]; + + void inittables(); + + // Primitives for tetrahedra. + inline tetrahedron encode(triface& t); + inline tetrahedron encode2(tetrahedron* ptr, int ver); + inline void decode(tetrahedron ptr, triface& t); + inline tetrahedron* decode_tet_only(tetrahedron ptr); + inline int decode_ver_only(tetrahedron ptr); + inline void bond(triface& t1, triface& t2); + inline void dissolve(triface& t); + inline void esym(triface& t1, triface& t2); + inline void esymself(triface& t); + inline void enext(triface& t1, triface& t2); + inline void enextself(triface& t); + inline void eprev(triface& t1, triface& t2); + inline void eprevself(triface& t); + inline void enextesym(triface& t1, triface& t2); + inline void enextesymself(triface& t); + inline void eprevesym(triface& t1, triface& t2); + inline void eprevesymself(triface& t); + inline void eorgoppo(triface& t1, triface& t2); + inline void eorgoppoself(triface& t); + inline void edestoppo(triface& t1, triface& t2); + inline void edestoppoself(triface& t); + inline void fsym(triface& t1, triface& t2); + inline void fsymself(triface& t); + inline void fnext(triface& t1, triface& t2); + inline void fnextself(triface& t); + inline point org (triface& t); + inline point dest(triface& t); + inline point apex(triface& t); + inline point oppo(triface& t); + inline void setorg (triface& t, point p); + inline void setdest(triface& t, point p); + inline void setapex(triface& t, point p); + inline void setoppo(triface& t, point p); + inline REAL elemattribute(tetrahedron* ptr, int attnum); + inline void setelemattribute(tetrahedron* ptr, int attnum, REAL value); + inline REAL* get_polar(tetrahedron* ptr); + inline REAL get_volume(tetrahedron* ptr); + inline REAL volumebound(tetrahedron* ptr); + inline void setvolumebound(tetrahedron* ptr, REAL value); + inline int elemindex(tetrahedron* ptr); + inline void setelemindex(tetrahedron* ptr, int value); + inline int elemmarker(tetrahedron* ptr); + inline void setelemmarker(tetrahedron* ptr, int value); + inline void infect(triface& t); + inline void uninfect(triface& t); + inline bool infected(triface& t); + inline void marktest(triface& t); + inline void unmarktest(triface& t); + inline bool marktested(triface& t); + inline void markface(triface& t); + inline void unmarkface(triface& t); + inline bool facemarked(triface& t); + inline void markedge(triface& t); + inline void unmarkedge(triface& t); + inline bool edgemarked(triface& t); + inline void marktest2(triface& t); + inline void unmarktest2(triface& t); + inline bool marktest2ed(triface& t); + inline int elemcounter(triface& t); + inline void setelemcounter(triface& t, int value); + inline void increaseelemcounter(triface& t); + inline void decreaseelemcounter(triface& t); + inline bool ishulltet(triface& t); + inline bool isdeadtet(triface& t); + + // Primitives for subfaces and subsegments. + inline void sdecode(shellface sptr, face& s); + inline shellface sencode(face& s); + inline shellface sencode2(shellface *sh, int shver); + inline void spivot(face& s1, face& s2); + inline void spivotself(face& s); + inline void sbond(face& s1, face& s2); + inline void sbond1(face& s1, face& s2); + inline void sdissolve(face& s); + inline point sorg(face& s); + inline point sdest(face& s); + inline point sapex(face& s); + inline void setsorg(face& s, point pointptr); + inline void setsdest(face& s, point pointptr); + inline void setsapex(face& s, point pointptr); + inline void sesym(face& s1, face& s2); + inline void sesymself(face& s); + inline void senext(face& s1, face& s2); + inline void senextself(face& s); + inline void senext2(face& s1, face& s2); + inline void senext2self(face& s); + inline REAL areabound(face& s); + inline void setareabound(face& s, REAL value); + inline int shellmark(face& s); + inline void setshellmark(face& s, int value); + inline void sinfect(face& s); + inline void suninfect(face& s); + inline bool sinfected(face& s); + inline void smarktest(face& s); + inline void sunmarktest(face& s); + inline bool smarktested(face& s); + inline void smarktest2(face& s); + inline void sunmarktest2(face& s); + inline bool smarktest2ed(face& s); + inline void smarktest3(face& s); + inline void sunmarktest3(face& s); + inline bool smarktest3ed(face& s); + inline void setfacetindex(face& f, int value); + inline int getfacetindex(face& f); + inline bool isdeadsh(face& s); + + // Primitives for interacting tetrahedra and subfaces. + inline void tsbond(triface& t, face& s); + inline void tsdissolve(triface& t); + inline void stdissolve(face& s); + inline void tspivot(triface& t, face& s); + inline void stpivot(face& s, triface& t); + + // Primitives for interacting tetrahedra and segments. + inline void tssbond1(triface& t, face& seg); + inline void sstbond1(face& s, triface& t); + inline void tssdissolve1(triface& t); + inline void sstdissolve1(face& s); + inline void tsspivot1(triface& t, face& s); + inline void sstpivot1(face& s, triface& t); + + // Primitives for interacting subfaces and segments. + inline void ssbond(face& s, face& edge); + inline void ssbond1(face& s, face& edge); + inline void ssdissolve(face& s); + inline void sspivot(face& s, face& edge); + + // Primitives for points. + inline int pointmark(point pt); + inline void setpointmark(point pt, int value); + inline enum verttype pointtype(point pt); + inline void setpointtype(point pt, enum verttype value); + inline int pointgeomtag(point pt); + inline void setpointgeomtag(point pt, int value); + inline REAL pointgeomuv(point pt, int i); + inline void setpointgeomuv(point pt, int i, REAL value); + inline void pinfect(point pt); + inline void puninfect(point pt); + inline bool pinfected(point pt); + inline void pmarktest(point pt); + inline void punmarktest(point pt); + inline bool pmarktested(point pt); + inline void pmarktest2(point pt); + inline void punmarktest2(point pt); + inline bool pmarktest2ed(point pt); + inline void pmarktest3(point pt); + inline void punmarktest3(point pt); + inline bool pmarktest3ed(point pt); + inline tetrahedron point2tet(point pt); + inline void setpoint2tet(point pt, tetrahedron value); + inline shellface point2sh(point pt); + inline void setpoint2sh(point pt, shellface value); + inline point point2ppt(point pt); + inline void setpoint2ppt(point pt, point value); + inline tetrahedron point2bgmtet(point pt); + inline void setpoint2bgmtet(point pt, tetrahedron value); + inline void setpointinsradius(point pt, REAL value); + inline REAL getpointinsradius(point pt); + inline bool issteinerpoint(point pt); + + // Advanced primitives. + inline void point2tetorg(point pt, triface& t); + inline void point2shorg(point pa, face& s); + inline point farsorg(face& seg); + inline point farsdest(face& seg); + +//============================================================================// +// // +// Memory managment // +// // +//============================================================================// + + void tetrahedrondealloc(tetrahedron*); + tetrahedron *tetrahedrontraverse(); + tetrahedron *alltetrahedrontraverse(); + void shellfacedealloc(memorypool*, shellface*); + shellface *shellfacetraverse(memorypool*); + void pointdealloc(point); + point pointtraverse(); + + void makeindex2pointmap(point*&); + void makepoint2submap(memorypool*, int*&, face*&); + void maketetrahedron(triface*); + void maketetrahedron2(triface*, point, point, point, point); + void makeshellface(memorypool*, face*); + void makepoint(point*, enum verttype); + + void initializepools(); + +//============================================================================// +// // +// Advanced geometric predicates and calculations // +// // +// the routine insphere_s() implements a simplified symbolic perturbation // +// scheme from Edelsbrunner, et al [*]. Hence the point-in-sphere test never // +// returns a zero. The idea is to perturb the weights of vertices in 4D. // +// // +// The routine tri_edge_test() determines whether or not a triangle and an // +// edge intersect in 3D. If they do cross, their intersection type is also // +// reported. This test is a combination of n 3D orientation tests (3 < n < 9).// +// It uses the robust orient3d() test to make the branch decisions. // +// // +// There are several routines to calculate geometrical quantities, e.g., // +// circumcenters, angles, dihedral angles, face normals, face areas, etc. // +// They are implemented using floating-point arithmetics. // +// // +//============================================================================// + + // Symbolic perturbations (robust) + REAL insphere_s(REAL*, REAL*, REAL*, REAL*, REAL*); + REAL orient4d_s(REAL*, REAL*, REAL*, REAL*, REAL*, + REAL, REAL, REAL, REAL, REAL); + + // An embedded 2-dimensional geometric predicate (non-robust) + REAL incircle3d(point pa, point pb, point pc, point pd); + + // Triangle-edge intersection test (robust) + int tri_edge_2d(point, point, point, point, point, point, int, int*, int*); + int tri_edge_tail(point,point,point,point,point,point,REAL,REAL,int,int*,int*); + int tri_edge_test(point, point, point, point, point, point, int, int*, int*); + + // Triangle-triangle intersection test (robust) + int tri_edge_inter_tail(point, point, point, point, point, REAL, REAL); + int tri_tri_inter(point, point, point, point, point, point); + + // Linear algebra functions + inline REAL dot(REAL* v1, REAL* v2); + inline void cross(REAL* v1, REAL* v2, REAL* n); + bool lu_decmp(REAL lu[4][4], int n, int* ps, REAL* d, int N); + void lu_solve(REAL lu[4][4], int n, int* ps, REAL* b, int N); + + // Geometric calculations (non-robust) + REAL orient3dfast(REAL *pa, REAL *pb, REAL *pc, REAL *pd); + inline REAL norm2(REAL x, REAL y, REAL z); + inline REAL distance(REAL* p1, REAL* p2); + inline REAL distance2(REAL* p1, REAL* p2); + void facenormal(point pa, point pb, point pc, REAL *n, int pivot, REAL *lav); + REAL facedihedral(REAL* pa, REAL* pb, REAL* pc1, REAL* pc2); + REAL triarea(REAL* pa, REAL* pb, REAL* pc); + REAL interiorangle(REAL* o, REAL* p1, REAL* p2, REAL* n); + REAL cos_interiorangle(REAL* o, REAL* p1, REAL* p2); + void projpt2edge(REAL* p, REAL* e1, REAL* e2, REAL* prj); + void projpt2face(REAL* p, REAL* f1, REAL* f2, REAL* f3, REAL* prj); + bool circumsphere(REAL*, REAL*, REAL*, REAL*, REAL* cent, REAL* radius); + bool orthosphere(REAL*,REAL*,REAL*,REAL*,REAL,REAL,REAL,REAL,REAL*,REAL*); + void planelineint(REAL*, REAL*, REAL*, REAL*, REAL*, REAL*, REAL*); + int linelineint(REAL*, REAL*, REAL*, REAL*, REAL*, REAL*, REAL*, REAL*); + REAL tetprismvol(REAL* pa, REAL* pb, REAL* pc, REAL* pd); + bool calculateabovepoint(arraypool*, point*, point*, point*); + void calculateabovepoint4(point, point, point, point); + +//============================================================================// +// // +// Local mesh transformations // +// // +// A local transformation replaces a set of tetrahedra with another set that // +// partitions the same space and boundaries. // +// // +// In 3D, the most straightforward local transformations are the elementary // +// flips performed within the convex hull of five vertices: 2-to-3, 3-to-2, // +// 1-to-4, and 4-to-1 flips. The numbers indicate the number of tetrahedra // +// before and after each flip. The 1-to-4 and 4-to-1 flip involve inserting // +// or deleting a vertex, respectively. // +// // +// There are complex local transformations that are a combination of element- // +// ary flips. For example, a 4-to-4 flip, which replaces two coplanar edges, // +// combines a 2-to-3 flip and a 3-to-2 flip. Note that the first 2-to-3 flip // +// will temporarily create a degenerate tetrahedron removed immediately by // +// the followed 3-to-2 flip. More generally, an n-to-m flip, where n > 3, // +// m = (n - 2) * 2, which removes an edge, can be done by first performing a // +// sequence of (n - 3) 2-to-3 flips followed by a 3-to-2 flip. // +// // +// The routines flip23(), flip32(), and flip41() perform the three elementray // +// flips. The flip14() is available inside the routine insertpoint(). // +// // +// The routines flipnm() and flipnm_post() implement a generalized edge flip // +// algorithm that uses elementary flips. // +// // +// The routine insertpoint() implements the Bowyer-Watson's cavity algorithm // +// to insert a vertex. It works for arbitrary tetrahedralization, either // +// Delaunay, or constrained Delaunay, or non-Delaunay. // +// // +//============================================================================// + + void flippush(badface*&, triface*); + + // The elementary flips. + void flip23(triface*, int, flipconstraints* fc); + void flip32(triface*, int, flipconstraints* fc); + void flip41(triface*, int, flipconstraints* fc); + + // A generalized edge flip. + int flipnm(triface*, int n, int level, int, flipconstraints* fc); + int flipnm_post(triface*, int n, int nn, int, flipconstraints* fc); + + // Point insertion. + int insertpoint(point, triface*, face*, face*, insertvertexflags*); + void insertpoint_abort(face*, insertvertexflags*); + +//============================================================================// +// // +// Delaunay tetrahedralization // +// // +// The routine incrementaldelaunay() implemented two incremental algorithms // +// for constructing Delaunay tetrahedralizations (DTs): the Bowyer-Watson // +// (B-W) algorithm and the incremental flip algorithm of Edelsbrunner and // +// Shah, "Incremental topological flipping works for regular triangulation," // +// Algorithmica, 15:233-241, 1996. // +// // +// The routine incrementalflip() implements the flip algorithm of [Edelsbrun- // +// ner and Shah, 1996]. It flips a queue of locally non-Delaunay faces (in // +// arbitrary order). The success is guaranteed when the Delaunay tetrahedra- // +// lization is constructed incrementally by adding one vertex at a time. // +// // +// The routine locate() finds a tetrahedron contains a new point in current // +// DT. It uses a simple stochastic walk algorithm: starting from an arbitrary // +// tetrahedron in DT, it finds the destination by visit one tetrahedron at a // +// time, randomly chooses a tetrahedron if there are more than one choices. // +// This algorithm terminates due to Edelsbrunner's acyclic theorem. // +// // +// Choose a good starting tetrahedron is crucial to the speed of the walk. // +// TetGen initially uses the "jump-and-walk" algorithm of Muecke, E.P., Saias,// +// I., and Zhu, B. "Fast Randomized Point Location Without Preprocessing." In // +// Proceedings of the 12th ACM Symposium on Computational Geometry, 274-283, // +// 1996. It first randomly samples several tetrahedra in the DT and then // +// choosing the closet one to start walking. // +// // +// The above algorithm slows download dramatically as the number of points // +// grows -- reported in Amenta, N., Choi, S. and Rote, G., "Incremental // +// construction con {BRIO}," In Proceedings of 19th ACM Symposium on Computa- // +// tional Geometry, 211-219, 2003. On the other hand, Liu and Snoeyink showed // +// that the point location could be made in constant time if the points are // +// pre-sorted so that the nearby points in space have nearby indices, then // +// adding the points in this order. They sorted the points along the 3D // +// Hilbert curve. // +// // +// The routine hilbert_sort3() sorts a set of 3D points along the 3D Hilbert // +// curve. It recursively splits a point set according to the Hilbert indices // +// mapped to the subboxes of the bounding box of the point set. The Hilbert // +// indices is calculated by Butz's algorithm in 1971. An excellent exposition // +// of this algorithm can be found in the paper of Hamilton, C., "Compact // +// Hilbert Indices", Technical Report CS-2006-07, Computer Science, Dalhousie // +// University, 2006 (the Section 2). My implementation also referenced Steven // +// Witham's performance of "Hilbert walk" (hopefully, it is still available // +// at http://www.tiac.net/~sw/2008/10/Hilbert/). // +// // +// TetGen sorts the points using the method in the paper of Boissonnat,J.-D., // +// Devillers, O. and Hornus, S. "Incremental Construction of the Delaunay // +// Triangulation and the Delaunay Graph in Medium Dimension," In Proceedings // +// of the 25th ACM Symposium on Computational Geometry, 2009. It first // +// randomly sorts the points into subgroups using the Biased Randomized // +// Insertion Ordering (BRIO) of Amenta et al 2003, then sorts the points in // +// each subgroup along the 3D Hilbert curve. Inserting points in this order // +// ensure a randomized "sprinkling" of the points over the domain, while // +// sorting of each subset provides locality. // +// // +//============================================================================// + + void transfernodes(); + + // Point sorting. + int transgc[8][3][8], tsb1mod3[8]; + void hilbert_init(int n); + int hilbert_split(point* vertexarray, int arraysize, int gc0, int gc1, + REAL, REAL, REAL, REAL, REAL, REAL); + void hilbert_sort3(point* vertexarray, int arraysize, int e, int d, + REAL, REAL, REAL, REAL, REAL, REAL, int depth); + void brio_multiscale_sort(point*,int,int threshold,REAL ratio,int* depth); + + // Point location. + unsigned long randomnation(unsigned int choices); + void randomsample(point searchpt, triface *searchtet); + enum locateresult locate(point searchpt, triface *searchtet, int chkencflag = 0); + + // Incremental Delaunay construction. + enum locateresult locate_dt(point searchpt, triface *searchtet); + int insert_vertex_bw(point, triface*, insertvertexflags*); + void initialdelaunay(point pa, point pb, point pc, point pd); + void incrementaldelaunay(clock_t&); + +//============================================================================// +// // +// Surface triangulation // +// // +//============================================================================// + + void flipshpush(face*); + void flip22(face*, int, int); + void flip31(face*, int); + long lawsonflip(); + int sinsertvertex(point newpt, face*, face*, int iloc, int bowywat, int); + int sremovevertex(point delpt, face*, face*, int lawson); + + enum locateresult slocate(point, face*, int, int, int); + enum interresult sscoutsegment(face*, point, int, int, int); + void scarveholes(int, REAL*); + int triangulate(int, arraypool*, arraypool*, int, REAL*); + + void unifysegments(); + void identifyinputedges(point*); + void mergefacets(); + void meshsurface(); + + +//============================================================================// +// // +// Constrained Delaunay tetrahedralization // +// // +// A constrained Delaunay tetrahedralization (CDT) is a variation of a Delau- // +// nay tetrahedralization (DT) that respects the boundary of a 3D PLC (mesh // +// domain). A crucial difference between a CDT and a DT is that triangles in // +// the PLC's polygons are not required to be locally Delaunay, which frees // +// the CDT to respect the PLC's polygons better. CDTs have optimal properties // +// similar to those of DTs. // +// // +// Steiner Points and Steiner CDTs. It is well-known that even a simple 3D // +// polyhedron may not have a tetrahedralization which only uses its vertices. // +// Some extra points, so-called "Steiner points" are needed to form a tetrah- // +// edralization of such polyhedron. A Steiner CDT of a 3D PLC is a CDT // +// containing Steiner points. TetGen generates Steiner CDTs. // +// // +// The routine constraineddelaunay() creates a (Steiner) CDT of the PLC // +// (including Steiner points). It has two steps, (1) segment recovery and (2) // +// facet (polygon) recovery. // +// // +// The routine delaunizesegments() implements the segment recovery algorithm // +// of Si, H., and Gaertner, K. "Meshing Piecewise Linear Complexes by // +// Constrained Delaunay Tetrahedralizations," In Proceedings of the 14th // +// International Meshing Roundtable, 147--163, 2005. It adds Steiner points // +// into non-Delaunay segments until all subsegments appear together in a DT. // +// The running time of this algorithm is proportional to the number of // +// Steiner points. // +// // +// There are two incremental facet recovery algorithms: the cavity re- // +// triangulation algorithm of Si, H., and Gaertner, K. "3D Boundary Recovery // +// by Constrained Delaunay Tetrahedralization," International Journal for // +// Numerical Methods in Engineering, 85:1341-1364, 2011, and the flip // +// algorithm of Shewchuk, J. "Updating and Constructing Constrained Delaunay // +// and Constrained Regular Triangulations by Flips." In Proceedings of the // +// 19th ACM Symposium on Computational Geometry, 86-95, 2003. // +// // +// Although no Steiner point is needed in step (2), a facet with non-coplanar // +// vertices might need Steiner points. It is discussed in the paper of Si, H.,// +// and Shewchuk, J., "Incrementally Constructing and Updating Constrained // +// Delaunay Tetrahedralizations with Finite Precision Coordinates." In // +// Proceedings of the 21th International Meshing Roundtable, 2012. // +// // +// Our implementation of the facet recovery algorithms recovers a "missing // +// region" at a time. Each missing region is a subset of connected interiors // +// of a polygon. The routine formcavity() creates the cavity of crossing // +// tetrahedra of the missing region. The cavity re-triangulation algorithm is // +// implemented by three subroutines, delaunizecavity(), fillcavity(), and // +// carvecavity(). Since it may fail due to non-coplanar vertices, the // +// subroutine restorecavity() is used to restore the original cavity. // +// // +// The routine flipinsertfacet() implements the flip algorithm. The sub- // +// routine flipcertify() is used to maintain the priority queue of flips. // +// The routine refineregion() is called when the facet recovery algorithm // +// fails to recover a missing region. It inserts Steiner points to refine the // +// missing region. To avoid inserting Steiner points very close to existing // +// segments. The classical encroachment rules of the Delaunay refinement // +// algorithm are used to choose the Steiner points. The routine // +// constrainedfacets() does the facet recovery by using either the cavity re- // +// triangulation algorithm (default) or the flip algorithm. It results in a // +// CDT of the (modified) PLC (including Steiner points). // +// // +//============================================================================// + + enum interresult finddirection(triface* searchtet, point endpt); + enum interresult scoutsegment(point, point, face*, triface*, point*, + arraypool*); + int getsteinerptonsegment(face* seg, point refpt, point steinpt); + void delaunizesegments(); + + int scoutsubface(face* searchsh,triface* searchtet,int shflag); + void formregion(face*, arraypool*, arraypool*, arraypool*); + int scoutcrossedge(triface& crosstet, arraypool*, arraypool*); + bool formcavity(triface*, arraypool*, arraypool*, arraypool*, arraypool*, + arraypool*, arraypool*); + // Facet recovery by cavity re-triangulation [Si and Gaertner 2011]. + void delaunizecavity(arraypool*, arraypool*, arraypool*, arraypool*, + arraypool*, arraypool*); + bool fillcavity(arraypool*, arraypool*, arraypool*, arraypool*, + arraypool*, arraypool*, triface* crossedge); + void carvecavity(arraypool*, arraypool*, arraypool*); + void restorecavity(arraypool*, arraypool*, arraypool*, arraypool*); + // Facet recovery by flips [Shewchuk 2003]. + void flipcertify(triface *chkface, badface **pqueue, point, point, point); + void flipinsertfacet(arraypool*, arraypool*, arraypool*, arraypool*); + + int insertpoint_cdt(point, triface*, face*, face*, insertvertexflags*, + arraypool*, arraypool*, arraypool*, arraypool*, + arraypool*, arraypool*); + void refineregion(face&, arraypool*, arraypool*, arraypool*, arraypool*, + arraypool*, arraypool*); + void constrainedfacets(); + + void constraineddelaunay(clock_t&); + +//============================================================================// +// // +// Constrained tetrahedralizations. // +// // +//============================================================================// + + void sort_2pts(point p1, point p2, point ppt[2]); + void sort_3pts(point p1, point p2, point p3, point ppt[3]); + + bool is_collinear_at(point mid, point left, point right); + bool is_segment(point p1, point p2); + bool valid_constrained_f23(triface&, point pd, point pe); + bool valid_constrained_f32(triface*, point pa, point pb); + + int checkflipeligibility(int fliptype, point, point, point, point, point, + int level, int edgepivot, flipconstraints* fc); + + int removeedgebyflips(triface*, flipconstraints*); + int removefacebyflips(triface*, flipconstraints*); + + int recoveredgebyflips(point, point, face*, triface*, int fullsearch, int& idir); + int add_steinerpt_in_schoenhardtpoly(triface*, int, int, int chkencflag); + int add_steinerpt_in_segment(face*, int searchlevel, int& idir); + int add_steinerpt_to_recover_edge(point, point, face*, int, int, int& idir); + int recoversegments(arraypool*, int fullsearch, int steinerflag); + + int recoverfacebyflips(point,point,point,face*,triface*,int&,point*,point*); + int recoversubfaces(arraypool*, int steinerflag); + + int getvertexstar(int, point searchpt, arraypool*, arraypool*, arraypool*); + int getedge(point, point, triface*); + int reduceedgesatvertex(point startpt, arraypool* endptlist); + int removevertexbyflips(point steinerpt); + + int smoothpoint(point smtpt, arraypool*, int ccw, optparameters *opm); + int suppressbdrysteinerpoint(point steinerpt); + int suppresssteinerpoints(); + + void recoverboundary(clock_t&); + +//============================================================================// +// // +// Mesh reconstruction // +// // +//============================================================================// + + void carveholes(); + + void reconstructmesh(); + + int search_face(point p0, point p1, point p2, triface &tetloop); + int search_edge(point p0, point p1, triface &tetloop); + int scout_point(point, triface*, int randflag); + REAL getpointmeshsize(point, triface*, int iloc); + void interpolatemeshsize(); + + void insertconstrainedpoints(point *insertarray, int arylen, int rejflag); + void insertconstrainedpoints(tetgenio *addio); + + void collectremovepoints(arraypool *remptlist); + void meshcoarsening(); + +//============================================================================// +// // +// Mesh refinement // +// // +// The purpose of mesh refinement is to obtain a tetrahedral mesh with well- // +// -shaped tetrahedra and appropriate mesh size. It is necessary to insert // +// new Steiner points to achieve this property. The questions are (1) how to // +// choose the Steiner points? and (2) how to insert them? // +// // +// Delaunay refinement is a technique first developed by Chew [1989] and // +// Ruppert [1993, 1995] to generate quality triangular meshes in the plane. // +// It provides guarantee on the smallest angle of the triangles. Rupper's // +// algorithm guarantees that the mesh is size-optimal (to within a constant // +// factor) among all meshes with the same quality. // +// Shewchuk generalized Ruppert's algorithm into 3D in his PhD thesis // +// [Shewchuk 1997]. A short version of his algorithm appears in "Tetrahedral // +// Mesh Generation by Delaunay Refinement," In Proceedings of the 14th ACM // +// Symposium on Computational Geometry, 86-95, 1998. It guarantees that all // +// tetrahedra of the output mesh have a "radius-edge ratio" (equivalent to // +// the minimal face angle) bounded. However, it does not remove slivers, a // +// type of very flat tetrahedra which can have no small face angles but have // +// very small (and large) dihedral angles. Moreover, it may not terminate if // +// the input PLC contains "sharp features", e.g., two edges (or two facets) // +// meet at an acute angle (or dihedral angle). // +// // +// TetGen uses the basic Delaunay refinement scheme to insert Steiner points. // +// While it always maintains a constrained Delaunay mesh. The algorithm is // +// described in Si, H., "Adaptive Constrained Delaunay Mesh Generation," // +// International Journal for Numerical Methods in Engineering, 75:856-880. // +// This algorithm always terminates and sharp features are easily preserved. // +// The mesh has good quality (same as Shewchuk's Delaunay refinement algori- // +// thm) in the bulk of the mesh domain. Moreover, it supports the generation // +// of adaptive mesh according to a (isotropic) mesh sizing function. // +// // +//============================================================================// + + void makesegmentendpointsmap(); + REAL set_ridge_vertex_protecting_ball(point); + REAL get_min_angle_at_ridge_vertex(face* seg); + REAL get_min_diahedral_angle(face* seg); + void create_segment_info_list(); + + void makefacetverticesmap(); + void create_segment_facet_map(); + + int ridge_vertices_adjacent(point, point); + int facet_ridge_vertex_adjacent(face *, point); + int segsegadjacent(face *, face *); + int segfacetadjacent(face *checkseg, face *checksh); + int facetfacetadjacent(face *, face *); + bool is_sharp_segment(face* seg); + bool does_seg_contain_acute_vertex(face* seg); + bool create_a_shorter_edge(point steinerpt, point nearpt); + + void enqueuesubface(memorypool*, face*); + void enqueuetetrahedron(triface*); + + bool check_encroachment(point pa, point pb, point checkpt); + bool check_enc_segment(face *chkseg, point *pencpt); + bool get_steiner_on_segment(face* seg, point encpt, point newpt); + bool split_segment(face *splitseg, point encpt, REAL *param, int qflag, int, int*); + void repairencsegs(REAL *param, int qflag, int chkencflag); + + bool get_subface_ccent(face *chkfac, REAL *ccent); + bool check_enc_subface(face *chkfac, point *pencpt, REAL *ccent, REAL *radius); + bool check_subface(face *chkfac, REAL *ccent, REAL radius, REAL *param); + void enqueue_subface(face *bface, point encpt, REAL *ccent, REAL *param); + badface* top_subface(); + void dequeue_subface(); + void parallel_shift(point pa, point pb, point pc, point pt, REAL* ppt); + enum locateresult locate_on_surface(point searchpt, face* searchsh); + bool split_subface(face *splitfac, point encpt, REAL *ccent, REAL*, int, int, int*); + void repairencfacs(REAL *param, int qflag, int chkencflag); + + bool check_tetrahedron(triface *chktet, REAL* param, int& qflag); + bool checktet4split(triface *chktet, REAL* param, int& qflag); + enum locateresult locate_point_walk(point searchpt, triface*, int chkencflag); + bool split_tetrahedron(triface*, REAL*, int, int, insertvertexflags &ivf); + void repairbadtets(REAL queratio, int chkencflag); + + void delaunayrefinement(); + +//============================================================================// +// // +// Mesh optimization // +// // +//============================================================================// + + long lawsonflip3d(flipconstraints *fc); + void recoverdelaunay(); + + int get_seg_laplacian_center(point mesh_vert, REAL target[3]); + int get_surf_laplacian_center(point mesh_vert, REAL target[3]); + int get_laplacian_center(point mesh_vert, REAL target[3]); + bool move_vertex(point mesh_vert, REAL target[3]); + void smooth_vertices(); + + bool get_tet(point, point, point, point, triface *); + bool get_tetqual(triface *chktet, point oppo_pt, badface *bf); + bool get_tetqual(point, point, point, point, badface *bf); + void enqueue_badtet(badface *bf); + badface* top_badtet(); + void dequeue_badtet(); + + bool add_steinerpt_to_repair(badface *bf, bool bSmooth); + bool flip_edge_to_improve(triface *sliver_edge, REAL& improved_cosmaxd); + bool repair_tet(badface *bf, bool bFlips, bool bSmooth, bool bSteiners); + long repair_badqual_tets(bool bFlips, bool bSmooth, bool bSteiners); + void improve_mesh(); + +//============================================================================// +// // +// Mesh check and statistics // +// // +//============================================================================// + + // Mesh validations. + int check_mesh(int topoflag); + int check_shells(); + int check_segments(); + int check_delaunay(int perturb = 1); + int check_regular(int); + int check_conforming(int); + + // Mesh statistics. + void printfcomma(unsigned long n); + void qualitystatistics(); + void memorystatistics(); + void statistics(); + +//============================================================================// +// // +// Mesh output // +// // +//============================================================================// + + void jettisonnodes(); + void highorder(); + void indexelements(); + void numberedges(); + void outnodes(tetgenio*); + void outmetrics(tetgenio*); + void outelements(tetgenio*); + void outfaces(tetgenio*); + void outhullfaces(tetgenio*); + void outsubfaces(tetgenio*); + void outedges(tetgenio*); + void outsubsegments(tetgenio*); + void outneighbors(tetgenio*); + void outvoronoi(tetgenio*); + void outsmesh(char*); + void outmesh2medit(char*); + void outmesh2vtk(char*, int); + void out_surfmesh_vtk(char*, int); + void out_intersected_facets(); + + + + +//============================================================================// +// // +// Constructor & destructor // +// // +//============================================================================// + + void initializetetgenmesh() + { + in = addin = NULL; + b = NULL; + bgm = NULL; + + tetrahedrons = subfaces = subsegs = points = NULL; + tet2segpool = tet2subpool = NULL; + dummypoint = NULL; + + badtetrahedrons = badsubfacs = badsubsegs = NULL; + split_segments_pool = split_subfaces_pool = NULL; + unsplit_badtets = unsplit_subfaces = unsplit_segments = NULL; + check_tets_list = NULL; + badqual_tets_pool = NULL; + + stack_enc_segments = stack_enc_subfaces = NULL; + + flippool = NULL; + flipstack = unflip_queue_front = unflip_queue_tail = NULL; + later_unflip_queue = unflipqueue = NULL; + + cavetetlist = cavebdrylist = caveoldtetlist = NULL; + cave_oldtet_list = NULL; + cavetetshlist = cavetetseglist = cavetetvertlist = NULL; + caveencshlist = caveencseglist = NULL; + caveshlist = caveshbdlist = cavesegshlist = NULL; + + subsegstack = subfacstack = subvertstack = NULL; + skipped_segment_list = skipped_facet_list = NULL; + + encseglist = encshlist = NULL; + + number_of_facets = 0; + idx2facetlist = NULL; + facetverticeslist = NULL; + idx_segment_facet_list = NULL; + segment_facet_list = NULL; + idx_ridge_vertex_facet_list = NULL; + ridge_vertex_facet_list = NULL; + + segmentendpointslist_length = 0; + segmentendpointslist = NULL; + segment_info_list = NULL; + idx_segment_ridge_vertex_list = NULL; + segment_ridge_vertex_list = NULL; + + subdomains = 0; + subdomain_markers = NULL; + + numpointattrib = numelemattrib = 0; + sizeoftensor = 0; + pointmtrindex = 0; + pointparamindex = 0; + pointmarkindex = 0; + point2simindex = 0; + pointinsradiusindex = 0; + elemattribindex = 0; + polarindex = 0; + volumeboundindex = 0; + shmarkindex = 0; + areaboundindex = 0; + checksubsegflag = 0; + checksubfaceflag = 0; + boundary_recovery_flag = 0; + checkconstraints = 0; + nonconvex = 0; + autofliplinklevel = 1; + useinsertradius = 0; + samples = 0l; + randomseed = 1l; + minfaceang = minfacetdihed = PI; + cos_facet_separate_ang_tol = cos(179.9/180.*PI); + cos_collinear_ang_tol = cos(179.9/180.*PI); + tetprism_vol_sum = 0.0; + longest = minedgelength = 0.0; + xmax = xmin = ymax = ymin = zmax = zmin = 0.0; + + smallest_insradius = 1.e+30; + big_radius_edge_ratio = 100.0; + elem_limit = 0; + insert_point_count = 0l; + report_refine_progress = 0l; + last_point_count = 0l; + last_insertion_count = 0l; + + insegments = 0l; + hullsize = 0l; + meshedges = meshhulledges = 0l; + steinerleft = -1; + dupverts = 0l; + unuverts = 0l; + duplicated_facets_count = 0l; + nonregularcount = 0l; + st_segref_count = st_facref_count = st_volref_count = 0l; + fillregioncount = cavitycount = cavityexpcount = 0l; + flip14count = flip26count = flipn2ncount = 0l; + flip23count = flip32count = flip44count = flip41count = 0l; + flip22count = flip31count = 0l; + recover_delaunay_count = 0l; + opt_flips_count = opt_collapse_count = opt_smooth_count = 0l; + totalworkmemory = 0l; + + } // tetgenmesh() + + void freememory() + { + if (bgm != NULL) { + delete bgm; + } + + if (points != (memorypool *) NULL) { + delete points; + delete [] dummypoint; + } + if (tetrahedrons != (memorypool *) NULL) { + delete tetrahedrons; + } + if (subfaces != (memorypool *) NULL) { + delete subfaces; + delete subsegs; + } + if (tet2segpool != NULL) { + delete tet2segpool; + delete tet2subpool; + } + + if (badtetrahedrons) { + delete badtetrahedrons; + } + if (badsubfacs) { + delete badsubfacs; + } + if (badsubsegs) { + delete badsubsegs; + } + if (unsplit_badtets) { + delete unsplit_badtets; + } + if (check_tets_list) { + delete check_tets_list; + } + + if (flippool != NULL) { + delete flippool; + delete later_unflip_queue; + delete unflipqueue; + } + + if (cavetetlist != NULL) { + delete cavetetlist; + delete cavebdrylist; + delete caveoldtetlist; + delete cavetetvertlist; + delete cave_oldtet_list; + } + + if (caveshlist != NULL) { + delete caveshlist; + delete caveshbdlist; + delete cavesegshlist; + delete cavetetshlist; + delete cavetetseglist; + delete caveencshlist; + delete caveencseglist; + } + + if (subsegstack != NULL) { + delete subsegstack; + delete subfacstack; + delete subvertstack; + } + + if (idx2facetlist != NULL) { + delete [] idx2facetlist; + delete [] facetverticeslist; + delete [] idx_segment_facet_list; + delete [] segment_facet_list; + delete [] idx_ridge_vertex_facet_list; + delete [] ridge_vertex_facet_list; + } + + if (segmentendpointslist != NULL) { + delete [] segmentendpointslist; + delete [] idx_segment_ridge_vertex_list; + delete [] segment_ridge_vertex_list; + } + + if (segment_info_list != NULL) { + delete [] segment_info_list; + } + + if (subdomain_markers != NULL) { + delete [] subdomain_markers; + } + + initializetetgenmesh(); + } + + tetgenmesh() + { + initializetetgenmesh(); + } + + ~tetgenmesh() + { + freememory(); + } // ~tetgenmesh() + +}; // End of class tetgenmesh. + +//============================================================================// +// // +// tetrahedralize() Interface for using TetGen's library to generate // +// Delaunay tetrahedralizations, constrained Delaunay // +// tetrahedralizations, quality tetrahedral meshes. // +// // +// 'in' is an object of 'tetgenio' containing a PLC or a previously generated // +// tetrahedral mesh you want to refine. 'out' is another object of 'tetgenio'// +// for returing the generated tetrahedral mesh. If it is a NULL pointer, the // +// output mesh is saved to file(s). If 'bgmin' != NULL, it contains a back- // +// ground mesh defining a mesh size function. // +// // +//============================================================================// + +void tetrahedralize(tetgenbehavior *b, tetgenio *in, tetgenio *out, + tetgenio *addin = NULL, tetgenio *bgmin = NULL); + +#ifdef TETLIBRARY +void tetrahedralize(char *switches, tetgenio *in, tetgenio *out, + tetgenio *addin = NULL, tetgenio *bgmin = NULL); + +#endif // #ifdef TETLIBRARY + +//============================================================================// +// // +// terminatetetgen() Terminate TetGen with a given exit code. // +// // +//============================================================================// + + +inline void terminatetetgen(tetgenmesh *m, int x) +{ +#ifdef TETLIBRARY + throw x; +#else + switch (x) { + case 1: // Out of memory. + printf("Error: Out of memory.\n"); + break; + case 2: // Encounter an internal error. + printf("Please report this bug to Hang.Si@wias-berlin.de. Include\n"); + printf(" the message above, your input data set, and the exact\n"); + printf(" command line you used to run this program, thank you.\n"); + break; + case 3: + printf("The input surface mesh contain self-intersections. Program stopped.\n"); + //printf("Hint: use -d option to detect all self-intersections.\n"); + break; + case 4: + printf("A very small input feature size was detected. Program stopped.\n"); + if (m) { + printf("Hint: use -T option to set a smaller tolerance. Current is %g\n", + m->b->epsilon); + } + break; + case 5: + printf("Two very close input facets were detected. Program stopped.\n"); + printf("Hint: use -Y option to avoid adding Steiner points in boundary.\n"); + break; + case 10: + printf("An input error was detected. Program stopped.\n"); + break; + case 200: + printf("Boundary contains Steiner points (-YY option). Program stopped.\n"); + break; + } // switch (x) + exit(x); +#endif // #ifdef TETLIBRARY +} + +//============================================================================// +// // +// Primitives for tetrahedra // +// // +//============================================================================// + +// encode() compress a handle into a single pointer. It relies on the +// assumption that all addresses of tetrahedra are aligned to sixteen- +// byte boundaries, so that the last four significant bits are zero. + +inline tetgenmesh::tetrahedron tetgenmesh::encode(triface& t) { + return (tetrahedron) ((uintptr_t) (t).tet | (uintptr_t) (t).ver); +} + +inline tetgenmesh::tetrahedron tetgenmesh::encode2(tetrahedron* ptr, int ver) { + return (tetrahedron) ((uintptr_t) (ptr) | (uintptr_t) (ver)); +} + +// decode() converts a pointer to a handle. The version is extracted from +// the four least significant bits of the pointer. + +inline void tetgenmesh::decode(tetrahedron ptr, triface& t) { + (t).ver = (int) ((uintptr_t) (ptr) & (uintptr_t) 15); + (t).tet = (tetrahedron *) ((uintptr_t) (ptr) ^ (uintptr_t) (t).ver); +} + +inline tetgenmesh::tetrahedron* tetgenmesh::decode_tet_only(tetrahedron ptr) +{ + return (tetrahedron *) ((((uintptr_t) ptr) >> 4) << 4); +} + +inline int tetgenmesh::decode_ver_only(tetrahedron ptr) +{ + return (int) ((uintptr_t) (ptr) & (uintptr_t) 15); +} + +// bond() connects two tetrahedra together. (t1,v1) and (t2,v2) must +// refer to the same face and the same edge. + +inline void tetgenmesh::bond(triface& t1, triface& t2) { + t1.tet[t1.ver & 3] = encode2(t2.tet, bondtbl[t1.ver][t2.ver]); + t2.tet[t2.ver & 3] = encode2(t1.tet, bondtbl[t2.ver][t1.ver]); +} + + +// dissolve() a bond (from one side). + +inline void tetgenmesh::dissolve(triface& t) { + t.tet[t.ver & 3] = NULL; +} + +// enext() finds the next edge (counterclockwise) in the same face. + +inline void tetgenmesh::enext(triface& t1, triface& t2) { + t2.tet = t1.tet; + t2.ver = enexttbl[t1.ver]; // (t1.ver + 4) % 12; +} + +inline void tetgenmesh::enextself(triface& t) { + t.ver = enexttbl[t.ver]; // (t.ver + 4) % 12; +} + +// eprev() finds the next edge (clockwise) in the same face. + +inline void tetgenmesh::eprev(triface& t1, triface& t2) { + t2.tet = t1.tet; + t2.ver = eprevtbl[t1.ver]; // (t1.ver + 8) % 12; +} + +inline void tetgenmesh::eprevself(triface& t) { + t.ver = eprevtbl[t.ver]; // (t.ver + 8) % 12; +} + +// esym() finds the reversed edge. It is in the other face of the +// same tetrahedron. + +inline void tetgenmesh::esym(triface& t1, triface& t2) { + (t2).tet = (t1).tet; + (t2).ver = esymtbl[(t1).ver]; +} + +inline void tetgenmesh::esymself(triface& t) { + (t).ver = esymtbl[(t).ver]; +} + +// enextesym() finds the reversed edge of the next edge. It is in the other +// face of the same tetrahedron. It is the combination esym() * enext(). + +inline void tetgenmesh::enextesym(triface& t1, triface& t2) { + t2.tet = t1.tet; + t2.ver = enextesymtbl[t1.ver]; +} + +inline void tetgenmesh::enextesymself(triface& t) { + t.ver = enextesymtbl[t.ver]; +} + +// eprevesym() finds the reversed edge of the previous edge. + +inline void tetgenmesh::eprevesym(triface& t1, triface& t2) { + t2.tet = t1.tet; + t2.ver = eprevesymtbl[t1.ver]; +} + +inline void tetgenmesh::eprevesymself(triface& t) { + t.ver = eprevesymtbl[t.ver]; +} + +// eorgoppo() Finds the opposite face of the origin of the current edge. +// Return the opposite edge of the current edge. + +inline void tetgenmesh::eorgoppo(triface& t1, triface& t2) { + t2.tet = t1.tet; + t2.ver = eorgoppotbl[t1.ver]; +} + +inline void tetgenmesh::eorgoppoself(triface& t) { + t.ver = eorgoppotbl[t.ver]; +} + +// edestoppo() Finds the opposite face of the destination of the current +// edge. Return the opposite edge of the current edge. + +inline void tetgenmesh::edestoppo(triface& t1, triface& t2) { + t2.tet = t1.tet; + t2.ver = edestoppotbl[t1.ver]; +} + +inline void tetgenmesh::edestoppoself(triface& t) { + t.ver = edestoppotbl[t.ver]; +} + +// fsym() finds the adjacent tetrahedron at the same face and the same edge. + +inline void tetgenmesh::fsym(triface& t1, triface& t2) { + decode((t1).tet[(t1).ver & 3], t2); + t2.ver = fsymtbl[t1.ver][t2.ver]; +} + + +#define fsymself(t) \ + t1ver = (t).ver; \ + decode((t).tet[(t).ver & 3], (t));\ + (t).ver = fsymtbl[t1ver][(t).ver] + +// fnext() finds the next face while rotating about an edge according to +// a right-hand rule. The face is in the adjacent tetrahedron. It is +// the combination: fsym() * esym(). + +inline void tetgenmesh::fnext(triface& t1, triface& t2) { + decode(t1.tet[facepivot1[t1.ver]], t2); + t2.ver = facepivot2[t1.ver][t2.ver]; +} + + +#define fnextself(t) \ + t1ver = (t).ver; \ + decode((t).tet[facepivot1[(t).ver]], (t)); \ + (t).ver = facepivot2[t1ver][(t).ver] + + +// The following primtives get or set the origin, destination, face apex, +// or face opposite of an ordered tetrahedron. + +inline tetgenmesh::point tetgenmesh::org(triface& t) { + return (point) (t).tet[orgpivot[(t).ver]]; +} + +inline tetgenmesh::point tetgenmesh:: dest(triface& t) { + return (point) (t).tet[destpivot[(t).ver]]; +} + +inline tetgenmesh::point tetgenmesh:: apex(triface& t) { + return (point) (t).tet[apexpivot[(t).ver]]; +} + +inline tetgenmesh::point tetgenmesh:: oppo(triface& t) { + return (point) (t).tet[oppopivot[(t).ver]]; +} + +inline void tetgenmesh:: setorg(triface& t, point p) { + (t).tet[orgpivot[(t).ver]] = (tetrahedron) (p); +} + +inline void tetgenmesh:: setdest(triface& t, point p) { + (t).tet[destpivot[(t).ver]] = (tetrahedron) (p); +} + +inline void tetgenmesh:: setapex(triface& t, point p) { + (t).tet[apexpivot[(t).ver]] = (tetrahedron) (p); +} + +inline void tetgenmesh:: setoppo(triface& t, point p) { + (t).tet[oppopivot[(t).ver]] = (tetrahedron) (p); +} + +#define setvertices(t, torg, tdest, tapex, toppo) \ + (t).tet[orgpivot[(t).ver]] = (tetrahedron) (torg);\ + (t).tet[destpivot[(t).ver]] = (tetrahedron) (tdest); \ + (t).tet[apexpivot[(t).ver]] = (tetrahedron) (tapex); \ + (t).tet[oppopivot[(t).ver]] = (tetrahedron) (toppo) + + +inline REAL* tetgenmesh::get_polar(tetrahedron* ptr) +{ + return &(((REAL *) (ptr))[polarindex]); +} +inline REAL tetgenmesh::get_volume(tetrahedron* ptr) +{ + return ((REAL *) (ptr))[polarindex + 4]; +} + +// Check or set a tetrahedron's attributes. + +inline REAL tetgenmesh::elemattribute(tetrahedron* ptr, int attnum) { + return ((REAL *) (ptr))[elemattribindex + attnum]; +} + +inline void tetgenmesh::setelemattribute(tetrahedron* ptr, int attnum, + REAL value) { + ((REAL *) (ptr))[elemattribindex + attnum] = value; +} + +// Check or set a tetrahedron's maximum volume bound. + +inline REAL tetgenmesh::volumebound(tetrahedron* ptr) { + return ((REAL *) (ptr))[volumeboundindex]; +} + +inline void tetgenmesh::setvolumebound(tetrahedron* ptr, REAL value) { + ((REAL *) (ptr))[volumeboundindex] = value; +} + +// Get or set a tetrahedron's index (only used for output). +// These two routines use the reserved slot ptr[10]. + +inline int tetgenmesh::elemindex(tetrahedron* ptr) { + int *iptr = (int *) &(ptr[10]); + return iptr[0]; +} + +inline void tetgenmesh::setelemindex(tetrahedron* ptr, int value) { + int *iptr = (int *) &(ptr[10]); + iptr[0] = value; +} + +// Get or set a tetrahedron's marker. +// Set 'value = 0' cleans all the face/edge flags. + +inline int tetgenmesh::elemmarker(tetrahedron* ptr) { + return ((int *) (ptr))[elemmarkerindex]; +} + +inline void tetgenmesh::setelemmarker(tetrahedron* ptr, int value) { + ((int *) (ptr))[elemmarkerindex] = value; +} + +// infect(), infected(), uninfect() -- primitives to flag or unflag a +// tetrahedron. The last bit of the element marker is flagged (1) +// or unflagged (0). + +inline void tetgenmesh::infect(triface& t) { + ((int *) (t.tet))[elemmarkerindex] |= 1; +} + +inline void tetgenmesh::uninfect(triface& t) { + ((int *) (t.tet))[elemmarkerindex] &= ~1; +} + +inline bool tetgenmesh::infected(triface& t) { + return (((int *) (t.tet))[elemmarkerindex] & 1) != 0; +} + +// marktest(), marktested(), unmarktest() -- primitives to flag or unflag a +// tetrahedron. Use the second lowerest bit of the element marker. + +inline void tetgenmesh::marktest(triface& t) { + ((int *) (t.tet))[elemmarkerindex] |= 2; +} + +inline void tetgenmesh::unmarktest(triface& t) { + ((int *) (t.tet))[elemmarkerindex] &= ~2; +} + +inline bool tetgenmesh::marktested(triface& t) { + return (((int *) (t.tet))[elemmarkerindex] & 2) != 0; +} + +// markface(), unmarkface(), facemarked() -- primitives to flag or unflag a +// face of a tetrahedron. From the last 3rd to 6th bits are used for +// face markers, e.g., the last third bit corresponds to loc = 0. + +inline void tetgenmesh::markface(triface& t) { + ((int *) (t.tet))[elemmarkerindex] |= (4 << (t.ver & 3)); +} + +inline void tetgenmesh::unmarkface(triface& t) { + ((int *) (t.tet))[elemmarkerindex] &= ~(4 << (t.ver & 3)); +} + +inline bool tetgenmesh::facemarked(triface& t) { + return (((int *) (t.tet))[elemmarkerindex] & (4 << (t.ver & 3))) != 0; +} + +// markedge(), unmarkedge(), edgemarked() -- primitives to flag or unflag an +// edge of a tetrahedron. From the last 7th to 12th bits are used for +// edge markers, e.g., the last 7th bit corresponds to the 0th edge, etc. +// Remark: The last 7th bit is marked by 2^6 = 64. + +inline void tetgenmesh::markedge(triface& t) { + ((int *) (t.tet))[elemmarkerindex] |= (int) (64 << ver2edge[(t).ver]); +} + +inline void tetgenmesh::unmarkedge(triface& t) { + ((int *) (t.tet))[elemmarkerindex] &= ~(int) (64 << ver2edge[(t).ver]); +} + +inline bool tetgenmesh::edgemarked(triface& t) { + return (((int *) (t.tet))[elemmarkerindex] & + (int) (64 << ver2edge[(t).ver])) != 0; +} + +// marktest2(), unmarktest2(), marktest2ed() -- primitives to flag and unflag +// a tetrahedron. The 13th bit (2^12 = 4096) is used for this flag. + +inline void tetgenmesh::marktest2(triface& t) { + ((int *) (t.tet))[elemmarkerindex] |= (int) (4096); +} + +inline void tetgenmesh::unmarktest2(triface& t) { + ((int *) (t.tet))[elemmarkerindex] &= ~(int) (4096); +} + +inline bool tetgenmesh::marktest2ed(triface& t) { + return (((int *) (t.tet))[elemmarkerindex] & (int) (4096)) != 0; +} + +// elemcounter(), setelemcounter() -- primitives to read or ser a (small) +// integer counter in this tet. It is saved from the 16th bit. On 32 bit +// system, the range of the counter is [0, 2^15 = 32768]. + +inline int tetgenmesh::elemcounter(triface& t) { + return (((int *) (t.tet))[elemmarkerindex]) >> 16; +} + +inline void tetgenmesh::setelemcounter(triface& t, int value) { + int c = ((int *) (t.tet))[elemmarkerindex]; + // Clear the old counter while keep the other flags. + c &= 65535; // sum_{i=0^15} 2^i + c |= (value << 16); + ((int *) (t.tet))[elemmarkerindex] = c; +} + +inline void tetgenmesh::increaseelemcounter(triface& t) { + int c = elemcounter(t); + setelemcounter(t, c + 1); +} + +inline void tetgenmesh::decreaseelemcounter(triface& t) { + int c = elemcounter(t); + setelemcounter(t, c - 1); +} + +// ishulltet() tests if t is a hull tetrahedron. + +inline bool tetgenmesh::ishulltet(triface& t) { + return (point) (t).tet[7] == dummypoint; +} + +// isdeadtet() tests if t is a tetrahedron is dead. + +inline bool tetgenmesh::isdeadtet(triface& t) { + return ((t.tet == NULL) || (t.tet[4] == NULL)); +} + +//============================================================================// +// // +// Primitives for subfaces and subsegments // +// // +//============================================================================// + +// Each subface contains three pointers to its neighboring subfaces, with +// edge versions. To save memory, both information are kept in a single +// pointer. To make this possible, all subfaces are aligned to eight-byte +// boundaries, so that the last three bits of each pointer are zeros. An +// edge version (in the range 0 to 5) is compressed into the last three +// bits of each pointer by 'sencode()'. 'sdecode()' decodes a pointer, +// extracting an edge version and a pointer to the beginning of a subface. + +inline void tetgenmesh::sdecode(shellface sptr, face& s) { + s.shver = (int) ((uintptr_t) (sptr) & (uintptr_t) 7); + s.sh = (shellface *) ((uintptr_t) (sptr) ^ (uintptr_t) (s.shver)); +} + +inline tetgenmesh::shellface tetgenmesh::sencode(face& s) { + return (shellface) ((uintptr_t) s.sh | (uintptr_t) s.shver); +} + +inline tetgenmesh::shellface tetgenmesh::sencode2(shellface *sh, int shver) { + return (shellface) ((uintptr_t) sh | (uintptr_t) shver); +} + +// sbond() bonds two subfaces (s1) and (s2) together. s1 and s2 must refer +// to the same edge. No requirement is needed on their orientations. + +inline void tetgenmesh::sbond(face& s1, face& s2) +{ + s1.sh[s1.shver >> 1] = sencode(s2); + s2.sh[s2.shver >> 1] = sencode(s1); +} + +// sbond1() bonds s1 <== s2, i.e., after bonding, s1 is pointing to s2, +// but s2 is not pointing to s1. s1 and s2 must refer to the same edge. +// No requirement is needed on their orientations. + +inline void tetgenmesh::sbond1(face& s1, face& s2) +{ + s1.sh[s1.shver >> 1] = sencode(s2); +} + +// Dissolve a subface bond (from one side). Note that the other subface +// will still think it's connected to this subface. + +inline void tetgenmesh::sdissolve(face& s) +{ + s.sh[s.shver >> 1] = NULL; +} + +// spivot() finds the adjacent subface (s2) for a given subface (s1). +// s1 and s2 share at the same edge. + +inline void tetgenmesh::spivot(face& s1, face& s2) +{ + shellface sptr = s1.sh[s1.shver >> 1]; + sdecode(sptr, s2); +} + +inline void tetgenmesh::spivotself(face& s) +{ + shellface sptr = s.sh[s.shver >> 1]; + sdecode(sptr, s); +} + +// These primitives determine or set the origin, destination, or apex +// of a subface with respect to the edge version. + +inline tetgenmesh::point tetgenmesh::sorg(face& s) +{ + return (point) s.sh[sorgpivot[s.shver]]; +} + +inline tetgenmesh::point tetgenmesh::sdest(face& s) +{ + return (point) s.sh[sdestpivot[s.shver]]; +} + +inline tetgenmesh::point tetgenmesh::sapex(face& s) +{ + return (point) s.sh[sapexpivot[s.shver]]; +} + +inline void tetgenmesh::setsorg(face& s, point pointptr) +{ + s.sh[sorgpivot[s.shver]] = (shellface) pointptr; +} + +inline void tetgenmesh::setsdest(face& s, point pointptr) +{ + s.sh[sdestpivot[s.shver]] = (shellface) pointptr; +} + +inline void tetgenmesh::setsapex(face& s, point pointptr) +{ + s.sh[sapexpivot[s.shver]] = (shellface) pointptr; +} + +#define setshvertices(s, pa, pb, pc)\ + setsorg(s, pa);\ + setsdest(s, pb);\ + setsapex(s, pc) + +// sesym() reserves the direction of the lead edge. + +inline void tetgenmesh::sesym(face& s1, face& s2) +{ + s2.sh = s1.sh; + s2.shver = (s1.shver ^ 1); // Inverse the last bit. +} + +inline void tetgenmesh::sesymself(face& s) +{ + s.shver ^= 1; +} + +// senext() finds the next edge (counterclockwise) in the same orientation +// of this face. + +inline void tetgenmesh::senext(face& s1, face& s2) +{ + s2.sh = s1.sh; + s2.shver = snextpivot[s1.shver]; +} + +inline void tetgenmesh::senextself(face& s) +{ + s.shver = snextpivot[s.shver]; +} + +inline void tetgenmesh::senext2(face& s1, face& s2) +{ + s2.sh = s1.sh; + s2.shver = snextpivot[snextpivot[s1.shver]]; +} + +inline void tetgenmesh::senext2self(face& s) +{ + s.shver = snextpivot[snextpivot[s.shver]]; +} + + +// Check or set a subface's maximum area bound. + +inline REAL tetgenmesh::areabound(face& s) +{ + return ((REAL *) (s.sh))[areaboundindex]; +} + +inline void tetgenmesh::setareabound(face& s, REAL value) +{ + ((REAL *) (s.sh))[areaboundindex] = value; +} + +// These two primitives read or set a shell marker. Shell markers are used +// to hold user boundary information. + +inline int tetgenmesh::shellmark(face& s) +{ + return ((int *) (s.sh))[shmarkindex]; +} + +inline void tetgenmesh::setshellmark(face& s, int value) +{ + ((int *) (s.sh))[shmarkindex] = value; +} + + + +// sinfect(), sinfected(), suninfect() -- primitives to flag or unflag a +// subface. The last bit of ((int *) ((s).sh))[shmarkindex+1] is flagged. + +inline void tetgenmesh::sinfect(face& s) +{ + ((int *) ((s).sh))[shmarkindex+1] = + (((int *) ((s).sh))[shmarkindex+1] | (int) 1); +} + +inline void tetgenmesh::suninfect(face& s) +{ + ((int *) ((s).sh))[shmarkindex+1] = + (((int *) ((s).sh))[shmarkindex+1] & ~(int) 1); +} + +// Test a subface for viral infection. + +inline bool tetgenmesh::sinfected(face& s) +{ + return (((int *) ((s).sh))[shmarkindex+1] & (int) 1) != 0; +} + +// smarktest(), smarktested(), sunmarktest() -- primitives to flag or unflag +// a subface. The last 2nd bit of the integer is flagged. + +inline void tetgenmesh::smarktest(face& s) +{ + ((int *) ((s).sh))[shmarkindex+1] = + (((int *)((s).sh))[shmarkindex+1] | (int) 2); +} + +inline void tetgenmesh::sunmarktest(face& s) +{ + ((int *) ((s).sh))[shmarkindex+1] = + (((int *)((s).sh))[shmarkindex+1] & ~(int)2); +} + +inline bool tetgenmesh::smarktested(face& s) +{ + return ((((int *) ((s).sh))[shmarkindex+1] & (int) 2) != 0); +} + +// smarktest2(), smarktest2ed(), sunmarktest2() -- primitives to flag or +// unflag a subface. The last 3rd bit of the integer is flagged. + +inline void tetgenmesh::smarktest2(face& s) +{ + ((int *) ((s).sh))[shmarkindex+1] = + (((int *)((s).sh))[shmarkindex+1] | (int) 4); +} + +inline void tetgenmesh::sunmarktest2(face& s) +{ + ((int *) ((s).sh))[shmarkindex+1] = + (((int *)((s).sh))[shmarkindex+1] & ~(int)4); +} + +inline bool tetgenmesh::smarktest2ed(face& s) +{ + return ((((int *) ((s).sh))[shmarkindex+1] & (int) 4) != 0); +} + +// The last 4th bit of ((int *) ((s).sh))[shmarkindex+1] is flagged. + +inline void tetgenmesh::smarktest3(face& s) +{ + ((int *) ((s).sh))[shmarkindex+1] = + (((int *)((s).sh))[shmarkindex+1] | (int) 8); +} + +inline void tetgenmesh::sunmarktest3(face& s) +{ + ((int *) ((s).sh))[shmarkindex+1] = + (((int *)((s).sh))[shmarkindex+1] & ~(int)8); +} + +inline bool tetgenmesh::smarktest3ed(face& s) +{ + return ((((int *) ((s).sh))[shmarkindex+1] & (int) 8) != 0); +} + + +// Each facet has a unique index (automatically indexed). Starting from '0'. +// We save this index in the same field of the shell type. + +inline void tetgenmesh::setfacetindex(face& s, int value) +{ + ((int *) (s.sh))[shmarkindex + 2] = value; +} + +inline int tetgenmesh::getfacetindex(face& s) +{ + return ((int *) (s.sh))[shmarkindex + 2]; +} + +// Tests if the subface (subsegment) s is dead. + +inline bool tetgenmesh::isdeadsh(face& s) { + return ((s.sh == NULL) || (s.sh[3] == NULL)); +} + +//============================================================================// +// // +// Primitives for interacting between tetrahedra and subfaces // +// // +//============================================================================// + +// tsbond() bond a tetrahedron (t) and a subface (s) together. +// Note that t and s must be the same face and the same edge. Moreover, +// t and s have the same orientation. +// Since the edge number in t and in s can be any number in {0,1,2}. We bond +// the edge in s which corresponds to t's 0th edge, and vice versa. + +inline void tetgenmesh::tsbond(triface& t, face& s) +{ + if ((t).tet[9] == NULL) { + // Allocate space for this tet. + (t).tet[9] = (tetrahedron) tet2subpool->alloc(); + // Initialize. + for (int i = 0; i < 4; i++) { + ((shellface *) (t).tet[9])[i] = NULL; + } + } + // Bond t <== s. + ((shellface *) (t).tet[9])[(t).ver & 3] = + sencode2((s).sh, tsbondtbl[t.ver][s.shver]); + // Bond s <== t. + s.sh[9 + ((s).shver & 1)] = + (shellface) encode2((t).tet, stbondtbl[t.ver][s.shver]); +} + +// tspivot() finds a subface (s) abutting on the given tetrahdera (t). +// Return s.sh = NULL if there is no subface at t. Otherwise, return +// the subface s, and s and t must be at the same edge wth the same +// orientation. + +inline void tetgenmesh::tspivot(triface& t, face& s) +{ + if ((t).tet[9] == NULL) { + (s).sh = NULL; + return; + } + // Get the attached subface s. + sdecode(((shellface *) (t).tet[9])[(t).ver & 3], (s)); + (s).shver = tspivottbl[t.ver][s.shver]; +} + +// Quickly check if the handle (t, v) is a subface. +#define issubface(t) \ + ((t).tet[9] && ((t).tet[9])[(t).ver & 3]) + +// stpivot() finds a tetrahedron (t) abutting a given subface (s). +// Return the t (if it exists) with the same edge and the same +// orientation of s. + +inline void tetgenmesh::stpivot(face& s, triface& t) +{ + decode((tetrahedron) s.sh[9 + (s.shver & 1)], t); + if ((t).tet == NULL) { + return; + } + (t).ver = stpivottbl[t.ver][s.shver]; +} + +// Quickly check if this subface is attached to a tetrahedron. + +#define isshtet(s) \ + ((s).sh[9 + ((s).shver & 1)]) + +// tsdissolve() dissolve a bond (from the tetrahedron side). + +inline void tetgenmesh::tsdissolve(triface& t) +{ + if ((t).tet[9] != NULL) { + ((shellface *) (t).tet[9])[(t).ver & 3] = NULL; + } +} + +// stdissolve() dissolve a bond (from the subface side). + +inline void tetgenmesh::stdissolve(face& s) +{ + (s).sh[9] = NULL; + (s).sh[10] = NULL; +} + +//============================================================================// +// // +// Primitives for interacting between subfaces and segments // +// // +//============================================================================// + +// ssbond() bond a subface to a subsegment. + +inline void tetgenmesh::ssbond(face& s, face& edge) +{ + s.sh[6 + (s.shver >> 1)] = sencode(edge); + edge.sh[0] = sencode(s); +} + +inline void tetgenmesh::ssbond1(face& s, face& edge) +{ + s.sh[6 + (s.shver >> 1)] = sencode(edge); + //edge.sh[0] = sencode(s); +} + +// ssdisolve() dissolve a bond (from the subface side) + +inline void tetgenmesh::ssdissolve(face& s) +{ + s.sh[6 + (s.shver >> 1)] = NULL; +} + +// sspivot() finds a subsegment abutting a subface. + +inline void tetgenmesh::sspivot(face& s, face& edge) +{ + sdecode((shellface) s.sh[6 + (s.shver >> 1)], edge); +} + +// Quickly check if the edge is a subsegment. + +#define isshsubseg(s) \ + ((s).sh[6 + ((s).shver >> 1)]) + +//============================================================================// +// // +// Primitives for interacting between tetrahedra and segments // +// // +//============================================================================// + +inline void tetgenmesh::tssbond1(triface& t, face& s) +{ + if ((t).tet[8] == NULL) { + // Allocate space for this tet. + (t).tet[8] = (tetrahedron) tet2segpool->alloc(); + // Initialization. + for (int i = 0; i < 6; i++) { + ((shellface *) (t).tet[8])[i] = NULL; + } + } + ((shellface *) (t).tet[8])[ver2edge[(t).ver]] = sencode((s)); +} + +inline void tetgenmesh::sstbond1(face& s, triface& t) +{ + ((tetrahedron *) (s).sh)[9] = encode(t); +} + +inline void tetgenmesh::tssdissolve1(triface& t) +{ + if ((t).tet[8] != NULL) { + ((shellface *) (t).tet[8])[ver2edge[(t).ver]] = NULL; + } +} + +inline void tetgenmesh::sstdissolve1(face& s) +{ + ((tetrahedron *) (s).sh)[9] = NULL; +} + +inline void tetgenmesh::tsspivot1(triface& t, face& s) +{ + if ((t).tet[8] != NULL) { + sdecode(((shellface *) (t).tet[8])[ver2edge[(t).ver]], s); + } else { + (s).sh = NULL; + } +} + +// Quickly check whether 't' is a segment or not. + +#define issubseg(t) \ + ((t).tet[8] && ((t).tet[8])[ver2edge[(t).ver]]) + +inline void tetgenmesh::sstpivot1(face& s, triface& t) +{ + decode((tetrahedron) s.sh[9], t); +} + +//============================================================================// +// // +// Primitives for points // +// // +//============================================================================// + +inline int tetgenmesh::pointmark(point pt) { + return ((int *) (pt))[pointmarkindex]; +} + +inline void tetgenmesh::setpointmark(point pt, int value) { + ((int *) (pt))[pointmarkindex] = value; +} + + +// These two primitives set and read the type of the point. + +inline enum tetgenmesh::verttype tetgenmesh::pointtype(point pt) { + return (enum verttype) (((int *) (pt))[pointmarkindex + 1] >> (int) 8); +} + +inline void tetgenmesh::setpointtype(point pt, enum verttype value) { + ((int *) (pt))[pointmarkindex + 1] = + ((int) value << 8) + (((int *) (pt))[pointmarkindex + 1] & (int) 255); +} + +// pinfect(), puninfect(), pinfected() -- primitives to flag or unflag +// a point. The last bit of the integer '[pointindex+1]' is flagged. + +inline void tetgenmesh::pinfect(point pt) { + ((int *) (pt))[pointmarkindex + 1] |= (int) 1; +} + +inline void tetgenmesh::puninfect(point pt) { + ((int *) (pt))[pointmarkindex + 1] &= ~(int) 1; +} + +inline bool tetgenmesh::pinfected(point pt) { + return (((int *) (pt))[pointmarkindex + 1] & (int) 1) != 0; +} + +// pmarktest(), punmarktest(), pmarktested() -- more primitives to +// flag or unflag a point. + +inline void tetgenmesh::pmarktest(point pt) { + ((int *) (pt))[pointmarkindex + 1] |= (int) 2; +} + +inline void tetgenmesh::punmarktest(point pt) { + ((int *) (pt))[pointmarkindex + 1] &= ~(int) 2; +} + +inline bool tetgenmesh::pmarktested(point pt) { + return (((int *) (pt))[pointmarkindex + 1] & (int) 2) != 0; +} + +inline void tetgenmesh::pmarktest2(point pt) { + ((int *) (pt))[pointmarkindex + 1] |= (int) 4; +} + +inline void tetgenmesh::punmarktest2(point pt) { + ((int *) (pt))[pointmarkindex + 1] &= ~(int) 4; +} + +inline bool tetgenmesh::pmarktest2ed(point pt) { + return (((int *) (pt))[pointmarkindex + 1] & (int) 4) != 0; +} + +inline void tetgenmesh::pmarktest3(point pt) { + ((int *) (pt))[pointmarkindex + 1] |= (int) 8; +} + +inline void tetgenmesh::punmarktest3(point pt) { + ((int *) (pt))[pointmarkindex + 1] &= ~(int) 8; +} + +inline bool tetgenmesh::pmarktest3ed(point pt) { + return (((int *) (pt))[pointmarkindex + 1] & (int) 8) != 0; +} + +// Read and set the geometry tag of the point (used by -s option). + +inline int tetgenmesh::pointgeomtag(point pt) { + return ((int *) (pt))[pointmarkindex + 2]; +} + +inline void tetgenmesh::setpointgeomtag(point pt, int value) { + ((int *) (pt))[pointmarkindex + 2] = value; +} + +// Read and set the u,v coordinates of the point (used by -s option). + +inline REAL tetgenmesh::pointgeomuv(point pt, int i) { + return pt[pointparamindex + i]; +} + +inline void tetgenmesh::setpointgeomuv(point pt, int i, REAL value) { + pt[pointparamindex + i] = value; +} + + + +// These following primitives set and read a pointer to a tetrahedron +// a subface/subsegment, a point, or a tet of background mesh. + +inline tetgenmesh::tetrahedron tetgenmesh::point2tet(point pt) { + return ((tetrahedron *) (pt))[point2simindex]; +} + +inline void tetgenmesh::setpoint2tet(point pt, tetrahedron value) { + ((tetrahedron *) (pt))[point2simindex] = value; +} + +inline tetgenmesh::point tetgenmesh::point2ppt(point pt) { + return (point) ((tetrahedron *) (pt))[point2simindex + 1]; +} + +inline void tetgenmesh::setpoint2ppt(point pt, point value) { + ((tetrahedron *) (pt))[point2simindex + 1] = (tetrahedron) value; +} + +inline tetgenmesh::shellface tetgenmesh::point2sh(point pt) { + return (shellface) ((tetrahedron *) (pt))[point2simindex + 2]; +} + +inline void tetgenmesh::setpoint2sh(point pt, shellface value) { + ((tetrahedron *) (pt))[point2simindex + 2] = (tetrahedron) value; +} + + +inline tetgenmesh::tetrahedron tetgenmesh::point2bgmtet(point pt) { + return ((tetrahedron *) (pt))[point2simindex + 3]; +} + +inline void tetgenmesh::setpoint2bgmtet(point pt, tetrahedron value) { + ((tetrahedron *) (pt))[point2simindex + 3] = value; +} + + +// The primitives for saving and getting the insertion radius. +inline void tetgenmesh::setpointinsradius(point pt, REAL value) +{ + pt[pointinsradiusindex] = value; +} + +inline REAL tetgenmesh::getpointinsradius(point pt) +{ + return pt[pointinsradiusindex]; +} + +inline bool tetgenmesh::issteinerpoint(point pt) { + return (pointtype(pt) == FREESEGVERTEX) || (pointtype(pt) == FREEFACETVERTEX) + || (pointtype(pt) == FREEVOLVERTEX); +} + +// point2tetorg() Get the tetrahedron whose origin is the point. + +inline void tetgenmesh::point2tetorg(point pa, triface& searchtet) +{ + decode(point2tet(pa), searchtet); + if ((point) searchtet.tet[4] == pa) { + searchtet.ver = 11; + } else if ((point) searchtet.tet[5] == pa) { + searchtet.ver = 3; + } else if ((point) searchtet.tet[6] == pa) { + searchtet.ver = 7; + } else { + searchtet.ver = 0; + } +} + +// point2shorg() Get the subface/segment whose origin is the point. + +inline void tetgenmesh::point2shorg(point pa, face& searchsh) +{ + sdecode(point2sh(pa), searchsh); + if ((point) searchsh.sh[3] == pa) { + searchsh.shver = 0; + } else if ((point) searchsh.sh[4] == pa) { + searchsh.shver = (searchsh.sh[5] != NULL ? 2 : 1); + } else { + searchsh.shver = 4; + } +} + +// farsorg() Return the origin of the subsegment. +// farsdest() Return the destination of the subsegment. + +inline tetgenmesh::point tetgenmesh::farsorg(face& s) +{ + face travesh, neighsh; + + travesh = s; + while (1) { + senext2(travesh, neighsh); + spivotself(neighsh); + if (neighsh.sh == NULL) break; + if (sorg(neighsh) != sorg(travesh)) sesymself(neighsh); + senext2(neighsh, travesh); + } + return sorg(travesh); +} + +inline tetgenmesh::point tetgenmesh::farsdest(face& s) +{ + face travesh, neighsh; + + travesh = s; + while (1) { + senext(travesh, neighsh); + spivotself(neighsh); + if (neighsh.sh == NULL) break; + if (sdest(neighsh) != sdest(travesh)) sesymself(neighsh); + senext(neighsh, travesh); + } + return sdest(travesh); +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// Linear algebra operators. // +// // +/////////////////////////////////////////////////////////////////////////////// + +// dot() returns the dot product: v1 dot v2. +inline REAL tetgenmesh::dot(REAL* v1, REAL* v2) +{ + return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]; +} + +// cross() computes the cross product: n = v1 cross v2. +inline void tetgenmesh::cross(REAL* v1, REAL* v2, REAL* n) +{ + n[0] = v1[1] * v2[2] - v2[1] * v1[2]; + n[1] = -(v1[0] * v2[2] - v2[0] * v1[2]); + n[2] = v1[0] * v2[1] - v2[0] * v1[1]; +} + +// distance() computes the Euclidean distance between two points. +inline REAL tetgenmesh::distance(REAL* p1, REAL* p2) +{ + return sqrt((p2[0] - p1[0]) * (p2[0] - p1[0]) + + (p2[1] - p1[1]) * (p2[1] - p1[1]) + + (p2[2] - p1[2]) * (p2[2] - p1[2])); +} + +inline REAL tetgenmesh::distance2(REAL* p1, REAL* p2) +{ + return norm2(p2[0] - p1[0], p2[1] - p1[1], p2[2] - p1[2]); +} + +inline REAL tetgenmesh::norm2(REAL x, REAL y, REAL z) +{ + return (x) * (x) + (y) * (y) + (z) * (z); +} + + + +#endif // #ifndef tetgenH + From e1233707d27be25940acbadd04e9f185655238c3 Mon Sep 17 00:00:00 2001 From: arlauwer Date: Sat, 6 Jan 2024 22:36:37 +0100 Subject: [PATCH 12/51] refactored to Tetrahedron.cpp added TetraMeshSpatialGrid options added NeedsSubdivide as tetgen unsuitable --- SKIRT/core/TetraMeshSnapshot.cpp | 610 +-- SKIRT/core/TetraMeshSnapshot.hpp | 107 +- SKIRT/core/TetraMeshSpatialGrid.cpp | 215 +- SKIRT/core/TetraMeshSpatialGrid.hpp | 79 +- SKIRT/core/Tetrahedron.cpp | 214 + SKIRT/core/Tetrahedron.hpp | 61 + SKIRT/tetgen/CMakeLists.txt | 3 +- SKIRT/tetgen/tetgen.cxx | 2 +- SKIRT/tetgen/tetgen.h | 5677 ++++++++++++++------------- 9 files changed, 3595 insertions(+), 3373 deletions(-) create mode 100644 SKIRT/core/Tetrahedron.cpp create mode 100644 SKIRT/core/Tetrahedron.hpp diff --git a/SKIRT/core/TetraMeshSnapshot.cpp b/SKIRT/core/TetraMeshSnapshot.cpp index 79ad046f..b4ba21a3 100644 --- a/SKIRT/core/TetraMeshSnapshot.cpp +++ b/SKIRT/core/TetraMeshSnapshot.cpp @@ -7,12 +7,14 @@ #include "EntityCollection.hpp" #include "FatalError.hpp" #include "Log.hpp" +#include "MediumSystem.hpp" #include "NR.hpp" #include "Parallel.hpp" #include "ParallelFactory.hpp" #include "PathSegmentGenerator.hpp" #include "ProcessManager.hpp" #include "Random.hpp" +#include "SimulationItem.hpp" #include "SiteListInterface.hpp" #include "SpatialGridPath.hpp" #include "SpatialGridPlotFile.hpp" @@ -84,252 +86,6 @@ namespace //////////////////////////////////////////////////////////////////// -// class to hold the information about a Tetra cell that is relevant for calculating paths and densities -class TetraMeshSnapshot::Site -{ -public: - Vec _r; // site position - vector _neighbors; // list of neighbor indices in _cells vector - Array _properties; // user-defined properties, if any - -public: - // constructor stores the specified site position; the other data members are set to zero or empty - Site(Vec r) : _r(r) {} - - // constructor derives the site position from the first three property values and stores the user properties; - // the other data members are set to zero or empty - Site(const Array& prop) : _r(prop[0], prop[1], prop[2]), _properties{prop} {} // WIP - - // adjusts the site position with the specified offset - void relax(double cx, double cy, double cz) { _r += Vec(cx, cy, cz); } - - // initializes the receiver with information taken from the specified fully computed Tetra cell - void init(voro::voronoicell_neighbor& cell) { cell.neighbors(_neighbors); } - - // clears the information taken from a Tetra cell so it can be reinitialized - void clear() { _neighbors.clear(); } - - // returns the cell's site position - Vec position() const { return _r; } - - // returns the x coordinate of the cell's site position - double x() const { return _r.x(); } - - // returns the squared distance from the cell's site to the specified point - double squaredDistanceTo(Vec r) const { return (r - _r).norm2(); } - - // returns a list of neighboring cell/site ids - const vector& neighbors() { return _neighbors; } - - // returns the cell/site user properties, if any - const Array& properties() { return _properties; } - - // writes the Voronoi cell geometry to the serialized data buffer, preceded by the specified cell index, - // if the cell geometry has been calculated for this cell; otherwise does nothing - void writeGeometryIfPresent(SerializedWrite& wdata, int m) - { - if (!_neighbors.empty()) - { - wdata.write(m); - wdata.write(_neighbors); - } - } - - // reads the Voronoi cell geometry from the serialized data buffer - void readGeometry(SerializedRead& rdata) { rdata.read(_neighbors); } -}; - -//////////////////////////////////////////////////////////////////// - -TetraMeshSnapshot::Plucker::Plucker() {} - -//////////////////////////////////////////////////////////////////// - -TetraMeshSnapshot::Plucker::Plucker(const Vec& pos, const Vec& dir) : U(dir), V(Vec::cross(dir, pos)) {} - -//////////////////////////////////////////////////////////////////// - -inline double TetraMeshSnapshot::Plucker::dot(const Plucker& a, const Plucker& b) -{ - return Vec::dot(a.U, b.V) + Vec::dot(b.U, a.V); -} - -//////////////////////////////////////////////////////////////////// - -TetraMeshSnapshot::Edge::Edge(int i1, int i2, const Vec* v1, const Vec* v2) : Plucker(*v1, *v2 - *v1), i1(i1), i2(i2) {} - -//////////////////////////////////////////////////////////////////// - -TetraMeshSnapshot::Tetra::Tetra(const std::array& vertices, const std::array& indices, - const std::array& neighbors, const std::array& edges) - : _vertices(vertices), _indices(indices), _neighbors(neighbors), _edges(edges) -{ - double xmin = DBL_MAX; - double ymin = DBL_MAX; - double zmin = DBL_MAX; - double xmax = -DBL_MAX; - double ymax = -DBL_MAX; - double zmax = -DBL_MAX; - for (const Vec* vertex : _vertices) - { - xmin = min(xmin, vertex->x()); - ymin = min(ymin, vertex->y()); - zmin = min(zmin, vertex->z()); - xmax = max(xmax, vertex->x()); - ymax = max(ymax, vertex->y()); - zmax = max(zmax, vertex->z()); - } - setExtent(Box(xmin, ymin, zmin, xmax, ymax, zmax)); - - _volume = 1 / 6. - * abs(Vec::dot(Vec::cross(*_vertices[1] - *_vertices[0], *_vertices[2] - *_vertices[0]), - *_vertices[3] - *_vertices[0])); - - const Vec e01 = *_vertices[1] - *_vertices[0]; - const Vec e02 = *_vertices[2] - *_vertices[0]; - const Vec e03 = *_vertices[3] - *_vertices[0]; - // this convention makes edges go clockwise around leaving rays from inside the tetrahedron - // so their plucker products are all positive if the ray leaves - double orientation = Vec::dot(Vec::cross(e01, e02), e03); - if (orientation < 0) - { - std::cout << "ORIENTATION SWITCHED!!!!!!!!!" << std::endl; - // swap last 2, this means first 2 indices can be ordered i < j - std::swap(_vertices[2], _vertices[3]); - std::swap(_neighbors[2], _neighbors[3]); - std::swap(_indices[2], _indices[3]); - } -} - -//////////////////////////////////////////////////////////////////// - -double TetraMeshSnapshot::Tetra::getProd(const Plucker& ray, int t1, int t2) const -{ - int e = (std::min(t1, t2) == 0) ? std::max(t1, t2) - 1 : t1 + t2; - Edge* edge = _edges[e]; - - // not the same order -> *-1 - // beginning of t1 == beginning of edge (= same order) - return (_indices[t1] == edge->i1 ? 1 : -1) * Plucker::dot(ray, *edge); -} - -//////////////////////////////////////////////////////////////////// - -bool TetraMeshSnapshot::Tetra::intersects(std::array& barycoords, const Plucker& ray, int face, - bool leaving) const -{ - std::array t = counterclockwiseVertices(face); - - double sum = 0; - for (int i = 0; i < 3; i++) - { - // edges: 12, 20, 01 - // verts: 0, 1, 2 - double prod = getProd(ray, t[(i + 1) % 3], t[(i + 2) % 3]); - if (leaving != (prod >= 0)) return false; // change this so for both leavig and entering prod=0 works - barycoords[i] = prod; - sum += prod; - } - for (int i = 0; i < 3; i++) barycoords[i] /= sum; - - return true; -} - -//////////////////////////////////////////////////////////////////// - -bool TetraMeshSnapshot::Tetra::inside(const Position& bfr) const -{ - if (!Box::contains(bfr)) return false; - - /* - face: normals for which the other vertex has a positive dot product with - 3:*02 x 01*| 10 x 12 | 21 x 20 - 2: 13 x 10 |*01 x 03*| 30 x 31 - 1: 20 x 23 | 32 x 30 |*03 x 02* - 0: 31 x 32 | 23 x 21 |*12 x 13* // last one doesn't matter - */ - - // optimized version (probably not that much better) - Vec e0p = bfr - *_vertices[0]; - Vec e02 = *_vertices[2] - *_vertices[0]; - Vec e01 = *_vertices[1] - *_vertices[0]; - if (Vec::dot(Vec::cross(e02, e01), e0p) > 0) // 02 x 01 - return false; - - Vec e03 = *_vertices[3] - *_vertices[0]; - if (Vec::dot(Vec::cross(e01, e03), e0p) > 0) // 01 x 03 - return false; - - if (Vec::dot(Vec::cross(e03, e02), e0p) > 0) // 03 x 02 - return false; - - Vec e1p = bfr - *_vertices[1]; - Vec e12 = *_vertices[2] - *_vertices[1]; - Vec e13 = *_vertices[3] - *_vertices[1]; - return Vec::dot(Vec::cross(e12, e13), e1p) < 0; // 12 x 13 - - // checks 3 edges too many but very simple - // for (int face = 0; face < 4; face++) - // { - // std::array t = clockwiseVertices(face); - // Vec& v0 = *_vertices[t[0]]; - // Vec& clock = *_vertices[t[1]]; - // Vec& counter = *_vertices[t[2]]; - // Vec normal = Vec::cross(counter - v0, clock - v0); - // if (Vec::dot(normal, bfr - v0) < 0) // is pos on the same side as v3 - // { - // return false; - // } - // } - // return true; -} - -//////////////////////////////////////////////////////////////////// - -Vec TetraMeshSnapshot::Tetra::calcExit(const std::array& barycoords, int face) const -{ - std::array t = Tetra::counterclockwiseVertices(face); - Vec exit; - for (int i = 0; i < 3; i++) exit += *_vertices[t[i]] * barycoords[i]; - return exit; -} - -//////////////////////////////////////////////////////////////////// - -Position TetraMeshSnapshot::Tetra::position() const -{ - Position pos; - for (int i = 0; i < 4; i++) pos += *_vertices[i]; - pos /= 4; - return pos; -} - -//////////////////////////////////////////////////////////////////// - -double TetraMeshSnapshot::Tetra::volume() const -{ - return _volume; -} - -//////////////////////////////////////////////////////////////////// - -const Array& TetraMeshSnapshot::Tetra::properties() -{ - return _properties; -} - -//////////////////////////////////////////////////////////////////// - -std::array TetraMeshSnapshot::Tetra::counterclockwiseVertices(int face) -{ - std::array t = {(face + 1) % 4, (face + 2) % 4, (face + 3) % 4}; - // if face is even we should swap two edges - if (face % 2 == 0) std::swap(t[0], t[2]); - return t; -} - -//////////////////////////////////////////////////////////////////// - namespace { bool lessthan(Vec p1, Vec p2, int axis) @@ -418,48 +174,48 @@ class TetraMeshSnapshot::Node Node* right() const { return _right; } // returns the apropriate child for the specified query point - Node* child(Vec bfr, const vector& sites) const + Node* child(Vec bfr, const vector& points) const { - return lessthan(bfr, sites[_m]->position(), _axis) ? _left : _right; + return lessthan(bfr, *points[_m], _axis) ? _left : _right; } // returns the other child than the one that would be apropriate for the specified query point - Node* otherChild(Vec bfr, const vector& sites) const + Node* otherChild(Vec bfr, const vector& points) const { - return lessthan(bfr, sites[_m]->position(), _axis) ? _right : _left; + return lessthan(bfr, *points[_m], _axis) ? _right : _left; } // returns the squared distance from the query point to the split plane - double squaredDistanceToSplitPlane(Vec bfr, const vector& sites) const + double squaredDistanceToSplitPlane(Vec bfr, const vector& points) const { switch (_axis) { case 0: // split on x - return sqr(sites[_m]->position().x() - bfr.x()); + return sqr(points[_m]->x() - bfr.x()); case 1: // split on y - return sqr(sites[_m]->position().y() - bfr.y()); + return sqr(points[_m]->y() - bfr.y()); case 2: // split on z - return sqr(sites[_m]->position().z() - bfr.z()); + return sqr(points[_m]->z() - bfr.z()); default: // this should never happen return 0; } } // returns the node in this subtree that represents the site nearest to the query point - Node* nearest(Vec bfr, const vector& sites) + Node* nearest(Vec bfr, const vector& points) { // recursively descend the tree until a leaf node is reached, going left or right depending on // whether the specified point is less than or greater than the current node in the split dimension Node* current = this; - while (Node* child = current->child(bfr, sites)) current = child; + while (Node* child = current->child(bfr, points)) current = child; // unwind the recursion, looking for the nearest node while climbing up Node* best = current; - double bestSD = sites[best->m()]->squaredDistanceTo(bfr); + double bestSD = (*points[best->m()] - bfr).norm2(); while (true) { // if the current node is closer than the current best, then it becomes the current best - double currentSD = sites[current->m()]->squaredDistanceTo(bfr); + double currentSD = (*points[current->m()] - bfr).norm2(); if (currentSD < bestSD) { best = current; @@ -468,16 +224,16 @@ class TetraMeshSnapshot::Node // if there could be points on the other side of the splitting plane for the current node // that are closer to the search point than the current best, then ... - double splitSD = current->squaredDistanceToSplitPlane(bfr, sites); + double splitSD = current->squaredDistanceToSplitPlane(bfr, points); if (splitSD < bestSD) { // move down the other branch of the tree from the current node looking for closer points, // following the same recursive process as the entire search - Node* other = current->otherChild(bfr, sites); + Node* other = current->otherChild(bfr, points); if (other) { - Node* otherBest = other->nearest(bfr, sites); - double otherBestSD = sites[otherBest->m()]->squaredDistanceTo(bfr); + Node* otherBest = other->nearest(bfr, points); + double otherBestSD = (*points[otherBest->m()] - bfr).norm2(); if (otherBestSD < bestSD) { best = otherBest; @@ -502,8 +258,8 @@ TetraMeshSnapshot::TetraMeshSnapshot() {} TetraMeshSnapshot::~TetraMeshSnapshot() { - for (auto cell : _sites) delete cell; - for (auto tetra : _tetrahedra) delete tetra; + for (auto cell : _vertices) delete cell; + for (auto tetra : _tetrahedra) delete tetra; // WIP FIX THIS for (auto tree : _blocktrees) delete tree; } @@ -517,55 +273,13 @@ void TetraMeshSnapshot::setExtent(const Box& extent) //////////////////////////////////////////////////////////////////// -TetraMeshSnapshot::TetraMeshSnapshot(const SimulationItem* item, const Box& extent, string filename, bool relax) -{ - // read the input file - TextInFile in(item, filename, "Tetra sites"); - in.addColumn("position x", "length", "pc"); - in.addColumn("position y", "length", "pc"); - in.addColumn("position z", "length", "pc"); - Array coords; - while (in.readRow(coords)) _sites.push_back(new Site(Vec(coords[0], coords[1], coords[2]))); - in.close(); - - // calculate the Tetra cells - setContext(item); - setExtent(extent); - buildMesh(relax); - // buildSearchPerBlock(); -} - -//////////////////////////////////////////////////////////////////// - -TetraMeshSnapshot::TetraMeshSnapshot(const SimulationItem* item, const Box& extent, SiteListInterface* sli, bool relax) +TetraMeshSnapshot::TetraMeshSnapshot(const SimulationItem* item, const Box& extent, const TetraMeshSpatialGrid& grid) { - // prepare the data - int n = sli->numSites(); - _sites.resize(n); - for (int m = 0; m != n; ++m) _sites[m] = new Site(sli->sitePosition(m)); - - // calculate the Tetra cells + // item will also contain the policies WIP setContext(item); setExtent(extent); - buildMesh(relax); - // buildSearchPerBlock(); -} - -//////////////////////////////////////////////////////////////////// - -TetraMeshSnapshot::TetraMeshSnapshot(const SimulationItem* item, const Box& extent, const vector& sites, - bool relax) -{ - // prepare the data - int n = sites.size(); - _sites.resize(n); - for (int m = 0; m != n; ++m) _sites[m] = new Site(sites[m]); - - // calculate the Tetra cells - setContext(item); - setExtent(extent); - buildMesh(relax); - // buildSearchPerBlock(); + buildMesh(grid); + buildSearchPerBlock(); } //////////////////////////////////////////////////////////////////// @@ -599,17 +313,20 @@ namespace //////////////////////////////////////////////////////////////////// -void TetraMeshSnapshot::buildMesh(bool relax) +void TetraMeshSnapshot::buildMesh(const TetraMeshSpatialGrid& grid) { tetgenio in, out; tetgenio::facet* f; tetgenio::polygon* p; in.firstnumber = 0; - in.numberofpoints = 8; + in.numberofpoints = 8 + 0; in.pointlist = new REAL[in.numberofpoints * 3]; - // _extent = Box(-1, -1, -1, 1, 1, 1); + // in.tetunsuitable = &grid.needsSubdivide; + in.tetunsuitable = [&grid](double* pa, double* pb, double* pc, double* pd, double vol) { + return grid.needsSubdivide(pa, pb, pc, pd, vol); + }; // bottom half (zmin) in.pointlist[0] = _extent.xmin(); @@ -631,11 +348,32 @@ void TetraMeshSnapshot::buildMesh(bool relax) // top half (zmax) for (int i = 0; i < 4; i++) { + // x, y the same but z = zmax in.pointlist[12 + i * 3 + 0] = in.pointlist[i * 3 + 0]; in.pointlist[12 + i * 3 + 1] = in.pointlist[i * 3 + 1]; in.pointlist[12 + i * 3 + 2] = _extent.zmax(); } + /* psc */ + // in.numberofpointattributes = 1; + // in.pointattributelist = new REAL[in.numberofpoints]; + // for (int i = 0; i < 8; i++) + // { + // in.pointattributelist[i] = 1.; + // } + // for (int i = 8; i < in.numberofpoints; i++) + // { + // Vec pos = random()->position(_extent); + // in.pointlist[i * 3 + 0] = pos.x(); + // in.pointlist[i * 3 + 1] = pos.y(); + // in.pointlist[i * 3 + 2] = pos.z(); + // } + // for (int i = 8; i < in.numberofpoints; i++) + // { + // in.pointattributelist[i] = 1.; + // } + // in.pointattributelist[8] = 50.; + in.numberoffacets = 6; in.facetlist = new tetgenio::facet[in.numberoffacets]; addFacet(&in.facetlist[0], {0, 1, 2, 3}); // Facet 1. bottom @@ -646,18 +384,22 @@ void TetraMeshSnapshot::buildMesh(bool relax) addFacet(&in.facetlist[5], {3, 7, 4, 0}); // Facet 6. left tetgenbehavior behavior; - behavior.plc = 1; // -p PLC - behavior.quality = 1; // -q quality mesh - behavior.neighout = 2; // -nn neighbors and edges? - behavior.zeroindex = 1; // -z zero index - behavior.edgesout = 1; // -e edges + behavior.plc = 1; // -p PLC + behavior.quality = 1; // -q quality mesh + behavior.fixedvolume = 1; // -a max volume + behavior.neighout = 2; // -nn neighbors and edges? + behavior.zeroindex = 1; // -z zero index + behavior.edgesout = 1; // -e edges + // behavior.weighted = 1; // -w weighted // parameters - behavior.minratio = 2.0; // -q quality - behavior.maxvolume = 0.9; // -a max volume + // behavior.minratio = 5.0; // -q quality + behavior.maxvolume = 0.05 * _extent.volume(); // -a max volume + // behavior.weighted_param = // behavior.mindihedral = 5.0; // -q/ minimal angle tetrahedralize(&behavior, &in, &out); + numTetra = out.numberoftetrahedra; numEdges = out.numberofedges; numVertices = out.numberofpoints; @@ -712,6 +454,11 @@ void TetraMeshSnapshot::buildMesh(bool relax) _tetrahedra[i] = new Tetra(vertices, indices, neighbors, edges); } + for (int i = 0; i < numTetra; i++) + { + _centroids.push_back(new Vec(_tetrahedra[i]->centroid())); + } + log()->info("number of vertices " + std::to_string(numVertices)); log()->info("number of edges " + std::to_string(numEdges)); log()->info("number of tetrahedra " + std::to_string(numTetra)); @@ -729,9 +476,8 @@ void TetraMeshSnapshot::calculateVolume() void TetraMeshSnapshot::calculateDensityAndMass() { // allocate vectors for mass and density - int numCells = _tetrahedra.size(); - _rhov.resize(numCells); - Array Mv(numCells); + _rhov.resize(numVertices); + Array Mv(numVertices); // get the maximum temperature, or zero of there is none double maxT = useTemperatureCutoff() ? maxTemperature() : 0.; @@ -743,7 +489,7 @@ void TetraMeshSnapshot::calculateDensityAndMass() // loop over all sites/cells int numIgnored = 0; - for (int m = 0; m != numCells; ++m) + for (int m = 0; m != numVertices; ++m) { const Array& prop = _tetrahedra[m]->properties(); @@ -780,7 +526,7 @@ void TetraMeshSnapshot::calculateDensityAndMass() _mass = totalEffectiveMass; // construct a vector with the normalized cumulative site densities - if (numCells) NR::cdf(_cumrhov, Mv); + if (numVertices) NR::cdf(_cumrhov, Mv); } //////////////////////////////////////////////////////////////////// @@ -793,7 +539,7 @@ TetraMeshSnapshot::Node* TetraMeshSnapshot::buildTree(vector::iterator firs { auto median = length >> 1; std::nth_element(first, first + median, last, [this, depth](int m1, int m2) { - return m1 != m2 && lessthan(_sites[m1]->position(), _sites[m2]->position(), depth % 3); + return m1 != m2 && lessthan(*_centroids[m1], *_centroids[m2], depth % 3); }); return new TetraMeshSnapshot::Node(*(first + median), depth, buildTree(first, first + median, depth + 1), buildTree(first + median + 1, last, depth + 1)); @@ -806,29 +552,34 @@ TetraMeshSnapshot::Node* TetraMeshSnapshot::buildTree(vector::iterator firs void TetraMeshSnapshot::buildSearchPerBlock() { // abort if there are no cells - int numTetra = _sites.size(); if (!numTetra) return; - log()->info("Building data structures to accelerate searching the Tetra tesselation"); + log()->info("Building data structures to accelerate searching the tetrahedralization"); // ------------- block lists ------------- + // _nb = max(3, min(250, static_cast(cbrt(numTetra)))); + _nb = 1; + _nb2 = _nb * _nb; + _nb3 = _nb * _nb * _nb; // initialize a vector of nb x nb x nb lists, each containing the cells overlapping a certain block in the domain _blocklists.resize(_nb3); - // add the cell object to the lists for all blocks it may overlap - int i1, j1, k1, i2, j2, k2; + // add the tetrahedron to the lists for all blocks it may overlap for (int m = 0; m != numTetra; ++m) { - auto& vert = _tetrahedra[m]->_vertices; - auto vert_minmax = - std::minmax_element(vert.begin(), vert.end(), [](Vec* a, Vec* b) { return a->norm2() < b->norm2(); }); - - _extent.cellIndices(i1, j1, k1, **vert_minmax.first - Vec(_eps, _eps, _eps), _nb, _nb, _nb); - _extent.cellIndices(i2, j2, k2, **vert_minmax.second + Vec(_eps, _eps, _eps), _nb, _nb, _nb); - for (int i = i1; i <= i2; i++) - for (int j = j1; j <= j2; j++) - for (int k = k1; k <= k2; k++) _blocklists[i * _nb2 + j * _nb + k].push_back(m); + std::set vertexBlockIndices; // block index for every vertex without duplicates + + for (int v = 0; v < 4; ++v) + { + int i, j, k; + _extent.cellIndices(i, j, k, *_tetrahedra[m]->_vertices[v], _nb, _nb, _nb); + + int blockIndex = i * _nb2 + j * _nb + k; + + if (vertexBlockIndices.insert(blockIndex).second) // if block index has not already been used + _blocklists[blockIndex].push_back(m); + } } // compile block list statistics @@ -874,34 +625,25 @@ void TetraMeshSnapshot::buildSearchPerBlock() void TetraMeshSnapshot::buildSearchSingle() { // log the number of sites - int numCells = _sites.size(); - log()->info(" Number of sites: " + std::to_string(numCells)); + log()->info(" Number of sites: " + std::to_string(numVertices)); // abort if there are no cells - if (!numCells) return; + if (!numVertices) return; // construct a single search tree on the site locations of all cells - log()->info("Building data structure to accelerate searching " + std::to_string(numCells) + " Voronoi sites"); + log()->info("Building data structure to accelerate searching " + std::to_string(numVertices) + " Voronoi sites"); _blocktrees.resize(1); - vector ids(numCells); - for (int m = 0; m != numCells; ++m) ids[m] = m; + vector ids(numVertices); + for (int m = 0; m != numVertices; ++m) ids[m] = m; _blocktrees[0] = buildTree(ids.begin(), ids.end(), 0); } //////////////////////////////////////////////////////////////////// -void TetraMeshSnapshot::writeGridPlotFiles(const SimulationItem* /*probe*/) const +void TetraMeshSnapshot::writeGridPlotFiles(const SimulationItem* probe) const { + ///////////////// TEMP std::ofstream outputFile("data/tetrahedra.txt"); - // outputFile << "vertices=" << _vertices.size() << "\n"; - // for (size_t i = 0; i < _vertices.size(); i++) - // { - // outputFile << "vertex=" << i << "\n"; - // Vec& r = *_vertices[i]; - // outputFile << r.x() << ", " << r.y() << ", " << r.z() << "\n"; - // outputFile << "\n"; - // } - for (size_t i = 0; i < _tetrahedra.size(); i++) { const Tetra* tetra = _tetrahedra[i]; @@ -910,22 +652,51 @@ void TetraMeshSnapshot::writeGridPlotFiles(const SimulationItem* /*probe*/) cons const Vec* r = tetra->_vertices[l]; outputFile << r->x() << ", " << r->y() << ", " << r->z() << "\n"; } - // outputFile << "neighbors="; - // for (size_t j = 0; j < 4; j++) - // { - // outputFile << " " << tetra->_neighbors[j]; - // if (j != 3) outputFile << ","; - // } - // outputFile << "\n"; } - // for (size_t i = 0; i < _tetrahedra.size(); i++) - // { - // outputFile << "circumsphere=" << i << "\n"; - // outputFile << centers[i].x() << "," << centers[i].y() << "," << centers[i].z() << "\n"; - // outputFile << radii[i] << "\n"; - // } - outputFile.close(); + ///////////////// + + // create the plot files + SpatialGridPlotFile plotxy(probe, probe->itemName() + "_grid_xy"); + SpatialGridPlotFile plotxz(probe, probe->itemName() + "_grid_xz"); + SpatialGridPlotFile plotyz(probe, probe->itemName() + "_grid_yz"); + SpatialGridPlotFile plotxyz(probe, probe->itemName() + "_grid_xyz"); + + // for each site, compute the corresponding cell and output its edges + log()->info("Writing plot files for tetrahedralization with " + std::to_string(numTetra) + " tetrahedra"); + log()->infoSetElapsed(numTetra); + int numDone = 0; + for (const Tetra* tetra : _tetrahedra) + { + vector coords; + coords.reserve(12); + vector indices; + indices.reserve(16); + + for (int v = 0; v < 4; v++) + { + const Vec* vertex = tetra->_vertices[v]; + coords.push_back(vertex->x()); + coords.push_back(vertex->y()); + coords.push_back(vertex->z()); + + // get vertices of opposite face + std::array faceIndices = tetra->clockwiseVertices(v); + indices.push_back(3); // amount of vertices per face + indices.push_back(faceIndices[0]); + indices.push_back(faceIndices[1]); + indices.push_back(faceIndices[2]); + } + + if (tetra->zmin() <= 0 && tetra->zmax() >= 0) plotxy.writePolyhedron(coords, indices); + if (tetra->ymin() <= 0 && tetra->ymax() >= 0) plotxz.writePolyhedron(coords, indices); + if (tetra->xmin() <= 0 && tetra->xmax() >= 0) plotyz.writePolyhedron(coords, indices); + if (numTetra <= 1000) plotxyz.writePolyhedron(coords, indices); + + // log message if the minimum time has elapsed + numDone++; + if (numDone % 2000 == 0) log()->infoIfElapsed("Computed tetrehedra: ", 2000); + } } //////////////////////////////////////////////////////////////////// @@ -946,7 +717,7 @@ int TetraMeshSnapshot::numEntities() const Position TetraMeshSnapshot::position(int m) const { - return _tetrahedra[m]->position(); + return Position(_tetrahedra[m]->centroid()); } //////////////////////////////////////////////////////////////////// @@ -989,34 +760,10 @@ double TetraMeshSnapshot::mass() const Position TetraMeshSnapshot::generatePosition(int m) const { - // https://vcg.isti.cnr.it/activities/OLD/geometryegraphics/pointintetraedro.html double s = random()->uniform(); double t = random()->uniform(); double u = random()->uniform(); - if (s + t > 1.0) - { // cut'n fold the cube into a prism - - s = 1.0 - s; - t = 1.0 - t; - } - if (t + u > 1.0) - { // cut'n fold the prism into a tetrahedron - - double tmp = u; - u = 1.0 - s - t; - t = 1.0 - tmp; - } - else if (s + t + u > 1.0) - { - - double tmp = u; - u = s + t + u - 1.0; - s = 1 - t - tmp; - } - double a = 1 - s - t - u; // a,s,t,u are the barycentric coordinates of the random point. - - const auto& vert = _tetrahedra[m]->_vertices; - return Position(*vert[0] * a + *vert[1] * s + *vert[2] * t + *vert[3] * u); + return _tetrahedra[m]->generatePosition(s, t, u); } //////////////////////////////////////////////////////////////////// @@ -1037,25 +784,50 @@ Position TetraMeshSnapshot::generatePosition() const int TetraMeshSnapshot::cellIndex(Position bfr) const { // make sure the position is inside the domain - // if (!_extent.contains(bfr)) return -1; + if (!_extent.contains(bfr)) return -1; // determine the block in which the point falls - // if we didn't build a Voronoi mesh, the search tree is always in the first "block" - // int i, j, k; - // _extent.cellIndices(i, j, k, bfr, _nb, _nb, _nb); - // int b = i * _nb2 + j * _nb + k; + int i, j, k; + _extent.cellIndices(i, j, k, bfr, _nb, _nb, _nb); + int b = i * _nb2 + j * _nb + k; // look for the closest site in this block, using the search tree if there is one - // Node* tree = _blocktrees[b]; - // int m = tree->nearest(bfr, _sites)->m(); - // find all edges that connect to m - // Site* site = _sites[m]; + Node* tree = _blocktrees[b]; + if (tree) + { + int m = tree->nearest(bfr, _centroids)->m(); + // find all neighboring tetrahedra + const Tetra* tetra = _tetrahedra[m]; + if (tetra->Tetra::inside(bfr)) return m; + for (int n = 0; n < 4; n++) + { + int neighbor = tetra->_neighbors[n]; + if (neighbor != -1 && _tetrahedra[neighbor]->Tetra::inside(bfr)) return neighbor; + } - // I can retreive all edges by looking at neighbors of Site - // i want tetra though so I'll build hashmap for Tetra instead of vector + // 2nd neighbors WIP + std::set second_neighbors; + for (int n = 0; n < 4; n++) + { + int neighbor = tetra->_neighbors[n]; + if (neighbor != -1) + { + for (int m = 0; m < 4; m++) + { + int second = _tetrahedra[neighbor]->_neighbors[m]; + if (second != -1) second_neighbors.insert(second); + } + } + } + for (int m : second_neighbors) + { + if (_tetrahedra[m]->Tetra::inside(bfr)) return m; + } + // + log()->warning("cellIndex failed to find using centroid!!!"); + } - // if there is no search tree, simply loop over the index list - // maybe use a k-d tree here to find nearest tetrahedra + // if no tree or tetrahedron was not found loop over all tetrahedra for (int i = 0; i < numTetra; i++) { const Tetra* tetra = _tetrahedra[i]; @@ -1068,7 +840,7 @@ int TetraMeshSnapshot::cellIndex(Position bfr) const const Array& TetraMeshSnapshot::properties(int m) const { - return _sites[m]->properties(); + // return _sites[m]->properties(); } //////////////////////////////////////////////////////////////////// @@ -1097,7 +869,7 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator public: MySegmentGenerator(const TetraMeshSnapshot* grid) : _grid(grid) {} -#define WRITE + // #define WRITE #ifdef WRITE std::ofstream out; @@ -1107,10 +879,7 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator if (!out.is_open()) out.open("data/photon.txt"); } - void stopwriting() - { - out.close(); - } + void stopwriting() { out.close(); } void write(const Vec& exit, double ds, int face) { @@ -1172,12 +941,14 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator } else { - std::array t = Tetra::counterclockwiseVertices(enteringFace); + std::array t = Tetra::clockwiseVertices(enteringFace); // 2 step decision tree prods[0] = tetra->getProd(ray, t[0], enteringFace); - bool clockwise0 = prods[0] > 0; - int i = clockwise0 ? 1 : 2; // if (counter)clockwise move (counter)clockwise + bool clockwise0 = prods[0] < 0; + // if clockwise move clockwise (t moves c but i moves cc) + // 1 is c and 2 is cc + int i = clockwise0 ? 1 : 2; prods[i] = tetra->getProd(ray, t[i], enteringFace); // if 2 clockwise: face=t0 @@ -1185,7 +956,7 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator // if clockwise then counter: face=t2 // if counter then clockwise: face=t1 - if (clockwise0 == (prods[i] > 0)) + if (clockwise0 == (prods[i] < 0)) leavingFace = t[0]; else if (clockwise0) leavingFace = t[2]; @@ -1247,6 +1018,8 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator if (state() == State::Outside) { + // everything below here is only useful if we use the convex hull + /* // outside the convex hull and inside extent if (wasInside && _grid->extent().contains(r())) { @@ -1309,6 +1082,7 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator } } } + */ } #ifdef WRITE stopwriting(); diff --git a/SKIRT/core/TetraMeshSnapshot.hpp b/SKIRT/core/TetraMeshSnapshot.hpp index 0856a80b..d0a87eaf 100644 --- a/SKIRT/core/TetraMeshSnapshot.hpp +++ b/SKIRT/core/TetraMeshSnapshot.hpp @@ -10,6 +10,9 @@ #include "Snapshot.hpp" #include #include "array" +#include "MediumSystem.hpp" +#include "TetraMeshSpatialGrid.hpp" +#include "Tetrahedron.hpp" class PathSegmentGenerator; class SiteListInterface; class SpatialGridPath; @@ -140,110 +143,13 @@ class TetraMeshSnapshot : public Snapshot //========== Specialty constructors ========== public: - /** This constructor reads the site positions from the specified text column file. The input - file must contain three columns specifying the x,y,z coordinates. The default unit is - parsec, which can be overridden by providing column header info in the file. The - constructor completes the configuration for the object (but without importing mass density - information or setting a mass density policy) and calls the private buildMesh() and - buildSearch() functions to create the relevant data structures. - - The \em item argument specifies a simulation item in the hierarchy of the caller (usually - the caller itself) used to retrieve context such as an appropriate logger. The \em extent - argument specifies the extent of the domain as a box lined up with the coordinate axes. - Sites located outside of the domain and sites that are too close to another site are - discarded. The \em filename argument specifies the name of the input file, including - filename extension but excluding path and simulation prefix. If the \em relax argument is - true, the function performs a single relaxation step on the site positions. */ - TetraMeshSnapshot(const SimulationItem* item, const Box& extent, string filename, bool relax); - - /** This constructor obtains the site positions from a SiteListInterface instance. The - constructor completes the configuration for the object (but without importing mass density - information or setting a mass density policy) and calls the private buildMesh() and - buildSearch() functions to create the relevant data structures. - - The \em item argument specifies a simulation item in the hierarchy of the caller (usually - the caller itself) used to retrieve context such as an appropriate logger. The \em extent - argument specifies the extent of the domain as a box lined up with the coordinate axes. - Sites located outside of the domain and sites that are too close to another site are - discarded. The \em sli argument specifies an object that provides the SiteListInterface - interface from which to obtain the site positions. If the \em relax argument is true, the - function performs a single relaxation step on the site positions. */ - TetraMeshSnapshot(const SimulationItem* item, const Box& extent, SiteListInterface* sli, bool relax); - - /** This constructor obtains the site positions from a programmatically prepared list. The - constructor completes the configuration for the object (but without importing mass density - information or setting a mass density policy) and calls the private buildMesh() and - buildSearch() functions to create the relevant data structures. - - The \em item argument specifies a simulation item in the hierarchy of the caller (usually - the caller itself) used to retrieve context such as an appropriate logger. The \em extent - argument specifies the extent of the domain as a box lined up with the coordinate axes. - Sites located outside of the domain and sites that are too close to another site are - discarded. The \em sites argument specifies the list of site positions. If the \em relax - argument is true, the function performs a single relaxation step on the site positions. */ - TetraMeshSnapshot(const SimulationItem* item, const Box& extent, const vector& sites, bool relax); + TetraMeshSnapshot(const SimulationItem* item, const Box& extent, const TetraMeshSpatialGrid& grid); //=========== Private construction ========== private: - /** Private class to hold the information about a Tetra cell that is relevant for calculating - paths and densities; see the buildMesh() function. */ - class Site; - class Node; - class Plucker - { - private: - Vec U, V; - - public: - Plucker(); - - Plucker(const Vec& pos, const Vec& dir); - - // permuted inner product - static inline double dot(const Plucker& a, const Plucker& b); - }; - - class Edge : public Plucker - { - public: - const int i1, i2; - Edge(int i1, int i2, const Vec* v1, const Vec* v2); - }; - - class Tetra : public Box - { - public: - std::array _vertices; - std::array _indices; - std::array _edges; - std::array _neighbors = {-1, -1, -1, -1}; - double _volume; - Array _properties; - - public: - Tetra(const std::array& vertices, const std::array& indices, - const std::array& neighbors, const std::array& edges); - - double getProd(const Plucker& ray, int t1, int t2) const; - - bool intersects(std::array& prods, const Plucker& ray, int face, bool leaving = true) const; - - bool inside(const Position& bfr) const; - - Vec calcExit(const std::array& barycoords, int face) const; - - Position position() const; - - double volume() const; - - const Array& properties(); - - static inline std::array counterclockwiseVertices(int face); - }; - /** Given a list of generating sites (represented as partially initialized Cell objects), this private function builds the Tetra tessellation and stores the corresponding cell information, including any properties relevant for supporting the @@ -261,7 +167,7 @@ class TetraMeshSnapshot : public Snapshot constructed with these adjusted site positions, which are distributed more uniformly, thereby avoiding overly elongated cells in the Tetra tessellation. Relaxation can be quite time-consuming because the Tetra tessellation must be constructed twice. */ - void buildMesh(bool relax); + void buildMesh(const TetraMeshSpatialGrid& grid); /** This private function calculates the volumes for all cells without using the Tetra mesh. It assumes that both mass and mass density columns are being imported. */ @@ -504,11 +410,10 @@ class TetraMeshSnapshot : public Snapshot int numVertices; // data members initialized when processing snapshot input and further completed by BuildMesh() - vector _sites; // cell objects, indexed on m - vector _tetrahedra; vector _edges; vector _vertices; + vector _centroids; // data members initialized when processing snapshot input, but only if a density policy has been set Array _rhov; // density for each cell (not normalized) diff --git a/SKIRT/core/TetraMeshSpatialGrid.cpp b/SKIRT/core/TetraMeshSpatialGrid.cpp index 56426963..b986a925 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.cpp +++ b/SKIRT/core/TetraMeshSpatialGrid.cpp @@ -4,6 +4,7 @@ ///////////////////////////////////////////////////////////////// */ #include "TetraMeshSpatialGrid.hpp" +#include "Configuration.hpp" #include "FatalError.hpp" #include "Log.hpp" #include "MediumSystem.hpp" @@ -54,94 +55,154 @@ void TetraMeshSpatialGrid::setupSelfBefore() { BoxSpatialGrid::setupSelfBefore(); - // determine an appropriate set of sites and construct the Tetra mesh - switch (_policy) + _random = find(); + _numSamples = find()->numDensitySamples(); + + const MediumSystem* ms = find(); + if (!ms) throw FATALERROR("MediumSystem not found"); + + for (Medium* medium : ms->media()) { - case Policy::Uniform: - { - auto random = find(); - vector rv(_numSites); - for (int m = 0; m != _numSites; ++m) rv[m] = random->position(extent()); - _mesh = new TetraMeshSnapshot(this, extent(), rv, _relaxSites); - break; - } - case Policy::CentralPeak: - { - auto random = find(); - const int a = 1000; // steepness of the peak; the central 1/a portion is NOT covered - const double rscale = extent().rmax().norm(); - vector rv(_numSites); - for (int m = 1; m != _numSites;) // skip first particle so that it remains (0,0,0) - { - double r = rscale * pow(1. / a, random->uniform()); // random distribution according to 1/x - Direction k = random->direction(); - Position p = Position(r, k); - if (extent().contains(p)) rv[m++] = p; // discard any points outside of the domain - } - _mesh = new TetraMeshSnapshot(this, extent(), rv, _relaxSites); - break; - } - case Policy::DustDensity: - { - // build a list of media that have this material type with corresponding weights - vector media; - vector weights; - auto ms = find(); - for (auto medium : ms->media()) - if (medium->mix()->isDust()) media.push_back(medium); - for (auto medium : media) weights.push_back(medium->mass()); - _mesh = - new TetraMeshSnapshot(this, extent(), sampleMedia(media, weights, extent(), _numSites), _relaxSites); - break; - } - case Policy::ElectronDensity: + medium->setup(); + switch (medium->mix()->materialType()) { - // build a list of media that have this material type with corresponding weights - vector media; - vector weights; - auto ms = find(); - for (auto medium : ms->media()) - if (medium->mix()->isElectrons()) media.push_back(medium); - for (auto medium : media) weights.push_back(medium->number()); - _mesh = - new TetraMeshSnapshot(this, extent(), sampleMedia(media, weights, extent(), _numSites), _relaxSites); - break; + case MaterialMix::MaterialType::Dust: _dustMedia.push_back(medium); break; + case MaterialMix::MaterialType::Electrons: _electronMedia.push_back(medium); break; + case MaterialMix::MaterialType::Gas: _gasMedia.push_back(medium); break; } - case Policy::GasDensity: + } + + if (!_dustMedia.empty()) + { + if (maxDustFraction() > 0) _hasAny = _hasDustAny = _hasDustFraction = true; + if (maxDustOpticalDepth() > 0) _hasAny = _hasDustAny = _hasDustOpticalDepth = true; + if (maxDustDensityDispersion() > 0) _hasAny = _hasDustAny = _hasDustDensityDispersion = true; + if (_hasDustFraction) { - // build a list of media that have this material type with corresponding weights - vector media; - vector weights; - auto ms = find(); - for (auto medium : ms->media()) - if (medium->mix()->isGas()) media.push_back(medium); - for (auto medium : media) weights.push_back(medium->number()); - _mesh = - new TetraMeshSnapshot(this, extent(), sampleMedia(media, weights, extent(), _numSites), _relaxSites); - break; + for (auto medium : _dustMedia) _dustMass += medium->mass(); } - case Policy::File: + if (_hasDustOpticalDepth) { - _mesh = new TetraMeshSnapshot(this, extent(), _filename, _relaxSites); - break; + double sigma = 0.; + double mu = 0.; + for (auto medium : _dustMedia) + { + sigma += medium->mix()->sectionExt(wavelength()); + mu += medium->mix()->mass(); + } + _dustKappa = sigma / mu; } - case Policy::ImportedSites: + } + + // precalculate information for electrons + if (!_electronMedia.empty() && maxElectronFraction() > 0) + { + _hasAny = _hasElectronFraction = true; + for (auto medium : _electronMedia) _electronNumber += medium->number(); + } + + // precalculate information for gas + if (!_gasMedia.empty() && maxGasFraction() > 0) + { + _hasAny = _hasGasFraction = true; + for (auto medium : _gasMedia) _gasNumber += medium->number(); + } + + // warn user if none of the criteria were enabled + if (!_hasAny) find()->warning("None of the tree subdivision criteria are enabled"); + + _mesh = new TetraMeshSnapshot(this, extent(), *this); +} + +bool TetraMeshSpatialGrid::needsSubdivide(double* pa, double* pb, double* pc, double* pd, double vol) const +{ + Vec a(pa[0], pa[1], pa[2]); + Vec b(pb[0], pb[1], pb[2]); + Vec c(pc[0], pc[1], pc[2]); + Vec d(pd[0], pd[1], pd[2]); + Tetra tetra(&a, &b, &c, &d); + + // double x = 0, y = 0, z = 0; + // x += (pa[0] + pb[0] + pc[0] + pd[0]) * .25; + // y += (pa[1] + pb[1] + pc[1] + pd[1]) * .25; + // z += (pa[2] + pb[2] + pc[2] + pd[2]) * .25; + // double density = ms.dustMassDensity(Position(x, y, z)); + // if (vol * density > 0.01 * M) return true; + + // results for the sampled mass or number densities, if applicable + double rho = 0.; // dust mass density + double rhomin = DBL_MAX; // smallest sample for dust mass density + double rhomax = 0.; // largest sample for dust mass density + double ne = 0; // electron number density + double ng = 0.; // gas number density + + // sample densities in node + if (_hasAny) + { + double rhosum = 0; + double nesum = 0; + double ngsum = 0; + for (int i = 0; i != _numSamples; ++i) { - auto sli = find()->interface(2); - _mesh = new TetraMeshSnapshot(this, extent(), sli, _relaxSites); - break; + double s = random()->uniform(); + double t = random()->uniform(); + double u = random()->uniform(); + Position bfr = tetra.generatePosition(s, t, u); + if (_hasDustAny) + { + double rhoi = 0.; + for (auto medium : _dustMedia) rhoi += medium->massDensity(bfr); + rhosum += rhoi; + if (rhoi < rhomin) rhomin = rhoi; + if (rhoi > rhomax) rhomax = rhoi; + } + if (_hasElectronFraction) + for (auto medium : _electronMedia) nesum += medium->numberDensity(bfr); + if (_hasGasFraction) + for (auto medium : _gasMedia) ngsum += medium->numberDensity(bfr); } - case Policy::ImportedMesh: - { - auto ms = find(false); - _mesh = ms->interface(2)->tetraMesh(); + rho = rhosum / _numSamples; + ne = nesum / _numSamples; + ng = ngsum / _numSamples; + } - // if there is a single medium component, calculate the normalization factor imposed by it; - // we need this to directly compute cell densities for the DensityInCellInterface - if (ms->media().size() == 1) _norm = _mesh->mass() > 0 ? ms->media()[0]->number() / _mesh->mass() : 0.; - break; - } + // handle maximum dust mass fraction + if (_hasDustFraction) + { + double delta = rho * vol / _dustMass; + if (delta > maxDustFraction()) return true; } + + // handle maximum dust optical depth + // if (_hasDustOpticalDepth) + // { + // double tau = _dustKappa * rho * node->diagonal(); + // if (tau > maxDustOpticalDepth()) return true; + // } + + // handle maximum dust density dispersion + if (_hasDustDensityDispersion) + { + double q = rhomax > 0 ? (rhomax - rhomin) / rhomax : 0.; + if (q > maxDustDensityDispersion()) return true; + } + + // handle maximum electron number fraction + if (_hasElectronFraction) + { + double delta = ne * vol / _electronNumber; + if (delta > maxElectronFraction()) return true; + } + + // handle maximum gas number fraction + if (_hasGasFraction) + { + double delta = ng * vol / _gasNumber; + if (delta > maxGasFraction()) return true; + } + + // if we get here, none of the criteria were violated + return false; } ////////////////////////////////////////////////////////////////////// diff --git a/SKIRT/core/TetraMeshSpatialGrid.hpp b/SKIRT/core/TetraMeshSpatialGrid.hpp index 06484a4b..13e3b6cc 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.hpp +++ b/SKIRT/core/TetraMeshSpatialGrid.hpp @@ -8,6 +8,7 @@ #include "BoxSpatialGrid.hpp" #include "DensityInCellInterface.hpp" +#include "Medium.hpp" class TetraMeshSnapshot; ////////////////////////////////////////////////////////////////////// @@ -43,18 +44,42 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid, public DensityInCellInterfac PROPERTY_ENUM(policy, Policy, "the policy for determining the positions of the sites") ATTRIBUTE_DEFAULT_VALUE(policy, "DustDensity") - PROPERTY_INT(numSites, "the number of random sites (or cells in the grid)") - ATTRIBUTE_MIN_VALUE(numSites, "5") - ATTRIBUTE_DEFAULT_VALUE(numSites, "500") - ATTRIBUTE_RELEVANT_IF(numSites, "policyUniform|policyCentralPeak|policyDustDensity|" - "policyElectronDensity|policyGasDensity") - - PROPERTY_STRING(filename, "the name of the file containing the site positions") - ATTRIBUTE_RELEVANT_IF(filename, "policyFile") - - PROPERTY_BOOL(relaxSites, "perform site relaxation to avoid overly elongated cells") - ATTRIBUTE_DEFAULT_VALUE(relaxSites, "false") - ATTRIBUTE_RELEVANT_IF(relaxSites, "!policyImportedMesh") + PROPERTY_DOUBLE(maxDustFraction, "the maximum fraction of dust contained in each cell") + ATTRIBUTE_MIN_VALUE(maxDustFraction, "[0") + ATTRIBUTE_MAX_VALUE(maxDustFraction, "1e-2]") + ATTRIBUTE_DEFAULT_VALUE(maxDustFraction, "1e-6") + ATTRIBUTE_DISPLAYED_IF(maxDustFraction, "DustMix") + + PROPERTY_DOUBLE(maxDustOpticalDepth, "the maximum diagonal dust optical depth for each cell") + ATTRIBUTE_MIN_VALUE(maxDustOpticalDepth, "[0") + ATTRIBUTE_MAX_VALUE(maxDustOpticalDepth, "100]") + ATTRIBUTE_DEFAULT_VALUE(maxDustOpticalDepth, "0") + ATTRIBUTE_DISPLAYED_IF(maxDustOpticalDepth, "DustMix&Level2") + + PROPERTY_DOUBLE(wavelength, "the wavelength at which to evaluate the optical depth") + ATTRIBUTE_QUANTITY(wavelength, "wavelength") + ATTRIBUTE_MIN_VALUE(wavelength, "1 pm") + ATTRIBUTE_MAX_VALUE(wavelength, "1 m") + ATTRIBUTE_DEFAULT_VALUE(wavelength, "0.55 micron") + ATTRIBUTE_RELEVANT_IF(wavelength, "maxDustOpticalDepth") + + PROPERTY_DOUBLE(maxDustDensityDispersion, "the maximum dust density dispersion in each cell") + ATTRIBUTE_MIN_VALUE(maxDustDensityDispersion, "[0") + ATTRIBUTE_MAX_VALUE(maxDustDensityDispersion, "1]") + ATTRIBUTE_DEFAULT_VALUE(maxDustDensityDispersion, "0") + ATTRIBUTE_DISPLAYED_IF(maxDustDensityDispersion, "DustMix&Level2") + + PROPERTY_DOUBLE(maxElectronFraction, "the maximum fraction of electrons contained in each cell") + ATTRIBUTE_MIN_VALUE(maxElectronFraction, "[0") + ATTRIBUTE_MAX_VALUE(maxElectronFraction, "1e-2]") + ATTRIBUTE_DEFAULT_VALUE(maxElectronFraction, "1e-6") + ATTRIBUTE_DISPLAYED_IF(maxElectronFraction, "ElectronMix") + + PROPERTY_DOUBLE(maxGasFraction, "the maximum fraction of gas contained in each cell") + ATTRIBUTE_MIN_VALUE(maxGasFraction, "[0") + ATTRIBUTE_MAX_VALUE(maxGasFraction, "1e-2]") + ATTRIBUTE_DEFAULT_VALUE(maxGasFraction, "1e-6") + ATTRIBUTE_DISPLAYED_IF(maxGasFraction, "GasMix") ITEM_END() @@ -74,6 +99,8 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid, public DensityInCellInterfac //======================== Other Functions ======================= public: + bool needsSubdivide(double* pa, double* pb, double* pc, double* pd, double vol) const; + /** This function returns the number of cells in the grid. */ int numCells() const override; @@ -128,7 +155,33 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid, public DensityInCellInterfac private: // data members initialized during setup TetraMeshSnapshot* _mesh{nullptr}; // Tetra mesh snapshot created here or obtained from medium component - double _norm{0.}; // in the latter case, normalization factor obtained from medium component + double _norm{0.}; // in the latter case, normalization factor obtained from medium component + + // data members initialized by setupSelfBefore() + Random* _random{nullptr}; + int _numSamples{0}; + + // lists of medium components of each material type; + // list remains empty if no criteria are enabled for the corresponding material type + vector _dustMedia; + vector _electronMedia; + vector _gasMedia; + + // flags become true if corresponding criterion is enabled + // (i.e. configured maximum is nonzero and material type is present) + bool _hasAny{false}; + bool _hasDustAny{false}; + bool _hasDustFraction{false}; + bool _hasDustOpticalDepth{false}; + bool _hasDustDensityDispersion{false}; + bool _hasElectronFraction{false}; + bool _hasGasFraction{false}; + + // cached values for each material type (valid if corresponding flag is enabled) + double _dustMass{0.}; + double _dustKappa{0.}; + double _electronNumber{0.}; + double _gasNumber{0.}; }; ////////////////////////////////////////////////////////////////////// diff --git a/SKIRT/core/Tetrahedron.cpp b/SKIRT/core/Tetrahedron.cpp new file mode 100644 index 00000000..5874e718 --- /dev/null +++ b/SKIRT/core/Tetrahedron.cpp @@ -0,0 +1,214 @@ +#include "Tetrahedron.hpp" +#include "FatalError.hpp" + +//////////////////////////////////////////////////////////////////// + +Plucker::Plucker() {} + +//////////////////////////////////////////////////////////////////// + +Plucker::Plucker(const Vec& pos, const Vec& dir) : U(dir), V(Vec::cross(dir, pos)) {} + +//////////////////////////////////////////////////////////////////// + +inline double Plucker::dot(const Plucker& a, const Plucker& b) +{ + return Vec::dot(a.U, b.V) + Vec::dot(b.U, a.V); +} + +//////////////////////////////////////////////////////////////////// + +Edge::Edge(int i1, int i2, const Vec* v1, const Vec* v2) : Plucker(*v1, *v2 - *v1), i1(i1), i2(i2) {} + +//////////////////////////////////////////////////////////////////// + +Tetra::Tetra(const std::array& vertices, const std::array& indices, + const std::array& neighbors, const std::array& edges) + : _vertices(vertices), _indices(indices), _edges(edges), _neighbors(neighbors) +{ + double xmin = DBL_MAX; + double ymin = DBL_MAX; + double zmin = DBL_MAX; + double xmax = -DBL_MAX; + double ymax = -DBL_MAX; + double zmax = -DBL_MAX; + for (const Vec* vertex : _vertices) + { + xmin = min(xmin, vertex->x()); + ymin = min(ymin, vertex->y()); + zmin = min(zmin, vertex->z()); + xmax = max(xmax, vertex->x()); + ymax = max(ymax, vertex->y()); + zmax = max(zmax, vertex->z()); + } + setExtent(Box(xmin, ymin, zmin, xmax, ymax, zmax)); + + _volume = 1 / 6. + * abs(Vec::dot(Vec::cross(*_vertices[1] - *_vertices[0], *_vertices[2] - *_vertices[0]), + *_vertices[3] - *_vertices[0])); +} + +//////////////////////////////////////////////////////////////////// + +Tetra::Tetra(Vec* va, Vec* vb, Vec* vc, Vec* vd) : _vertices({va, vb, vc, vd}) +{ + _volume = 1 / 6. + * abs(Vec::dot(Vec::cross(*_vertices[1] - *_vertices[0], *_vertices[2] - *_vertices[0]), + *_vertices[3] - *_vertices[0])); +} + +//////////////////////////////////////////////////////////////////// + +double Tetra::getProd(const Plucker& ray, int t1, int t2) const +{ + int e = (std::min(t1, t2) == 0) ? std::max(t1, t2) - 1 : t1 + t2; + Edge* edge = _edges[e]; + + // not the same order -> *-1 + // beginning of t1 == beginning of edge (= same order) + return (_indices[t1] == edge->i1 ? 1 : -1) * Plucker::dot(ray, *edge); +} + +//////////////////////////////////////////////////////////////////// + +bool Tetra::intersects(std::array& barycoords, const Plucker& ray, int face, bool leaving) const +{ + std::array t = clockwiseVertices(face); + + double sum = 0; + for (int i = 0; i < 3; i++) + { + // edges: 12, 20, 01 + // verts: 0, 1, 2 + double prod = getProd(ray, t[(i + 1) % 3], t[(i + 2) % 3]); + if (leaving != (prod <= 0)) return false; // change this so for both leavig and entering prod=0 works + barycoords[i] = prod; + sum += prod; + } + for (int i = 0; i < 3; i++) barycoords[i] /= sum; + + return true; +} + +//////////////////////////////////////////////////////////////////// + +bool Tetra::inside(const Position& bfr) const +{ + if (!Box::contains(bfr)) return false; + + /* + face: normals for which the other vertex has a positive dot product with + 3:*02 x 01*| 10 x 12 | 21 x 20 + 2: 13 x 10 |*01 x 03*| 30 x 31 + 1: 20 x 23 | 32 x 30 |*03 x 02* + 0: 31 x 32 | 23 x 21 |*12 x 13* // last one doesn't matter + */ + + // optimized version (probably not that much better) + Vec e0p = bfr - *_vertices[0]; + Vec e02 = *_vertices[2] - *_vertices[0]; + Vec e01 = *_vertices[1] - *_vertices[0]; + if (Vec::dot(Vec::cross(e02, e01), e0p) > 0) // 02 x 01 + return false; + + Vec e03 = *_vertices[3] - *_vertices[0]; + if (Vec::dot(Vec::cross(e01, e03), e0p) > 0) // 01 x 03 + return false; + + if (Vec::dot(Vec::cross(e03, e02), e0p) > 0) // 03 x 02 + return false; + + Vec e1p = bfr - *_vertices[1]; + Vec e12 = *_vertices[2] - *_vertices[1]; + Vec e13 = *_vertices[3] - *_vertices[1]; + return Vec::dot(Vec::cross(e12, e13), e1p) < 0; // 12 x 13 + + // checks 3 edges too many but very simple + // for (int face = 0; face < 4; face++) + // { + // std::array t = clockwiseVertices(face); + // Vec& v0 = *_vertices[t[0]]; + // Vec& clock = *_vertices[t[1]]; + // Vec& counter = *_vertices[t[2]]; + // Vec normal = Vec::cross(counter - v0, clock - v0); + // if (Vec::dot(normal, bfr - v0) < 0) // is pos on the same side as v3 + // { + // return false; + // } + // } + // return true; +} + +//////////////////////////////////////////////////////////////////// + +Vec Tetra::calcExit(const std::array& barycoords, int face) const +{ + std::array t = Tetra::clockwiseVertices(face); + Vec exit; + for (int i = 0; i < 3; i++) exit += *_vertices[t[i]] * barycoords[i]; + return exit; +} + +//////////////////////////////////////////////////////////////////// + +Vec Tetra::centroid() const +{ + Vec pos; + for (int i = 0; i < 4; i++) pos += *_vertices[i]; + pos /= 4; + return pos; +} + +//////////////////////////////////////////////////////////////////// + +double Tetra::volume() const +{ + return _volume; +} + +//////////////////////////////////////////////////////////////////// + +const Array& Tetra::properties() +{ + return _properties; +} + +//////////////////////////////////////////////////////////////////// + +Position Tetra::generatePosition(double s, double t, double u) const +{ + // https://vcg.isti.cnr.it/activities/OLD/geometryegraphics/pointintetraedro.html + if (s + t > 1.0) + { // cut'n fold the cube into a prism + + s = 1.0 - s; + t = 1.0 - t; + } + if (t + u > 1.0) + { // cut'n fold the prism into a tetrahedron + + double tmp = u; + u = 1.0 - s - t; + t = 1.0 - tmp; + } + else if (s + t + u > 1.0) + { + + double tmp = u; + u = s + t + u - 1.0; + s = 1 - t - tmp; + } + double a = 1 - u - t - s; + + return Position(a * *_vertices[0] + u * *_vertices[1] + t * *_vertices[2] + s * *_vertices[3]); +} + +//////////////////////////////////////////////////////////////////// + +std::array Tetra::clockwiseVertices(int face) +{ + std::array t = {(face + 3) % 4, (face + 2) % 4, (face + 1) % 4}; + // if face is even we should swap two edges + if (face % 2 == 0) std::swap(t[0], t[2]); + return t; +} diff --git a/SKIRT/core/Tetrahedron.hpp b/SKIRT/core/Tetrahedron.hpp new file mode 100644 index 00000000..46c5412e --- /dev/null +++ b/SKIRT/core/Tetrahedron.hpp @@ -0,0 +1,61 @@ +#include "Array.hpp" +#include "Box.hpp" +#include "Position.hpp" + +class Plucker +{ +private: + Vec U, V; + +public: + Plucker(); + + Plucker(const Vec& pos, const Vec& dir); + + // permuted inner product + static inline double dot(const Plucker& a, const Plucker& b); +}; + +class Edge : public Plucker +{ +public: + const int i1, i2; + Edge(int i1, int i2, const Vec* v1, const Vec* v2); +}; + +class Tetra : public Box +{ +private: + double _volume; + Array _properties; + +public: + std::array _vertices; + std::array _indices; + std::array _edges; + std::array _neighbors; + +public: + Tetra(const std::array& vertices, const std::array& indices, const std::array& neighbors, + const std::array& edges); + + Tetra(Vec* va, Vec* vb, Vec* vc, Vec* vd); + + double getProd(const Plucker& ray, int t1, int t2) const; + + bool intersects(std::array& prods, const Plucker& ray, int face, bool leaving = true) const; + + bool inside(const Position& bfr) const; + + Vec calcExit(const std::array& barycoords, int face) const; + + Vec centroid() const; + + double volume() const; + + const Array& properties(); + + Position generatePosition(double s, double t, double u) const; + + static std::array clockwiseVertices(int face); +}; diff --git a/SKIRT/tetgen/CMakeLists.txt b/SKIRT/tetgen/CMakeLists.txt index d360f22a..a4857542 100644 --- a/SKIRT/tetgen/CMakeLists.txt +++ b/SKIRT/tetgen/CMakeLists.txt @@ -10,7 +10,8 @@ # at https://github.com/libigl/tetgen (git commit 4f3bfba). # # Changes: -# - +# - changed TetSizeFunc from function pointer to std::function so lambda with captures can be used +# - adjusted the calling of the tetunsuitable to remove the last two unused parameters #--------------------------------------------------------------------- # set the target name diff --git a/SKIRT/tetgen/tetgen.cxx b/SKIRT/tetgen/tetgen.cxx index 899a47b6..872578f8 100644 --- a/SKIRT/tetgen/tetgen.cxx +++ b/SKIRT/tetgen/tetgen.cxx @@ -28403,7 +28403,7 @@ bool tetgenmesh::checktet4split(triface *chktet, REAL* param, int& qflag) if (in->tetunsuitable != NULL) { // Execute the user-defined meshing sizing evaluation. - if ((*(in->tetunsuitable))(pa, pb, pc, pd, NULL, 0)) { + if (((in->tetunsuitable))(pa, pb, pc, pd, vol)) { // SKIRT: removed unused parameters here return true; } } diff --git a/SKIRT/tetgen/tetgen.h b/SKIRT/tetgen/tetgen.h index 0e34818a..e5810eae 100644 --- a/SKIRT/tetgen/tetgen.h +++ b/SKIRT/tetgen/tetgen.h @@ -45,7 +45,6 @@ // // //============================================================================// - #ifndef tetgenH #define tetgenH @@ -54,7 +53,6 @@ #define TETLIBRARY - // TetGen default uses the double-precision (64 bit) for a real number. // Alternatively, one can use the single-precision (32 bit) 'float' if the // memory is limited. @@ -73,11 +71,12 @@ // manipulate strings and arrays, compute common mathematical operations, // get date and time information. +#include #include #include #include -#include #include +#include // The types 'intptr_t' and 'uintptr_t' are signed and unsigned integer types, // respectively. They are guaranteed to be the same width as a pointer. @@ -106,494 +105,536 @@ // // //============================================================================// -class tetgenio { +class tetgenio +{ public: - - // A "polygon" describes a simple polygon (no holes). It is not necessarily - // convex. Each polygon contains a number of corners (points) and the same - // number of sides (edges). The points of the polygon must be given in - // either counterclockwise or clockwise order and they form a ring, so - // every two consecutive points forms an edge of the polygon. - typedef struct { - int *vertexlist; - int numberofvertices; - } polygon; - - // A "facet" describes a polygonal region possibly with holes, edges, and - // points floating in it. Each facet consists of a list of polygons and - // a list of hole points (which lie strictly inside holes). - typedef struct { - polygon *polygonlist; - int numberofpolygons; - REAL *holelist; + // A "polygon" describes a simple polygon (no holes). It is not necessarily + // convex. Each polygon contains a number of corners (points) and the same + // number of sides (edges). The points of the polygon must be given in + // either counterclockwise or clockwise order and they form a ring, so + // every two consecutive points forms an edge of the polygon. + typedef struct + { + int* vertexlist; + int numberofvertices; + } polygon; + + // A "facet" describes a polygonal region possibly with holes, edges, and + // points floating in it. Each facet consists of a list of polygons and + // a list of hole points (which lie strictly inside holes). + typedef struct + { + polygon* polygonlist; + int numberofpolygons; + REAL* holelist; + int numberofholes; + } facet; + + // A "voroedge" is an edge of the Voronoi diagram. It corresponds to a + // Delaunay face. Each voroedge is either a line segment connecting + // two Voronoi vertices or a ray starting from a Voronoi vertex to an + // "infinite vertex". 'v1' and 'v2' are two indices pointing to the + // list of Voronoi vertices. 'v1' must be non-negative, while 'v2' may + // be -1 if it is a ray, in this case, the unit normal of this ray is + // given in 'vnormal'. + typedef struct + { + int v1, v2; + REAL vnormal[3]; + } voroedge; + + // A "vorofacet" is an facet of the Voronoi diagram. It corresponds to a + // Delaunay edge. Each Voronoi facet is a convex polygon formed by a + // list of Voronoi edges, it may not be closed. 'c1' and 'c2' are two + // indices pointing into the list of Voronoi cells, i.e., the two cells + // share this facet. 'elist' is an array of indices pointing into the + // list of Voronoi edges, 'elist[0]' saves the number of Voronoi edges + // (including rays) of this facet. + typedef struct + { + int c1, c2; + int* elist; + } vorofacet; + + // Additional parameters associated with an input (or mesh) vertex. + // These informations are provided by CAD libraries. + typedef struct + { + REAL uv[2]; + int tag; + int type; // 0, 1, or 2. + } pointparam; + + // Callback functions for meshing PSCs. + typedef REAL (*GetVertexParamOnEdge)(void*, int, int); + typedef void (*GetSteinerOnEdge)(void*, int, REAL, REAL*); + typedef void (*GetVertexParamOnFace)(void*, int, int, REAL*); + typedef void (*GetEdgeSteinerParamOnFace)(void*, int, REAL, int, REAL*); + typedef void (*GetSteinerOnFace)(void*, int, REAL*, REAL*); + + // A callback function for mesh refinement. + // typedef bool (* TetSizeFunc)(REAL*, REAL*, REAL*, REAL*, REAL*, REAL); + // SKIRT: changed this to std::function for lambda with captures + typedef std::function TetSizeFunc; + + // Items are numbered starting from 'firstnumber' (0 or 1), default is 0. + int firstnumber; + + // Dimension of the mesh (2 or 3), default is 3. + int mesh_dim; + + // Does the lines in .node file contain index or not, default is 1. + int useindex; + + // 'pointlist': An array of point coordinates. The first point's x + // coordinate is at index [0] and its y coordinate at index [1], its + // z coordinate is at index [2], followed by the coordinates of the + // remaining points. Each point occupies three REALs. + // 'pointattributelist': An array of point attributes. Each point's + // attributes occupy 'numberofpointattributes' REALs. + // 'pointmtrlist': An array of metric tensors at points. Each point's + // tensor occupies 'numberofpointmtr' REALs. + // 'pointmarkerlist': An array of point markers; one integer per point. + // 'point2tetlist': An array of tetrahedra indices; one integer per point. + REAL* pointlist; + REAL* pointattributelist; + REAL* pointmtrlist; + int* pointmarkerlist; + int* point2tetlist; + pointparam* pointparamlist; + int numberofpoints; + int numberofpointattributes; + int numberofpointmtrs; + + // 'tetrahedronlist': An array of tetrahedron corners. The first + // tetrahedron's first corner is at index [0], followed by its other + // corners, followed by six nodes on the edges of the tetrahedron if the + // second order option (-o2) is applied. Each tetrahedron occupies + // 'numberofcorners' ints. The second order nodes are ouput only. + // 'tetrahedronattributelist': An array of tetrahedron attributes. Each + // tetrahedron's attributes occupy 'numberoftetrahedronattributes' REALs. + // 'tetrahedronvolumelist': An array of constraints, i.e. tetrahedron's + // volume; one REAL per element. Input only. + // 'neighborlist': An array of tetrahedron neighbors; 4 ints per element. + // 'tet2facelist': An array of tetrahedron face indices; 4 ints per element. + // 'tet2edgelist': An array of tetrahedron edge indices; 6 ints per element. + int* tetrahedronlist; + REAL* tetrahedronattributelist; + REAL* tetrahedronvolumelist; + int* neighborlist; + int* tet2facelist; + int* tet2edgelist; + int numberoftetrahedra; + int numberofcorners; + int numberoftetrahedronattributes; + + // 'facetlist': An array of facets. Each entry is a structure of facet. + // 'facetmarkerlist': An array of facet markers; one int per facet. + facet* facetlist; + int* facetmarkerlist; + int numberoffacets; + + // 'holelist': An array of holes (in volume). Each hole is given by a + // seed (point) which lies strictly inside it. The first seed's x, y and z + // coordinates are at indices [0], [1] and [2], followed by the + // remaining seeds. Three REALs per hole. + REAL* holelist; int numberofholes; - } facet; - - // A "voroedge" is an edge of the Voronoi diagram. It corresponds to a - // Delaunay face. Each voroedge is either a line segment connecting - // two Voronoi vertices or a ray starting from a Voronoi vertex to an - // "infinite vertex". 'v1' and 'v2' are two indices pointing to the - // list of Voronoi vertices. 'v1' must be non-negative, while 'v2' may - // be -1 if it is a ray, in this case, the unit normal of this ray is - // given in 'vnormal'. - typedef struct { - int v1, v2; - REAL vnormal[3]; - } voroedge; - - // A "vorofacet" is an facet of the Voronoi diagram. It corresponds to a - // Delaunay edge. Each Voronoi facet is a convex polygon formed by a - // list of Voronoi edges, it may not be closed. 'c1' and 'c2' are two - // indices pointing into the list of Voronoi cells, i.e., the two cells - // share this facet. 'elist' is an array of indices pointing into the - // list of Voronoi edges, 'elist[0]' saves the number of Voronoi edges - // (including rays) of this facet. - typedef struct { - int c1, c2; - int *elist; - } vorofacet; - - - // Additional parameters associated with an input (or mesh) vertex. - // These informations are provided by CAD libraries. - typedef struct { - REAL uv[2]; - int tag; - int type; // 0, 1, or 2. - } pointparam; - - // Callback functions for meshing PSCs. - typedef REAL (* GetVertexParamOnEdge)(void*, int, int); - typedef void (* GetSteinerOnEdge)(void*, int, REAL, REAL*); - typedef void (* GetVertexParamOnFace)(void*, int, int, REAL*); - typedef void (* GetEdgeSteinerParamOnFace)(void*, int, REAL, int, REAL*); - typedef void (* GetSteinerOnFace)(void*, int, REAL*, REAL*); - - // A callback function for mesh refinement. - typedef bool (* TetSizeFunc)(REAL*, REAL*, REAL*, REAL*, REAL*, REAL); - - // Items are numbered starting from 'firstnumber' (0 or 1), default is 0. - int firstnumber; - - // Dimension of the mesh (2 or 3), default is 3. - int mesh_dim; - - // Does the lines in .node file contain index or not, default is 1. - int useindex; - - // 'pointlist': An array of point coordinates. The first point's x - // coordinate is at index [0] and its y coordinate at index [1], its - // z coordinate is at index [2], followed by the coordinates of the - // remaining points. Each point occupies three REALs. - // 'pointattributelist': An array of point attributes. Each point's - // attributes occupy 'numberofpointattributes' REALs. - // 'pointmtrlist': An array of metric tensors at points. Each point's - // tensor occupies 'numberofpointmtr' REALs. - // 'pointmarkerlist': An array of point markers; one integer per point. - // 'point2tetlist': An array of tetrahedra indices; one integer per point. - REAL *pointlist; - REAL *pointattributelist; - REAL *pointmtrlist; - int *pointmarkerlist; - int *point2tetlist; - pointparam *pointparamlist; - int numberofpoints; - int numberofpointattributes; - int numberofpointmtrs; - - // 'tetrahedronlist': An array of tetrahedron corners. The first - // tetrahedron's first corner is at index [0], followed by its other - // corners, followed by six nodes on the edges of the tetrahedron if the - // second order option (-o2) is applied. Each tetrahedron occupies - // 'numberofcorners' ints. The second order nodes are ouput only. - // 'tetrahedronattributelist': An array of tetrahedron attributes. Each - // tetrahedron's attributes occupy 'numberoftetrahedronattributes' REALs. - // 'tetrahedronvolumelist': An array of constraints, i.e. tetrahedron's - // volume; one REAL per element. Input only. - // 'neighborlist': An array of tetrahedron neighbors; 4 ints per element. - // 'tet2facelist': An array of tetrahedron face indices; 4 ints per element. - // 'tet2edgelist': An array of tetrahedron edge indices; 6 ints per element. - int *tetrahedronlist; - REAL *tetrahedronattributelist; - REAL *tetrahedronvolumelist; - int *neighborlist; - int *tet2facelist; - int *tet2edgelist; - int numberoftetrahedra; - int numberofcorners; - int numberoftetrahedronattributes; - - // 'facetlist': An array of facets. Each entry is a structure of facet. - // 'facetmarkerlist': An array of facet markers; one int per facet. - facet *facetlist; - int *facetmarkerlist; - int numberoffacets; - - // 'holelist': An array of holes (in volume). Each hole is given by a - // seed (point) which lies strictly inside it. The first seed's x, y and z - // coordinates are at indices [0], [1] and [2], followed by the - // remaining seeds. Three REALs per hole. - REAL *holelist; - int numberofholes; - - // 'regionlist': An array of regions (subdomains). Each region is given by - // a seed (point) which lies strictly inside it. The first seed's x, y and - // z coordinates are at indices [0], [1] and [2], followed by the regional - // attribute at index [3], followed by the maximum volume at index [4]. - // Five REALs per region. - // Note that each regional attribute is used only if you select the 'A' - // switch, and each volume constraint is used only if you select the - // 'a' switch (with no number following). - REAL *regionlist; - int numberofregions; - - // 'refine_elem_list': An array of tetrahedra to be refined. The first - // tetrahedron's first corner is at index [0], followed by its other - // corners. Four integers per element. - // 'refine_elem_vol_list': An array of constraints, i.e. tetrahedron's - // volume; one REAL per element. - int *refine_elem_list; - REAL *refine_elem_vol_list; - int numberofrefineelems; - - // 'facetconstraintlist': An array of facet constraints. Each constraint - // specifies a maximum area bound on the subfaces of that facet. The - // first facet constraint is given by a facet marker at index [0] and its - // maximum area bound at index [1], followed by the remaining facet con- - // straints. Two REALs per facet constraint. Note: the facet marker is - // actually an integer. - REAL *facetconstraintlist; - int numberoffacetconstraints; - - // 'segmentconstraintlist': An array of segment constraints. Each constraint - // specifies a maximum length bound on the subsegments of that segment. - // The first constraint is given by the two endpoints of the segment at - // index [0] and [1], and the maximum length bound at index [2], followed - // by the remaining segment constraints. Three REALs per constraint. - // Note the segment endpoints are actually integers. - REAL *segmentconstraintlist; - int numberofsegmentconstraints; - - - // 'trifacelist': An array of face (triangle) corners. The first face's - // three corners are at indices [0], [1] and [2], followed by the remaining - // faces. Three ints per face. - // 'trifacemarkerlist': An array of face markers; one int per face. - // 'o2facelist': An array of second order nodes (on the edges) of the face. - // It is output only if the second order option (-o2) is applied. The - // first face's three second order nodes are at [0], [1], and [2], - // followed by the remaining faces. Three ints per face. - // 'face2tetlist': An array of tetrahedra indices; 2 ints per face. - // 'face2edgelist': An array of edge indices; 3 ints per face. - int *trifacelist; - int *trifacemarkerlist; - int *o2facelist; - int *face2tetlist; - int *face2edgelist; - int numberoftrifaces; - - // 'edgelist': An array of edge endpoints. The first edge's endpoints - // are at indices [0] and [1], followed by the remaining edges. - // Two ints per edge. - // 'edgemarkerlist': An array of edge markers; one int per edge. - // 'o2edgelist': An array of midpoints of edges. It is output only if the - // second order option (-o2) is applied. One int per edge. - // 'edge2tetlist': An array of tetrahedra indices. One int per edge. - int *edgelist; - int *edgemarkerlist; - int *o2edgelist; - int *edge2tetlist; - int numberofedges; - - // 'vpointlist': An array of Voronoi vertex coordinates (like pointlist). - // 'vedgelist': An array of Voronoi edges. Each entry is a 'voroedge'. - // 'vfacetlist': An array of Voronoi facets. Each entry is a 'vorofacet'. - // 'vcelllist': An array of Voronoi cells. Each entry is an array of - // indices pointing into 'vfacetlist'. The 0th entry is used to store - // the length of this array. - REAL *vpointlist; - voroedge *vedgelist; - vorofacet *vfacetlist; - int **vcelllist; - int numberofvpoints; - int numberofvedges; - int numberofvfacets; - int numberofvcells; - - - // Variable (and callback functions) for meshing PSCs. - void *geomhandle; - GetVertexParamOnEdge getvertexparamonedge; - GetSteinerOnEdge getsteineronedge; - GetVertexParamOnFace getvertexparamonface; - GetEdgeSteinerParamOnFace getedgesteinerparamonface; - GetSteinerOnFace getsteineronface; - - // A callback function. - TetSizeFunc tetunsuitable; - - // Input & output routines. - bool load_node_call(FILE* infile, int markers, int uvflag, char*); - bool load_node(char*); - bool load_edge(char*); - bool load_face(char*); - bool load_tet(char*); - bool load_vol(char*); - bool load_var(char*); - bool load_mtr(char*); - bool load_elem(char*); - bool load_poly(char*); - bool load_off(char*); - bool load_ply(char*); - bool load_stl(char*); - bool load_vtk(char*); - bool load_medit(char*, int); - bool load_neumesh(char*, int); - bool load_plc(char*, int); - bool load_tetmesh(char*, int); - void save_nodes(const char*); - void save_elements(const char*); - void save_faces(const char*); - void save_edges(char*); - void save_neighbors(char*); - void save_poly(const char*); - void save_faces2smesh(char*); - - // Read line and parse string functions. - char *readline(char* string, FILE* infile, int *linenumber); - char *findnextfield(char* string); - char *readnumberline(char* string, FILE* infile, char* infilename); - char *findnextnumber(char* string); - - static void init(polygon* p) { - p->vertexlist = (int *) NULL; - p->numberofvertices = 0; - } - - static void init(facet* f) { - f->polygonlist = (polygon *) NULL; - f->numberofpolygons = 0; - f->holelist = (REAL *) NULL; - f->numberofholes = 0; - } - - // Initialize routine. - void initialize() - { - firstnumber = 0; - mesh_dim = 3; - useindex = 1; - - pointlist = (REAL *) NULL; - pointattributelist = (REAL *) NULL; - pointmtrlist = (REAL *) NULL; - pointmarkerlist = (int *) NULL; - point2tetlist = (int *) NULL; - pointparamlist = (pointparam *) NULL; - numberofpoints = 0; - numberofpointattributes = 0; - numberofpointmtrs = 0; - - tetrahedronlist = (int *) NULL; - tetrahedronattributelist = (REAL *) NULL; - tetrahedronvolumelist = (REAL *) NULL; - neighborlist = (int *) NULL; - tet2facelist = (int *) NULL; - tet2edgelist = (int *) NULL; - numberoftetrahedra = 0; - numberofcorners = 4; - numberoftetrahedronattributes = 0; - - trifacelist = (int *) NULL; - trifacemarkerlist = (int *) NULL; - o2facelist = (int *) NULL; - face2tetlist = (int *) NULL; - face2edgelist = (int *) NULL; - numberoftrifaces = 0; - - edgelist = (int *) NULL; - edgemarkerlist = (int *) NULL; - o2edgelist = (int *) NULL; - edge2tetlist = (int *) NULL; - numberofedges = 0; - - facetlist = (facet *) NULL; - facetmarkerlist = (int *) NULL; - numberoffacets = 0; - - holelist = (REAL *) NULL; - numberofholes = 0; - - regionlist = (REAL *) NULL; - numberofregions = 0; - - refine_elem_list = (int *) NULL; - refine_elem_vol_list = (REAL *) NULL; - numberofrefineelems = 0; - - facetconstraintlist = (REAL *) NULL; - numberoffacetconstraints = 0; - segmentconstraintlist = (REAL *) NULL; - numberofsegmentconstraints = 0; - - - vpointlist = (REAL *) NULL; - vedgelist = (voroedge *) NULL; - vfacetlist = (vorofacet *) NULL; - vcelllist = (int **) NULL; - numberofvpoints = 0; - numberofvedges = 0; - numberofvfacets = 0; - numberofvcells = 0; - - - tetunsuitable = NULL; - - geomhandle = NULL; - getvertexparamonedge = NULL; - getsteineronedge = NULL; - getvertexparamonface = NULL; - getedgesteinerparamonface = NULL; - getsteineronface = NULL; - } - - // Free the memory allocated in 'tetgenio'. Note that it assumes that the - // memory was allocated by the "new" operator (C++). - void clean_memory() - { - int i, j; - - if (pointlist != (REAL *) NULL) { - delete [] pointlist; - } - if (pointattributelist != (REAL *) NULL) { - delete [] pointattributelist; - } - if (pointmtrlist != (REAL *) NULL) { - delete [] pointmtrlist; - } - if (pointmarkerlist != (int *) NULL) { - delete [] pointmarkerlist; - } - if (point2tetlist != (int *) NULL) { - delete [] point2tetlist; - } - if (pointparamlist != (pointparam *) NULL) { - delete [] pointparamlist; - } - if (tetrahedronlist != (int *) NULL) { - delete [] tetrahedronlist; - } - if (tetrahedronattributelist != (REAL *) NULL) { - delete [] tetrahedronattributelist; - } - if (tetrahedronvolumelist != (REAL *) NULL) { - delete [] tetrahedronvolumelist; - } - if (neighborlist != (int *) NULL) { - delete [] neighborlist; - } - if (tet2facelist != (int *) NULL) { - delete [] tet2facelist; - } - if (tet2edgelist != (int *) NULL) { - delete [] tet2edgelist; - } - - if (trifacelist != (int *) NULL) { - delete [] trifacelist; - } - if (trifacemarkerlist != (int *) NULL) { - delete [] trifacemarkerlist; - } - if (o2facelist != (int *) NULL) { - delete [] o2facelist; - } - if (face2tetlist != (int *) NULL) { - delete [] face2tetlist; - } - if (face2edgelist != (int *) NULL) { - delete [] face2edgelist; + // 'regionlist': An array of regions (subdomains). Each region is given by + // a seed (point) which lies strictly inside it. The first seed's x, y and + // z coordinates are at indices [0], [1] and [2], followed by the regional + // attribute at index [3], followed by the maximum volume at index [4]. + // Five REALs per region. + // Note that each regional attribute is used only if you select the 'A' + // switch, and each volume constraint is used only if you select the + // 'a' switch (with no number following). + REAL* regionlist; + int numberofregions; + + // 'refine_elem_list': An array of tetrahedra to be refined. The first + // tetrahedron's first corner is at index [0], followed by its other + // corners. Four integers per element. + // 'refine_elem_vol_list': An array of constraints, i.e. tetrahedron's + // volume; one REAL per element. + int* refine_elem_list; + REAL* refine_elem_vol_list; + int numberofrefineelems; + + // 'facetconstraintlist': An array of facet constraints. Each constraint + // specifies a maximum area bound on the subfaces of that facet. The + // first facet constraint is given by a facet marker at index [0] and its + // maximum area bound at index [1], followed by the remaining facet con- + // straints. Two REALs per facet constraint. Note: the facet marker is + // actually an integer. + REAL* facetconstraintlist; + int numberoffacetconstraints; + + // 'segmentconstraintlist': An array of segment constraints. Each constraint + // specifies a maximum length bound on the subsegments of that segment. + // The first constraint is given by the two endpoints of the segment at + // index [0] and [1], and the maximum length bound at index [2], followed + // by the remaining segment constraints. Three REALs per constraint. + // Note the segment endpoints are actually integers. + REAL* segmentconstraintlist; + int numberofsegmentconstraints; + + // 'trifacelist': An array of face (triangle) corners. The first face's + // three corners are at indices [0], [1] and [2], followed by the remaining + // faces. Three ints per face. + // 'trifacemarkerlist': An array of face markers; one int per face. + // 'o2facelist': An array of second order nodes (on the edges) of the face. + // It is output only if the second order option (-o2) is applied. The + // first face's three second order nodes are at [0], [1], and [2], + // followed by the remaining faces. Three ints per face. + // 'face2tetlist': An array of tetrahedra indices; 2 ints per face. + // 'face2edgelist': An array of edge indices; 3 ints per face. + int* trifacelist; + int* trifacemarkerlist; + int* o2facelist; + int* face2tetlist; + int* face2edgelist; + int numberoftrifaces; + + // 'edgelist': An array of edge endpoints. The first edge's endpoints + // are at indices [0] and [1], followed by the remaining edges. + // Two ints per edge. + // 'edgemarkerlist': An array of edge markers; one int per edge. + // 'o2edgelist': An array of midpoints of edges. It is output only if the + // second order option (-o2) is applied. One int per edge. + // 'edge2tetlist': An array of tetrahedra indices. One int per edge. + int* edgelist; + int* edgemarkerlist; + int* o2edgelist; + int* edge2tetlist; + int numberofedges; + + // 'vpointlist': An array of Voronoi vertex coordinates (like pointlist). + // 'vedgelist': An array of Voronoi edges. Each entry is a 'voroedge'. + // 'vfacetlist': An array of Voronoi facets. Each entry is a 'vorofacet'. + // 'vcelllist': An array of Voronoi cells. Each entry is an array of + // indices pointing into 'vfacetlist'. The 0th entry is used to store + // the length of this array. + REAL* vpointlist; + voroedge* vedgelist; + vorofacet* vfacetlist; + int** vcelllist; + int numberofvpoints; + int numberofvedges; + int numberofvfacets; + int numberofvcells; + + // Variable (and callback functions) for meshing PSCs. + void* geomhandle; + GetVertexParamOnEdge getvertexparamonedge; + GetSteinerOnEdge getsteineronedge; + GetVertexParamOnFace getvertexparamonface; + GetEdgeSteinerParamOnFace getedgesteinerparamonface; + GetSteinerOnFace getsteineronface; + + // A callback function. + TetSizeFunc tetunsuitable; + + // Input & output routines. + bool load_node_call(FILE* infile, int markers, int uvflag, char*); + bool load_node(char*); + bool load_edge(char*); + bool load_face(char*); + bool load_tet(char*); + bool load_vol(char*); + bool load_var(char*); + bool load_mtr(char*); + bool load_elem(char*); + bool load_poly(char*); + bool load_off(char*); + bool load_ply(char*); + bool load_stl(char*); + bool load_vtk(char*); + bool load_medit(char*, int); + bool load_neumesh(char*, int); + bool load_plc(char*, int); + bool load_tetmesh(char*, int); + void save_nodes(const char*); + void save_elements(const char*); + void save_faces(const char*); + void save_edges(char*); + void save_neighbors(char*); + void save_poly(const char*); + void save_faces2smesh(char*); + + // Read line and parse string functions. + char* readline(char* string, FILE* infile, int* linenumber); + char* findnextfield(char* string); + char* readnumberline(char* string, FILE* infile, char* infilename); + char* findnextnumber(char* string); + + static void init(polygon* p) + { + p->vertexlist = (int*)NULL; + p->numberofvertices = 0; } - if (edgelist != (int *) NULL) { - delete [] edgelist; + static void init(facet* f) + { + f->polygonlist = (polygon*)NULL; + f->numberofpolygons = 0; + f->holelist = (REAL*)NULL; + f->numberofholes = 0; } - if (edgemarkerlist != (int *) NULL) { - delete [] edgemarkerlist; - } - if (o2edgelist != (int *) NULL) { - delete [] o2edgelist; - } - if (edge2tetlist != (int *) NULL) { - delete [] edge2tetlist; + + // Initialize routine. + void initialize() + { + firstnumber = 0; + mesh_dim = 3; + useindex = 1; + + pointlist = (REAL*)NULL; + pointattributelist = (REAL*)NULL; + pointmtrlist = (REAL*)NULL; + pointmarkerlist = (int*)NULL; + point2tetlist = (int*)NULL; + pointparamlist = (pointparam*)NULL; + numberofpoints = 0; + numberofpointattributes = 0; + numberofpointmtrs = 0; + + tetrahedronlist = (int*)NULL; + tetrahedronattributelist = (REAL*)NULL; + tetrahedronvolumelist = (REAL*)NULL; + neighborlist = (int*)NULL; + tet2facelist = (int*)NULL; + tet2edgelist = (int*)NULL; + numberoftetrahedra = 0; + numberofcorners = 4; + numberoftetrahedronattributes = 0; + + trifacelist = (int*)NULL; + trifacemarkerlist = (int*)NULL; + o2facelist = (int*)NULL; + face2tetlist = (int*)NULL; + face2edgelist = (int*)NULL; + numberoftrifaces = 0; + + edgelist = (int*)NULL; + edgemarkerlist = (int*)NULL; + o2edgelist = (int*)NULL; + edge2tetlist = (int*)NULL; + numberofedges = 0; + + facetlist = (facet*)NULL; + facetmarkerlist = (int*)NULL; + numberoffacets = 0; + + holelist = (REAL*)NULL; + numberofholes = 0; + + regionlist = (REAL*)NULL; + numberofregions = 0; + + refine_elem_list = (int*)NULL; + refine_elem_vol_list = (REAL*)NULL; + numberofrefineelems = 0; + + facetconstraintlist = (REAL*)NULL; + numberoffacetconstraints = 0; + segmentconstraintlist = (REAL*)NULL; + numberofsegmentconstraints = 0; + + vpointlist = (REAL*)NULL; + vedgelist = (voroedge*)NULL; + vfacetlist = (vorofacet*)NULL; + vcelllist = (int**)NULL; + numberofvpoints = 0; + numberofvedges = 0; + numberofvfacets = 0; + numberofvcells = 0; + + tetunsuitable = NULL; + + geomhandle = NULL; + getvertexparamonedge = NULL; + getsteineronedge = NULL; + getvertexparamonface = NULL; + getedgesteinerparamonface = NULL; + getsteineronface = NULL; } - if (facetlist != (facet *) NULL) { - facet *f; - polygon *p; - for (i = 0; i < numberoffacets; i++) { - f = &facetlist[i]; - for (j = 0; j < f->numberofpolygons; j++) { - p = &f->polygonlist[j]; - delete [] p->vertexlist; + // Free the memory allocated in 'tetgenio'. Note that it assumes that the + // memory was allocated by the "new" operator (C++). + void clean_memory() + { + int i, j; + + if (pointlist != (REAL*)NULL) + { + delete[] pointlist; } - delete [] f->polygonlist; - if (f->holelist != (REAL *) NULL) { - delete [] f->holelist; + if (pointattributelist != (REAL*)NULL) + { + delete[] pointattributelist; + } + if (pointmtrlist != (REAL*)NULL) + { + delete[] pointmtrlist; + } + if (pointmarkerlist != (int*)NULL) + { + delete[] pointmarkerlist; + } + if (point2tetlist != (int*)NULL) + { + delete[] point2tetlist; + } + if (pointparamlist != (pointparam*)NULL) + { + delete[] pointparamlist; } - } - delete [] facetlist; - } - if (facetmarkerlist != (int *) NULL) { - delete [] facetmarkerlist; - } - if (holelist != (REAL *) NULL) { - delete [] holelist; - } - if (regionlist != (REAL *) NULL) { - delete [] regionlist; - } - if (refine_elem_list != (int *) NULL) { - delete [] refine_elem_list; - if (refine_elem_vol_list != (REAL *) NULL) { - delete [] refine_elem_vol_list; - } - } - if (facetconstraintlist != (REAL *) NULL) { - delete [] facetconstraintlist; - } - if (segmentconstraintlist != (REAL *) NULL) { - delete [] segmentconstraintlist; - } - if (vpointlist != (REAL *) NULL) { - delete [] vpointlist; - } - if (vedgelist != (voroedge *) NULL) { - delete [] vedgelist; - } - if (vfacetlist != (vorofacet *) NULL) { - for (i = 0; i < numberofvfacets; i++) { - delete [] vfacetlist[i].elist; - } - delete [] vfacetlist; - } - if (vcelllist != (int **) NULL) { - for (i = 0; i < numberofvcells; i++) { - delete [] vcelllist[i]; - } - delete [] vcelllist; + if (tetrahedronlist != (int*)NULL) + { + delete[] tetrahedronlist; + } + if (tetrahedronattributelist != (REAL*)NULL) + { + delete[] tetrahedronattributelist; + } + if (tetrahedronvolumelist != (REAL*)NULL) + { + delete[] tetrahedronvolumelist; + } + if (neighborlist != (int*)NULL) + { + delete[] neighborlist; + } + if (tet2facelist != (int*)NULL) + { + delete[] tet2facelist; + } + if (tet2edgelist != (int*)NULL) + { + delete[] tet2edgelist; + } + + if (trifacelist != (int*)NULL) + { + delete[] trifacelist; + } + if (trifacemarkerlist != (int*)NULL) + { + delete[] trifacemarkerlist; + } + if (o2facelist != (int*)NULL) + { + delete[] o2facelist; + } + if (face2tetlist != (int*)NULL) + { + delete[] face2tetlist; + } + if (face2edgelist != (int*)NULL) + { + delete[] face2edgelist; + } + + if (edgelist != (int*)NULL) + { + delete[] edgelist; + } + if (edgemarkerlist != (int*)NULL) + { + delete[] edgemarkerlist; + } + if (o2edgelist != (int*)NULL) + { + delete[] o2edgelist; + } + if (edge2tetlist != (int*)NULL) + { + delete[] edge2tetlist; + } + + if (facetlist != (facet*)NULL) + { + facet* f; + polygon* p; + for (i = 0; i < numberoffacets; i++) + { + f = &facetlist[i]; + for (j = 0; j < f->numberofpolygons; j++) + { + p = &f->polygonlist[j]; + delete[] p->vertexlist; + } + delete[] f->polygonlist; + if (f->holelist != (REAL*)NULL) + { + delete[] f->holelist; + } + } + delete[] facetlist; + } + if (facetmarkerlist != (int*)NULL) + { + delete[] facetmarkerlist; + } + + if (holelist != (REAL*)NULL) + { + delete[] holelist; + } + if (regionlist != (REAL*)NULL) + { + delete[] regionlist; + } + if (refine_elem_list != (int*)NULL) + { + delete[] refine_elem_list; + if (refine_elem_vol_list != (REAL*)NULL) + { + delete[] refine_elem_vol_list; + } + } + if (facetconstraintlist != (REAL*)NULL) + { + delete[] facetconstraintlist; + } + if (segmentconstraintlist != (REAL*)NULL) + { + delete[] segmentconstraintlist; + } + if (vpointlist != (REAL*)NULL) + { + delete[] vpointlist; + } + if (vedgelist != (voroedge*)NULL) + { + delete[] vedgelist; + } + if (vfacetlist != (vorofacet*)NULL) + { + for (i = 0; i < numberofvfacets; i++) + { + delete[] vfacetlist[i].elist; + } + delete[] vfacetlist; + } + if (vcelllist != (int**)NULL) + { + for (i = 0; i < numberofvcells; i++) + { + delete[] vcelllist[i]; + } + delete[] vcelllist; + } } - } - // Constructor & destructor. - tetgenio() {initialize();} - ~tetgenio() {clean_memory();} + // Constructor & destructor. + tetgenio() { initialize(); } + ~tetgenio() { clean_memory(); } -}; // class tetgenio +}; // class tetgenio //============================================================================// // // @@ -612,230 +653,226 @@ class tetgenio { // // //============================================================================// -class tetgenbehavior { +class tetgenbehavior +{ public: + // Switches of TetGen. + int plc; // '-p', 0. + int psc; // '-s', 0. + int refine; // '-r', 0. + int quality; // '-q', 0. + int nobisect; // '-Y', 0. + int cdt; // '-D', 0. + int cdtrefine; // '-D#', 7. + int coarsen; // '-R', 0. + int weighted; // '-w', 0. + int brio_hilbert; // '-b', 1. + int flipinsert; // '-L', 0. + int metric; // '-m', 0. + int varvolume; // '-a', 0. + int fixedvolume; // '-a', 0. + int regionattrib; // '-A', 0. + int insertaddpoints; // '-i', 0. + int diagnose; // '-d', 0. + int convex; // '-c', 0. + int nomergefacet; // '-M', 0. + int nomergevertex; // '-M', 0. + int noexact; // '-X', 0. + int nostaticfilter; // '-X', 0. + int zeroindex; // '-z', 0. + int facesout; // '-f', 0. + int edgesout; // '-e', 0. + int neighout; // '-n', 0. + int voroout; // '-v', 0. + int meditview; // '-g', 0. + int vtkview; // '-k', 0. + int vtksurfview; // '-k', 0. + int nobound; // '-B', 0. + int nonodewritten; // '-N', 0. + int noelewritten; // '-E', 0. + int nofacewritten; // '-F', 0. + int noiterationnum; // '-I', 0. + int nojettison; // '-J', 0. + int docheck; // '-C', 0. + int quiet; // '-Q', 0. + int nowarning; // '-W', 0. + int verbose; // '-V', 0. + + // Parameters of TetGen. + int vertexperblock; // '-x', 4092. + int tetrahedraperblock; // '-x', 8188. + int shellfaceperblock; // '-x', 2044. + int supsteiner_level; // '-Y/', 2. + int addsteiner_algo; // '-Y//', 1. + int coarsen_param; // '-R', 0. + int weighted_param; // '-w', 0. + int fliplinklevel; // -1. + int flipstarsize; // -1. + int fliplinklevelinc; // 1. + int opt_max_flip_level; // '-O', 3. + int opt_scheme; // '-O/#', 7. + int opt_iterations; // -O//#, 3. + int smooth_cirterion; // -s, 1. + int smooth_maxiter; // -s, 7. + int delmaxfliplevel; // 1. + int order; // '-o', 1. + int reversetetori; // '-o/', 0. + int steinerleft; // '-S', 0. + int unflip_queue_limit; // '-U#', 1000. + int no_sort; // 0. + int hilbert_order; // '-b///', 52. + int hilbert_limit; // '-b//' 8. + int brio_threshold; // '-b' 64. + REAL brio_ratio; // '-b/' 0.125. + REAL epsilon; // '-T', 1.0e-8. + REAL facet_separate_ang_tol; // '-p', 179.9. + REAL collinear_ang_tol; // '-p/', 179.9. + REAL facet_small_ang_tol; // '-p//', 15.0. + REAL maxvolume; // '-a', -1.0. + REAL maxvolume_length; // '-a', -1.0. + REAL minratio; // '-q', 0.0. + REAL opt_max_asp_ratio; // 1000.0. + REAL opt_max_edge_ratio; // 100.0. + REAL mindihedral; // '-q', 5.0. + REAL optmaxdihedral; // -o/# 177.0. + REAL metric_scale; // -m#, 1.0. + REAL smooth_alpha; // '-s', 0.3. + REAL coarsen_percent; // -R1/#, 1.0. + REAL elem_growth_ratio; // Growth ratio of # elements, -r#, 0.0. + REAL refine_progress_ratio; // -r/#, 0.333. + + // Strings of command line arguments and input/output file names. + char commandline[1024]; + char infilename[1024]; + char outfilename[1024]; + char addinfilename[1024]; + char bgmeshfilename[1024]; + + // Read an additional tetrahedral mesh and treat it as holes [2018-07-30]. + int hole_mesh; // '-H', 0. + char hole_mesh_filename[1024]; + + // The input object of TetGen. They are recognized by either the input + // file extensions or by the specified options. + // Currently the following objects are supported: + // - NODES, a list of nodes (.node); + // - POLY, a piecewise linear complex (.poly or .smesh); + // - OFF, a polyhedron (.off, Geomview's file format); + // - PLY, a polyhedron (.ply, file format from gatech, only ASCII); + // - STL, a surface mesh (.stl, stereolithography format); + // - MEDIT, a surface mesh (.mesh, Medit's file format); + // - MESH, a tetrahedral mesh (.ele). + // If no extension is available, the imposed command line switch + // (-p or -r) implies the object. + enum objecttype { NODES, POLY, OFF, PLY, STL, MEDIT, VTK, MESH, NEU_MESH } object; + + void syntax(); + void usage(); + + // Command line parse routine. + bool parse_commandline(int argc, char** argv); + bool parse_commandline(char* switches) { return parse_commandline(0, &switches); } + + // Initialize all variables. + tetgenbehavior() + { + plc = 0; + psc = 0; + refine = 0; + quality = 0; + nobisect = 0; + cdt = 0; // set by -D (without a number following it) + cdtrefine = 7; // default, set by -D# + coarsen = 0; + metric = 0; + weighted = 0; + brio_hilbert = 1; + flipinsert = 0; + varvolume = 0; + fixedvolume = 0; + noexact = 0; + nostaticfilter = 0; + insertaddpoints = 0; + regionattrib = 0; + diagnose = 0; + convex = 0; + zeroindex = 0; + facesout = 0; + edgesout = 0; + neighout = 0; + voroout = 0; + meditview = 0; + vtkview = 0; + vtksurfview = 0; + nobound = 0; + nonodewritten = 0; + noelewritten = 0; + nofacewritten = 0; + noiterationnum = 0; + nomergefacet = 0; + nomergevertex = 0; + nojettison = 0; + docheck = 0; + quiet = 0; + nowarning = 0; + verbose = 0; + + vertexperblock = 4092; + tetrahedraperblock = 8188; + shellfaceperblock = 4092; + supsteiner_level = 2; + addsteiner_algo = 1; + coarsen_param = 0; + weighted_param = 0; + fliplinklevel = -1; + flipstarsize = -1; + fliplinklevelinc = 1; + opt_scheme = 7; + opt_max_flip_level = 3; + opt_iterations = 3; + delmaxfliplevel = 1; + order = 1; + reversetetori = 0; + steinerleft = -1; + unflip_queue_limit = 1000; + no_sort = 0; + hilbert_order = 52; //-1; + hilbert_limit = 8; + brio_threshold = 64; + brio_ratio = 0.125; + facet_separate_ang_tol = 179.9; + collinear_ang_tol = 179.9; + facet_small_ang_tol = 15.0; + maxvolume = -1.0; + maxvolume_length = -1.0; + minratio = 2.0; + opt_max_asp_ratio = 1000.; + opt_max_edge_ratio = 100.; + mindihedral = 3.5; + optmaxdihedral = 177.00; + epsilon = 1.0e-8; + coarsen_percent = 1.0; + metric_scale = 1.0; // -m# + elem_growth_ratio = 0.0; // -r# + refine_progress_ratio = 0.333; // -r/# + object = NODES; + + smooth_cirterion = 3; // -s# default smooth surface and volume vertices. + smooth_maxiter = 7; // set by -s#/7 + smooth_alpha = 0.3; // relax parameter, set by -s#/#/0.3 + + commandline[0] = '\0'; + infilename[0] = '\0'; + outfilename[0] = '\0'; + addinfilename[0] = '\0'; + bgmeshfilename[0] = '\0'; + + hole_mesh = 0; + hole_mesh_filename[0] = '\0'; + } - // Switches of TetGen. - int plc; // '-p', 0. - int psc; // '-s', 0. - int refine; // '-r', 0. - int quality; // '-q', 0. - int nobisect; // '-Y', 0. - int cdt; // '-D', 0. - int cdtrefine; // '-D#', 7. - int coarsen; // '-R', 0. - int weighted; // '-w', 0. - int brio_hilbert; // '-b', 1. - int flipinsert; // '-L', 0. - int metric; // '-m', 0. - int varvolume; // '-a', 0. - int fixedvolume; // '-a', 0. - int regionattrib; // '-A', 0. - int insertaddpoints; // '-i', 0. - int diagnose; // '-d', 0. - int convex; // '-c', 0. - int nomergefacet; // '-M', 0. - int nomergevertex; // '-M', 0. - int noexact; // '-X', 0. - int nostaticfilter; // '-X', 0. - int zeroindex; // '-z', 0. - int facesout; // '-f', 0. - int edgesout; // '-e', 0. - int neighout; // '-n', 0. - int voroout; // '-v', 0. - int meditview; // '-g', 0. - int vtkview; // '-k', 0. - int vtksurfview; // '-k', 0. - int nobound; // '-B', 0. - int nonodewritten; // '-N', 0. - int noelewritten; // '-E', 0. - int nofacewritten; // '-F', 0. - int noiterationnum; // '-I', 0. - int nojettison; // '-J', 0. - int docheck; // '-C', 0. - int quiet; // '-Q', 0. - int nowarning; // '-W', 0. - int verbose; // '-V', 0. - - // Parameters of TetGen. - int vertexperblock; // '-x', 4092. - int tetrahedraperblock; // '-x', 8188. - int shellfaceperblock; // '-x', 2044. - int supsteiner_level; // '-Y/', 2. - int addsteiner_algo; // '-Y//', 1. - int coarsen_param; // '-R', 0. - int weighted_param; // '-w', 0. - int fliplinklevel; // -1. - int flipstarsize; // -1. - int fliplinklevelinc; // 1. - int opt_max_flip_level; // '-O', 3. - int opt_scheme; // '-O/#', 7. - int opt_iterations; // -O//#, 3. - int smooth_cirterion; // -s, 1. - int smooth_maxiter; // -s, 7. - int delmaxfliplevel; // 1. - int order; // '-o', 1. - int reversetetori; // '-o/', 0. - int steinerleft; // '-S', 0. - int unflip_queue_limit; // '-U#', 1000. - int no_sort; // 0. - int hilbert_order; // '-b///', 52. - int hilbert_limit; // '-b//' 8. - int brio_threshold; // '-b' 64. - REAL brio_ratio; // '-b/' 0.125. - REAL epsilon; // '-T', 1.0e-8. - REAL facet_separate_ang_tol; // '-p', 179.9. - REAL collinear_ang_tol; // '-p/', 179.9. - REAL facet_small_ang_tol; // '-p//', 15.0. - REAL maxvolume; // '-a', -1.0. - REAL maxvolume_length; // '-a', -1.0. - REAL minratio; // '-q', 0.0. - REAL opt_max_asp_ratio; // 1000.0. - REAL opt_max_edge_ratio; // 100.0. - REAL mindihedral; // '-q', 5.0. - REAL optmaxdihedral; // -o/# 177.0. - REAL metric_scale; // -m#, 1.0. - REAL smooth_alpha; // '-s', 0.3. - REAL coarsen_percent; // -R1/#, 1.0. - REAL elem_growth_ratio; // Growth ratio of # elements, -r#, 0.0. - REAL refine_progress_ratio; // -r/#, 0.333. - - // Strings of command line arguments and input/output file names. - char commandline[1024]; - char infilename[1024]; - char outfilename[1024]; - char addinfilename[1024]; - char bgmeshfilename[1024]; - - // Read an additional tetrahedral mesh and treat it as holes [2018-07-30]. - int hole_mesh; // '-H', 0. - char hole_mesh_filename[1024]; - - // The input object of TetGen. They are recognized by either the input - // file extensions or by the specified options. - // Currently the following objects are supported: - // - NODES, a list of nodes (.node); - // - POLY, a piecewise linear complex (.poly or .smesh); - // - OFF, a polyhedron (.off, Geomview's file format); - // - PLY, a polyhedron (.ply, file format from gatech, only ASCII); - // - STL, a surface mesh (.stl, stereolithography format); - // - MEDIT, a surface mesh (.mesh, Medit's file format); - // - MESH, a tetrahedral mesh (.ele). - // If no extension is available, the imposed command line switch - // (-p or -r) implies the object. - enum objecttype {NODES, POLY, OFF, PLY, STL, MEDIT, VTK, MESH, NEU_MESH} object; - - - void syntax(); - void usage(); - - // Command line parse routine. - bool parse_commandline(int argc, char **argv); - bool parse_commandline(char *switches) { - return parse_commandline(0, &switches); - } - - // Initialize all variables. - tetgenbehavior() - { - plc = 0; - psc = 0; - refine = 0; - quality = 0; - nobisect = 0; - cdt = 0; // set by -D (without a number following it) - cdtrefine = 7; // default, set by -D# - coarsen = 0; - metric = 0; - weighted = 0; - brio_hilbert = 1; - flipinsert = 0; - varvolume = 0; - fixedvolume = 0; - noexact = 0; - nostaticfilter = 0; - insertaddpoints = 0; - regionattrib = 0; - diagnose = 0; - convex = 0; - zeroindex = 0; - facesout = 0; - edgesout = 0; - neighout = 0; - voroout = 0; - meditview = 0; - vtkview = 0; - vtksurfview = 0; - nobound = 0; - nonodewritten = 0; - noelewritten = 0; - nofacewritten = 0; - noiterationnum = 0; - nomergefacet = 0; - nomergevertex = 0; - nojettison = 0; - docheck = 0; - quiet = 0; - nowarning = 0; - verbose = 0; - - vertexperblock = 4092; - tetrahedraperblock = 8188; - shellfaceperblock = 4092; - supsteiner_level = 2; - addsteiner_algo = 1; - coarsen_param = 0; - weighted_param = 0; - fliplinklevel = -1; - flipstarsize = -1; - fliplinklevelinc = 1; - opt_scheme = 7; - opt_max_flip_level = 3; - opt_iterations = 3; - delmaxfliplevel = 1; - order = 1; - reversetetori = 0; - steinerleft = -1; - unflip_queue_limit = 1000; - no_sort = 0; - hilbert_order = 52; //-1; - hilbert_limit = 8; - brio_threshold = 64; - brio_ratio = 0.125; - facet_separate_ang_tol = 179.9; - collinear_ang_tol = 179.9; - facet_small_ang_tol = 15.0; - maxvolume = -1.0; - maxvolume_length = -1.0; - minratio = 2.0; - opt_max_asp_ratio = 1000.; - opt_max_edge_ratio = 100.; - mindihedral = 3.5; - optmaxdihedral = 177.00; - epsilon = 1.0e-8; - coarsen_percent = 1.0; - metric_scale = 1.0; // -m# - elem_growth_ratio = 0.0; // -r# - refine_progress_ratio = 0.333; // -r/# - object = NODES; - - smooth_cirterion = 3; // -s# default smooth surface and volume vertices. - smooth_maxiter = 7; // set by -s#/7 - smooth_alpha = 0.3; // relax parameter, set by -s#/#/0.3 - - commandline[0] = '\0'; - infilename[0] = '\0'; - outfilename[0] = '\0'; - addinfilename[0] = '\0'; - bgmeshfilename[0] = '\0'; - - hole_mesh = 0; - hole_mesh_filename[0] = '\0'; - - } - -}; // class tetgenbehavior +}; // class tetgenbehavior //============================================================================// // // @@ -856,16 +893,13 @@ class tetgenbehavior { void exactinit(int, int, int, REAL, REAL, REAL); -REAL orient3d(REAL *pa, REAL *pb, REAL *pc, REAL *pd); -REAL insphere(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL *pe); -REAL orient4d(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL *pe, - REAL ah, REAL bh, REAL ch, REAL dh, REAL eh); - -REAL orient2dexact(REAL *pa, REAL *pb, REAL *pc); -REAL orient3dexact(REAL *pa, REAL *pb, REAL *pc, REAL *pd); -REAL orient4dexact(REAL* pa, REAL* pb, REAL* pc, REAL* pd, REAL* pe, - REAL ah, REAL bh, REAL ch, REAL dh, REAL eh); +REAL orient3d(REAL* pa, REAL* pb, REAL* pc, REAL* pd); +REAL insphere(REAL* pa, REAL* pb, REAL* pc, REAL* pd, REAL* pe); +REAL orient4d(REAL* pa, REAL* pb, REAL* pc, REAL* pd, REAL* pe, REAL ah, REAL bh, REAL ch, REAL dh, REAL eh); +REAL orient2dexact(REAL* pa, REAL* pb, REAL* pc); +REAL orient3dexact(REAL* pa, REAL* pb, REAL* pc, REAL* pd); +REAL orient4dexact(REAL* pa, REAL* pb, REAL* pc, REAL* pd, REAL* pe, REAL ah, REAL bh, REAL ch, REAL dh, REAL eh); //============================================================================// // // @@ -879,1580 +913,1625 @@ REAL orient4dexact(REAL* pa, REAL* pb, REAL* pc, REAL* pd, REAL* pe, // // //============================================================================// -class tetgenmesh { +class tetgenmesh +{ public: - -//============================================================================// -// // -// Mesh data structure // -// // -// A tetrahedral mesh T of a 3D piecewise linear complex (PLC) X is a 3D // -// simplicial complex whose underlying space is equal to the space of X. T // -// contains a 2D subcomplex S which is a triangular mesh of the boundary of // -// X. S contains a 1D subcomplex L which is a linear mesh of the boundary of // -// S. Faces and edges in S and L are respectively called subfaces and segme- // -// nts to distinguish them from others in T. // -// // -// TetGen uses a tetrahedron-based data structure. It stores tetrahedra and // -// vertices. This data structure is pointer-based. Each tetrahedron contains // -// pointers to its vertices and adjacent tetrahedra. Each vertex holds its x-,// -// y-, z-coordinates, and a pointer to one of the tetrahedra having it. Both // -// tetrahedra and vertices may contain user data. // -// // -// Let T be a tetrahedralization. Each triangular face of T belongs to either // -// two or one tetrahedron. In the latter case, it is an exterior boundary // -// face of T. TetGen attaches tetrahedra (one-to-one) to such faces. All such // -// tetrahedra contain an "infinite vertex" (which has no geometric coordinates// -// ). One can imagine such a vertex lies in 4D space and is visible by all // -// exterior boundary faces simultaneously. This extended set of tetrahedra // -// (including the infinite vertex) becomes a tetrahedralization of a 3-sphere // -// that has no boundary in 3d. It has the nice property that every triangular // -// face is shared by exactly two tetrahedra. // -// // -// The current version of TetGen stores explicitly the subfaces and segments // -// (which are in surface mesh S and the linear mesh L), respectively. Extra // -// pointers are allocated in tetrahedra and subfaces to point each other. // -// // -//============================================================================// - - // The tetrahedron data structure. It includes the following fields: - // - a list of four adjoining tetrahedra; - // - a list of four vertices; - // - a pointer to a list of four subfaces (optional, for -p switch); - // - a pointer to a list of six segments (optional, for -p switch); - // - a list of user-defined floating-point attributes (optional); - // - a volume constraint (optional, for -a switch); - // - an integer of element marker (and flags); - // The structure of a tetrahedron is an array of pointers. Its actual size - // (the length of the array) is determined at runtime. - - typedef REAL **tetrahedron; - - // The subface data structure. It includes the following fields: - // - a list of three adjoining subfaces; - // - a list of three vertices; - // - a list of three adjoining segments; - // - two adjoining tetrahedra; - // - an area constraint (optional, for -q switch); - // - an integer for boundary marker; - // - an integer for type, flags, etc. - - typedef REAL **shellface; - - // The point data structure. It includes the following fields: - // - x, y and z coordinates; - // - a list of user-defined point attributes (optional); - // - u, v coordinates (optional, for -s switch); - // - a metric tensor (optional, for -q or -m switch); - // - a pointer to an adjacent tetrahedron; - // - a pointer to a parent (or a duplicate) point; - // - a pointer to an adjacent subface or segment (optional, -p switch); - // - a pointer to a tet in background mesh (optional, for -m switch); - // - an integer for boundary marker (point index); - // - an integer for point type (and flags). - // - an integer for geometry tag (optional, for -s switch). - // The structure of a point is an array of REALs. Its acutal size is - // determined at the runtime. - - typedef REAL *point; - -//============================================================================// -// // -// Handles // -// // -// Navigation and manipulation in a tetrahedralization are accomplished by // -// operating on structures referred as ``handles". A handle is a pair (t,v), // -// where t is a pointer to a tetrahedron, and v is a 4-bit integer, in the // -// range from 0 to 11. v is called the ``version'' of a tetrahedron, it rep- // -// resents a directed edge of a specific face of the tetrahedron. // -// // -// There are 12 even permutations of the four vertices, each of them corres- // -// ponds to a directed edge (a version) of the tetrahedron. The 12 versions // -// can be grouped into 4 distinct ``edge rings'' in 4 ``oriented faces'' of // -// this tetrahedron. One can encode each version (a directed edge) into a // -// 4-bit integer such that the two upper bits encode the index (from 0 to 2) // -// of this edge in the edge ring, and the two lower bits encode the index ( // -// from 0 to 3) of the oriented face which contains this edge. // -// // -// The four vertices of a tetrahedron are indexed from 0 to 3 (according to // -// their storage in the data structure). Give each face the same index as // -// the node opposite it in the tetrahedron. Denote the edge connecting face // -// i to face j as i/j. We number the twelve versions as follows: // -// // -// | edge 0 edge 1 edge 2 // -// --------|-------------------------------- // -// face 0 | 0 (0/1) 4 (0/3) 8 (0/2) // -// face 1 | 1 (1/2) 5 (1/3) 9 (1/0) // -// face 2 | 2 (2/3) 6 (2/1) 10 (2/0) // -// face 3 | 3 (3/0) 7 (3/1) 11 (3/2) // -// // -// Similarly, navigation and manipulation in a (boundary) triangulation are // -// done by using handles of triangles. Each handle is a pair (s, v), where s // -// is a pointer to a triangle, and v is a version in the range from 0 to 5. // -// Each version corresponds to a directed edge of this triangle. // -// // -// Number the three vertices of a triangle from 0 to 2 (according to their // -// storage in the data structure). Give each edge the same index as the node // -// opposite it in the triangle. The six versions of a triangle are: // -// // -// | edge 0 edge 1 edge 2 // -// ---------------|-------------------------- // -// ccw orieation | 0 2 4 // -// cw orieation | 1 3 5 // -// // -// In the following, a 'triface' is a handle of tetrahedron, and a 'face' is // -// a handle of a triangle. // -// // -//============================================================================// - - class triface { - public: - tetrahedron *tet; - int ver; // Range from 0 to 11. - triface() : tet(0), ver(0) {} - triface& operator=(const triface& t) { - tet = t.tet; ver = t.ver; - return *this; - } - }; - - class face { - public: - shellface *sh; - int shver; // Range from 0 to 5. - face() : sh(0), shver(0) {} - face& operator=(const face& s) { - sh = s.sh; shver = s.shver; - return *this; - } - }; - -//============================================================================// -// // -// Arraypool // -// // -// A dynamic linear array. (It is written by J. Shewchuk) // -// // -// Each arraypool contains an array of pointers to a number of blocks. Each // -// block contains the same fixed number of objects. Each index of the array // -// addresses a particular object in the pool. The most significant bits add- // -// ress the index of the block containing the object. The less significant // -// bits address this object within the block. // -// // -// 'objectbytes' is the size of one object in blocks; 'log2objectsperblock' // -// is the base-2 logarithm of 'objectsperblock'; 'objects' counts the number // -// of allocated objects; 'totalmemory' is the total memory in bytes. // -// // -//============================================================================// - - class arraypool { - - public: - - int objectbytes; - int objectsperblock; - int log2objectsperblock; - int objectsperblockmark; - int toparraylen; - char **toparray; - long objects; - unsigned long totalmemory; - - void restart(); - void poolinit(int sizeofobject, int log2objperblk); - char* getblock(int objectindex); - void* lookup(int objectindex); - int newindex(void **newptr); - - arraypool(int sizeofobject, int log2objperblk); - ~arraypool(); - }; - -// fastlookup() -- A fast, unsafe operation. Return the pointer to the object -// with a given index. Note: The object's block must have been allocated, -// i.e., by the function newindex(). + //============================================================================// + // // + // Mesh data structure // + // // + // A tetrahedral mesh T of a 3D piecewise linear complex (PLC) X is a 3D // + // simplicial complex whose underlying space is equal to the space of X. T // + // contains a 2D subcomplex S which is a triangular mesh of the boundary of // + // X. S contains a 1D subcomplex L which is a linear mesh of the boundary of // + // S. Faces and edges in S and L are respectively called subfaces and segme- // + // nts to distinguish them from others in T. // + // // + // TetGen uses a tetrahedron-based data structure. It stores tetrahedra and // + // vertices. This data structure is pointer-based. Each tetrahedron contains // + // pointers to its vertices and adjacent tetrahedra. Each vertex holds its x-,// + // y-, z-coordinates, and a pointer to one of the tetrahedra having it. Both // + // tetrahedra and vertices may contain user data. // + // // + // Let T be a tetrahedralization. Each triangular face of T belongs to either // + // two or one tetrahedron. In the latter case, it is an exterior boundary // + // face of T. TetGen attaches tetrahedra (one-to-one) to such faces. All such // + // tetrahedra contain an "infinite vertex" (which has no geometric coordinates// + // ). One can imagine such a vertex lies in 4D space and is visible by all // + // exterior boundary faces simultaneously. This extended set of tetrahedra // + // (including the infinite vertex) becomes a tetrahedralization of a 3-sphere // + // that has no boundary in 3d. It has the nice property that every triangular // + // face is shared by exactly two tetrahedra. // + // // + // The current version of TetGen stores explicitly the subfaces and segments // + // (which are in surface mesh S and the linear mesh L), respectively. Extra // + // pointers are allocated in tetrahedra and subfaces to point each other. // + // // + //============================================================================// + + // The tetrahedron data structure. It includes the following fields: + // - a list of four adjoining tetrahedra; + // - a list of four vertices; + // - a pointer to a list of four subfaces (optional, for -p switch); + // - a pointer to a list of six segments (optional, for -p switch); + // - a list of user-defined floating-point attributes (optional); + // - a volume constraint (optional, for -a switch); + // - an integer of element marker (and flags); + // The structure of a tetrahedron is an array of pointers. Its actual size + // (the length of the array) is determined at runtime. + + typedef REAL** tetrahedron; + + // The subface data structure. It includes the following fields: + // - a list of three adjoining subfaces; + // - a list of three vertices; + // - a list of three adjoining segments; + // - two adjoining tetrahedra; + // - an area constraint (optional, for -q switch); + // - an integer for boundary marker; + // - an integer for type, flags, etc. + + typedef REAL** shellface; + + // The point data structure. It includes the following fields: + // - x, y and z coordinates; + // - a list of user-defined point attributes (optional); + // - u, v coordinates (optional, for -s switch); + // - a metric tensor (optional, for -q or -m switch); + // - a pointer to an adjacent tetrahedron; + // - a pointer to a parent (or a duplicate) point; + // - a pointer to an adjacent subface or segment (optional, -p switch); + // - a pointer to a tet in background mesh (optional, for -m switch); + // - an integer for boundary marker (point index); + // - an integer for point type (and flags). + // - an integer for geometry tag (optional, for -s switch). + // The structure of a point is an array of REALs. Its acutal size is + // determined at the runtime. + + typedef REAL* point; + + //============================================================================// + // // + // Handles // + // // + // Navigation and manipulation in a tetrahedralization are accomplished by // + // operating on structures referred as ``handles". A handle is a pair (t,v), // + // where t is a pointer to a tetrahedron, and v is a 4-bit integer, in the // + // range from 0 to 11. v is called the ``version'' of a tetrahedron, it rep- // + // resents a directed edge of a specific face of the tetrahedron. // + // // + // There are 12 even permutations of the four vertices, each of them corres- // + // ponds to a directed edge (a version) of the tetrahedron. The 12 versions // + // can be grouped into 4 distinct ``edge rings'' in 4 ``oriented faces'' of // + // this tetrahedron. One can encode each version (a directed edge) into a // + // 4-bit integer such that the two upper bits encode the index (from 0 to 2) // + // of this edge in the edge ring, and the two lower bits encode the index ( // + // from 0 to 3) of the oriented face which contains this edge. // + // // + // The four vertices of a tetrahedron are indexed from 0 to 3 (according to // + // their storage in the data structure). Give each face the same index as // + // the node opposite it in the tetrahedron. Denote the edge connecting face // + // i to face j as i/j. We number the twelve versions as follows: // + // // + // | edge 0 edge 1 edge 2 // + // --------|-------------------------------- // + // face 0 | 0 (0/1) 4 (0/3) 8 (0/2) // + // face 1 | 1 (1/2) 5 (1/3) 9 (1/0) // + // face 2 | 2 (2/3) 6 (2/1) 10 (2/0) // + // face 3 | 3 (3/0) 7 (3/1) 11 (3/2) // + // // + // Similarly, navigation and manipulation in a (boundary) triangulation are // + // done by using handles of triangles. Each handle is a pair (s, v), where s // + // is a pointer to a triangle, and v is a version in the range from 0 to 5. // + // Each version corresponds to a directed edge of this triangle. // + // // + // Number the three vertices of a triangle from 0 to 2 (according to their // + // storage in the data structure). Give each edge the same index as the node // + // opposite it in the triangle. The six versions of a triangle are: // + // // + // | edge 0 edge 1 edge 2 // + // ---------------|-------------------------- // + // ccw orieation | 0 2 4 // + // cw orieation | 1 3 5 // + // // + // In the following, a 'triface' is a handle of tetrahedron, and a 'face' is // + // a handle of a triangle. // + // // + //============================================================================// + + class triface + { + public: + tetrahedron* tet; + int ver; // Range from 0 to 11. + triface() : tet(0), ver(0) {} + triface& operator=(const triface& t) + { + tet = t.tet; + ver = t.ver; + return *this; + } + }; + + class face + { + public: + shellface* sh; + int shver; // Range from 0 to 5. + face() : sh(0), shver(0) {} + face& operator=(const face& s) + { + sh = s.sh; + shver = s.shver; + return *this; + } + }; + + //============================================================================// + // // + // Arraypool // + // // + // A dynamic linear array. (It is written by J. Shewchuk) // + // // + // Each arraypool contains an array of pointers to a number of blocks. Each // + // block contains the same fixed number of objects. Each index of the array // + // addresses a particular object in the pool. The most significant bits add- // + // ress the index of the block containing the object. The less significant // + // bits address this object within the block. // + // // + // 'objectbytes' is the size of one object in blocks; 'log2objectsperblock' // + // is the base-2 logarithm of 'objectsperblock'; 'objects' counts the number // + // of allocated objects; 'totalmemory' is the total memory in bytes. // + // // + //============================================================================// + + class arraypool + { + + public: + int objectbytes; + int objectsperblock; + int log2objectsperblock; + int objectsperblockmark; + int toparraylen; + char** toparray; + long objects; + unsigned long totalmemory; + + void restart(); + void poolinit(int sizeofobject, int log2objperblk); + char* getblock(int objectindex); + void* lookup(int objectindex); + int newindex(void** newptr); + + arraypool(int sizeofobject, int log2objperblk); + ~arraypool(); + }; + + // fastlookup() -- A fast, unsafe operation. Return the pointer to the object + // with a given index. Note: The object's block must have been allocated, + // i.e., by the function newindex(). #define fastlookup(pool, index) \ - (void *) ((pool)->toparray[(index) >> (pool)->log2objectsperblock] + \ - ((index) & (pool)->objectsperblockmark) * (pool)->objectbytes) - -//============================================================================// -// // -// Memorypool // -// // -// A structure for memory allocation. (It is written by J. Shewchuk) // -// // -// firstblock is the first block of items. nowblock is the block from which // -// items are currently being allocated. nextitem points to the next slab // -// of free memory for an item. deaditemstack is the head of a linked list // -// (stack) of deallocated items that can be recycled. unallocateditems is // -// the number of items that remain to be allocated from nowblock. // -// // -// Traversal is the process of walking through the entire list of items, and // -// is separate from allocation. Note that a traversal will visit items on // -// the "deaditemstack" stack as well as live items. pathblock points to // -// the block currently being traversed. pathitem points to the next item // -// to be traversed. pathitemsleft is the number of items that remain to // -// be traversed in pathblock. // -// // -//============================================================================// - - class memorypool { - - public: - - void **firstblock, **nowblock; - void *nextitem; - void *deaditemstack; - void **pathblock; - void *pathitem; - int alignbytes; - int itembytes, itemwords; - int itemsperblock; - long items, maxitems; - int unallocateditems; - int pathitemsleft; - - memorypool(); - memorypool(int, int, int, int); - ~memorypool(); - - void poolinit(int, int, int, int); - void restart(); - void *alloc(); - void dealloc(void*); - void traversalinit(); - void *traverse(); - }; - -//============================================================================// -// // -// badface // -// // -// Despite of its name, a 'badface' can be used to represent one of the // -// following objects: // -// - a face of a tetrahedron which is (possibly) non-Delaunay; // -// - an encroached subsegment or subface; // -// - a bad-quality tetrahedron, i.e, has too large radius-edge ratio; // -// - a sliver, i.e., has good radius-edge ratio but nearly zero volume; // -// - a recently flipped face (saved for undoing the flip later). // -// // -//============================================================================// - - class badface { - public: - triface tt; - face ss; - REAL key, cent[6]; // circumcenter or cos(dihedral angles) at 6 edges. - point forg, fdest, fapex, foppo, noppo; - badface *nextitem; - badface() : key(0), forg(0), fdest(0), fapex(0), foppo(0), noppo(0), - nextitem(0) {} - void init() { - key = 0.; - for (int k = 0; k < 6; k++) cent[k] = 0.; - tt.tet = NULL; tt.ver = 0; - ss.sh = NULL; ss.shver = 0; - forg = fdest = fapex = foppo = noppo = NULL; - nextitem = NULL; - } - }; - -//============================================================================// -// // -// insertvertexflags // -// // -// A collection of flags that pass to the routine insertvertex(). // -// // -//============================================================================// - - class insertvertexflags { - - public: - - int iloc; // input/output. - int bowywat, lawson; - int splitbdflag, validflag, respectbdflag; - int rejflag, chkencflag, cdtflag; - int assignmeshsize; - int sloc, sbowywat; - - // Used by Delaunay refinement. - int collect_inial_cavity_flag; - int ignore_near_vertex; - int check_insert_radius; - int refineflag; // 0, 1, 2, 3 - triface refinetet; - face refinesh; - int smlenflag; // for useinsertradius. - REAL smlen; // for useinsertradius. - point parentpt; - - void init() { - iloc = bowywat = lawson = 0; - splitbdflag = validflag = respectbdflag = 0; - rejflag = chkencflag = cdtflag = 0; - assignmeshsize = 0; - sloc = sbowywat = 0; - - collect_inial_cavity_flag = 0; - ignore_near_vertex = 0; - check_insert_radius = 0; - refineflag = 0; - refinetet.tet = NULL; - refinesh.sh = NULL; - smlenflag = 0; - smlen = 0.0; - parentpt = NULL; - } - - insertvertexflags() { - init(); - } - }; - -//============================================================================// -// // -// flipconstraints // -// // -// A structure of a collection of data (options and parameters) which pass // -// to the edge flip function flipnm(). // -// // -//============================================================================// - - class flipconstraints { - - public: - - // Elementary flip flags. - int enqflag; // (= flipflag) - int chkencflag; - - // Control flags - int unflip; // Undo the performed flips. - int collectnewtets; // Collect the new tets created by flips. - int collectencsegflag; - - // Optimization flags. - int noflip_in_surface; // do not flip edges (not segment) in surface. - int remove_ndelaunay_edge; // Remove a non-Delaunay edge. - REAL bak_tetprism_vol; // The value to be minimized. - REAL tetprism_vol_sum; - int remove_large_angle; // Remove a large dihedral angle at edge. - REAL cosdihed_in; // The input cosine of the dihedral angle (> 0). - REAL cosdihed_out; // The improved cosine of the dihedral angle. - REAL max_asp_out; // Max asp ratio after the improvement of dihedral angle. - - // Boundary recovery flags. - int checkflipeligibility; - point seg[2]; // A constraining edge to be recovered. - point fac[3]; // A constraining face to be recovered. - point remvert; // A vertex to be removed. - - - flipconstraints() { - enqflag = 0; - chkencflag = 0; - - unflip = 0; - collectnewtets = 0; - collectencsegflag = 0; - - noflip_in_surface = 0; - remove_ndelaunay_edge = 0; - bak_tetprism_vol = 0.0; - tetprism_vol_sum = 0.0; - remove_large_angle = 0; - cosdihed_in = 0.0; - cosdihed_out = 0.0; - max_asp_out = 0.0; - - checkflipeligibility = 0; - seg[0] = NULL; - fac[0] = NULL; - remvert = NULL; - } - }; - -//============================================================================// -// // -// optparameters // -// // -// Optimization options and parameters. // -// // -//============================================================================// - - class optparameters { - - public: - - // The one of goals of optimization. - int max_min_volume; // Maximize the minimum volume. - int min_max_aspectratio; // Minimize the maximum aspect ratio. - int min_max_dihedangle; // Minimize the maximum dihedral angle. - - // The initial and improved value. - REAL initval, imprval; - - int numofsearchdirs; - REAL searchstep; - int maxiter; // Maximum smoothing iterations (disabled by -1). - int smthiter; // Performed iterations. - - - optparameters() { - max_min_volume = 0; - min_max_aspectratio = 0; - min_max_dihedangle = 0; - - initval = imprval = 0.0; - - numofsearchdirs = 10; - searchstep = 0.01; - maxiter = -1; // Unlimited smoothing iterations. - smthiter = 0; - - } - }; - - -//============================================================================// -// // -// Labels (enumeration declarations) used by TetGen. // -// // -//============================================================================// - - // Labels that signify the type of a vertex. - enum verttype {UNUSEDVERTEX, DUPLICATEDVERTEX, RIDGEVERTEX, /*ACUTEVERTEX,*/ - FACETVERTEX, VOLVERTEX, FREESEGVERTEX, FREEFACETVERTEX, - FREEVOLVERTEX, NREGULARVERTEX, DEADVERTEX}; - - // Labels that signify the result of triangle-triangle intersection test. - enum interresult {DISJOINT, INTERSECT, SHAREVERT, SHAREEDGE, SHAREFACE, - TOUCHEDGE, TOUCHFACE, ACROSSVERT, ACROSSEDGE, ACROSSFACE, - SELF_INTERSECT}; - - // Labels that signify the result of point location. - enum locateresult {UNKNOWN, OUTSIDE, INTETRAHEDRON, ONFACE, ONEDGE, ONVERTEX, - ENCVERTEX, ENCSEGMENT, ENCSUBFACE, NEARVERTEX, NONREGULAR, - INSTAR, BADELEMENT, NULLCAVITY, SHARPCORNER, FENSEDIN, - NONCOPLANAR, SELF_ENCROACH}; - -//============================================================================// -// // -// Variables of TetGen // -// // -//============================================================================// - - // Pointer to the input data (a set of nodes, a PLC, or a mesh). - tetgenio *in, *addin; - - // Pointer to the switches and parameters. - tetgenbehavior *b; - - // Pointer to a background mesh (contains size specification map). - tetgenmesh *bgm; - - // Memorypools to store mesh elements (points, tetrahedra, subfaces, and - // segments) and extra pointers between tetrahedra, subfaces, and segments. - memorypool *tetrahedrons, *subfaces, *subsegs, *points; - memorypool *tet2subpool, *tet2segpool; - - // Memorypools to store bad-quality (or encroached) elements. - memorypool *badtetrahedrons, *badsubfacs, *badsubsegs; - memorypool *split_subfaces_pool, *split_segments_pool; - arraypool *unsplit_badtets, *unsplit_subfaces, *unsplit_segments; - arraypool *check_tets_list; - - badface *stack_enc_segments, *stack_enc_subfaces; - - // Bad quality subfaces are ordered by priority queues. - badface *queuefront[64]; - badface *queuetail[64]; - int nextnonemptyq[64]; - int firstnonemptyq, recentq; - - // Bad quality tetrahedra are ordered by priority queues. - memorypool *badqual_tets_pool; - badface *bt_queuefront[64]; - badface *bt_queuetail[64]; - int bt_nextnonemptyq[64]; - int bt_firstnonemptyq, bt_recentq; - - // A memorypool to store faces to be flipped. - memorypool *flippool; - arraypool *later_unflip_queue, *unflipqueue; - badface *flipstack, *unflip_queue_front, *unflip_queue_tail; - - // Arrays used for point insertion (the Bowyer-Watson algorithm). - arraypool *cavetetlist, *cavebdrylist, *caveoldtetlist; - arraypool *cave_oldtet_list; // only tetrahedron's - arraypool *cavetetshlist, *cavetetseglist, *cavetetvertlist; - arraypool *caveencshlist, *caveencseglist; - arraypool *caveshlist, *caveshbdlist, *cavesegshlist; - triface _bw_faces[4096]; // _bw_faces[64][64]; - - // Stacks used for CDT construction and boundary recovery. - arraypool *subsegstack, *subfacstack, *subvertstack; - arraypool *skipped_segment_list, *skipped_facet_list; - - // Arrays of encroached segments and subfaces (for mesh refinement). - arraypool *encseglist, *encshlist; - - // The map between facets to their vertices (for mesh refinement). - int number_of_facets; - int *idx2facetlist; - point *facetverticeslist; - int *idx_segment_facet_list; // segment-to-facet map. - int *segment_facet_list; - int *idx_ridge_vertex_facet_list; // vertex-to-facet map. - int *ridge_vertex_facet_list; - - // The map between segments to their endpoints (for mesh refinement). - int segmentendpointslist_length; - point *segmentendpointslist; - double *segment_info_list; - int *idx_segment_ridge_vertex_list; // are two ridge vertices form a segment? - point *segment_ridge_vertex_list; - - // The infinite vertex. - point dummypoint; - // The recently visited tetrahedron, subface. - triface recenttet; - face recentsh; - - // PI is the ratio of a circle's circumference to its diameter. - static REAL PI; - - // The list of subdomains. (-A option). - int subdomains; // Number of subdomains. - int *subdomain_markers; - - // Various variables. - int numpointattrib; // Number of point attributes. - int numelemattrib; // Number of tetrahedron attributes. - int sizeoftensor; // Number of REALs per metric tensor. - int pointmtrindex; // Index to find the metric tensor of a point. - int pointparamindex; // Index to find the u,v coordinates of a point. - int point2simindex; // Index to find a simplex adjacent to a point. - int pointmarkindex; // Index to find boundary marker of a point. - int pointinsradiusindex; // Index to find the insertion radius of a point. - int elemattribindex; // Index to find attributes of a tetrahedron. - int polarindex; // Index to find the polar plane parameters. - int volumeboundindex; // Index to find volume bound of a tetrahedron. - int elemmarkerindex; // Index to find marker of a tetrahedron. - int shmarkindex; // Index to find boundary marker of a subface. - int areaboundindex; // Index to find area bound of a subface. - int checksubsegflag; // Are there segments in the tetrahedralization yet? - int checksubfaceflag; // Are there subfaces in the tetrahedralization yet? - int boundary_recovery_flag; - int checkconstraints; // Are there variant (node, seg, facet) constraints? - int nonconvex; // Is current mesh non-convex? - int autofliplinklevel; // The increase of link levels, default is 1. - int useinsertradius; // Save the insertion radius for Steiner points. - long samples; // Number of random samples for point location. - unsigned long randomseed; // Current random number seed. - REAL cosmaxdihed, cosmindihed; // The cosine values of max/min dihedral. - REAL cossmtdihed; // The cosine value of a bad dihedral to be smoothed. - REAL cosslidihed; // The cosine value of the max dihedral of a sliver. - REAL cos_large_dihed; // The cosine value of large dihedral (135 degree). - REAL opt_max_sliver_asp_ratio; // = 10 x b->opt_max_asp_ratio. - REAL minfaceang, minfacetdihed; // The minimum input (dihedral) angles. - REAL cos_facet_separate_ang_tol; - REAL cos_collinear_ang_tol; - REAL tetprism_vol_sum; // The total volume of tetrahedral-prisms (in 4D). - REAL longest; // The longest possible edge length. - REAL minedgelength; // = longest * b->epsion. - REAL xmax, xmin, ymax, ymin, zmax, zmin; // Bounding box of points. - - // Options for mesh refinement. - REAL big_radius_edge_ratio; // calculated by qualitystatistics(). - REAL smallest_insradius; // Save the smallest insertion radius. - long elem_limit; - long insert_point_count; // number of attempted insertions. - long report_refine_progress; // the next report event. - long last_point_count; // number of points after last report event. - long last_insertion_count; // number of insertions after last report event. - - // Counters. - long insegments; // Number of input segments. - long hullsize; // Number of exterior boundary faces. - long meshedges; // Number of mesh edges. - long meshhulledges; // Number of boundary mesh edges. - long steinerleft; // Number of Steiner points not yet used. - long dupverts; // Are there duplicated vertices? - long unuverts; // Are there unused vertices? - long duplicated_facets_count; // Are there duplicated facets.? - long nonregularcount; // Are there non-regular vertices? - long st_segref_count, st_facref_count, st_volref_count; // Steiner points. - long fillregioncount, cavitycount, cavityexpcount; - long flip14count, flip26count, flipn2ncount; - long flip23count, flip32count, flip44count, flip41count; - long flip31count, flip22count; - long opt_flips_count, opt_collapse_count, opt_smooth_count; - long recover_delaunay_count; - unsigned long totalworkmemory; // Total memory used by working arrays. - - -//============================================================================// -// // -// Mesh manipulation primitives // -// // -//============================================================================// - - // Fast lookup tables for mesh manipulation primitives. - static int bondtbl[12][12], fsymtbl[12][12]; - static int esymtbl[12], enexttbl[12], eprevtbl[12]; - static int enextesymtbl[12], eprevesymtbl[12]; - static int eorgoppotbl[12], edestoppotbl[12]; - static int facepivot1[12], facepivot2[12][12]; - static int orgpivot[12], destpivot[12], apexpivot[12], oppopivot[12]; - static int tsbondtbl[12][6], stbondtbl[12][6]; - static int tspivottbl[12][6], stpivottbl[12][6]; - static int ver2edge[12], edge2ver[6], epivot[12]; - static int sorgpivot [6], sdestpivot[6], sapexpivot[6]; - static int snextpivot[6]; - - void inittables(); - - // Primitives for tetrahedra. - inline tetrahedron encode(triface& t); - inline tetrahedron encode2(tetrahedron* ptr, int ver); - inline void decode(tetrahedron ptr, triface& t); - inline tetrahedron* decode_tet_only(tetrahedron ptr); - inline int decode_ver_only(tetrahedron ptr); - inline void bond(triface& t1, triface& t2); - inline void dissolve(triface& t); - inline void esym(triface& t1, triface& t2); - inline void esymself(triface& t); - inline void enext(triface& t1, triface& t2); - inline void enextself(triface& t); - inline void eprev(triface& t1, triface& t2); - inline void eprevself(triface& t); - inline void enextesym(triface& t1, triface& t2); - inline void enextesymself(triface& t); - inline void eprevesym(triface& t1, triface& t2); - inline void eprevesymself(triface& t); - inline void eorgoppo(triface& t1, triface& t2); - inline void eorgoppoself(triface& t); - inline void edestoppo(triface& t1, triface& t2); - inline void edestoppoself(triface& t); - inline void fsym(triface& t1, triface& t2); - inline void fsymself(triface& t); - inline void fnext(triface& t1, triface& t2); - inline void fnextself(triface& t); - inline point org (triface& t); - inline point dest(triface& t); - inline point apex(triface& t); - inline point oppo(triface& t); - inline void setorg (triface& t, point p); - inline void setdest(triface& t, point p); - inline void setapex(triface& t, point p); - inline void setoppo(triface& t, point p); - inline REAL elemattribute(tetrahedron* ptr, int attnum); - inline void setelemattribute(tetrahedron* ptr, int attnum, REAL value); - inline REAL* get_polar(tetrahedron* ptr); - inline REAL get_volume(tetrahedron* ptr); - inline REAL volumebound(tetrahedron* ptr); - inline void setvolumebound(tetrahedron* ptr, REAL value); - inline int elemindex(tetrahedron* ptr); - inline void setelemindex(tetrahedron* ptr, int value); - inline int elemmarker(tetrahedron* ptr); - inline void setelemmarker(tetrahedron* ptr, int value); - inline void infect(triface& t); - inline void uninfect(triface& t); - inline bool infected(triface& t); - inline void marktest(triface& t); - inline void unmarktest(triface& t); - inline bool marktested(triface& t); - inline void markface(triface& t); - inline void unmarkface(triface& t); - inline bool facemarked(triface& t); - inline void markedge(triface& t); - inline void unmarkedge(triface& t); - inline bool edgemarked(triface& t); - inline void marktest2(triface& t); - inline void unmarktest2(triface& t); - inline bool marktest2ed(triface& t); - inline int elemcounter(triface& t); - inline void setelemcounter(triface& t, int value); - inline void increaseelemcounter(triface& t); - inline void decreaseelemcounter(triface& t); - inline bool ishulltet(triface& t); - inline bool isdeadtet(triface& t); - - // Primitives for subfaces and subsegments. - inline void sdecode(shellface sptr, face& s); - inline shellface sencode(face& s); - inline shellface sencode2(shellface *sh, int shver); - inline void spivot(face& s1, face& s2); - inline void spivotself(face& s); - inline void sbond(face& s1, face& s2); - inline void sbond1(face& s1, face& s2); - inline void sdissolve(face& s); - inline point sorg(face& s); - inline point sdest(face& s); - inline point sapex(face& s); - inline void setsorg(face& s, point pointptr); - inline void setsdest(face& s, point pointptr); - inline void setsapex(face& s, point pointptr); - inline void sesym(face& s1, face& s2); - inline void sesymself(face& s); - inline void senext(face& s1, face& s2); - inline void senextself(face& s); - inline void senext2(face& s1, face& s2); - inline void senext2self(face& s); - inline REAL areabound(face& s); - inline void setareabound(face& s, REAL value); - inline int shellmark(face& s); - inline void setshellmark(face& s, int value); - inline void sinfect(face& s); - inline void suninfect(face& s); - inline bool sinfected(face& s); - inline void smarktest(face& s); - inline void sunmarktest(face& s); - inline bool smarktested(face& s); - inline void smarktest2(face& s); - inline void sunmarktest2(face& s); - inline bool smarktest2ed(face& s); - inline void smarktest3(face& s); - inline void sunmarktest3(face& s); - inline bool smarktest3ed(face& s); - inline void setfacetindex(face& f, int value); - inline int getfacetindex(face& f); - inline bool isdeadsh(face& s); - - // Primitives for interacting tetrahedra and subfaces. - inline void tsbond(triface& t, face& s); - inline void tsdissolve(triface& t); - inline void stdissolve(face& s); - inline void tspivot(triface& t, face& s); - inline void stpivot(face& s, triface& t); - - // Primitives for interacting tetrahedra and segments. - inline void tssbond1(triface& t, face& seg); - inline void sstbond1(face& s, triface& t); - inline void tssdissolve1(triface& t); - inline void sstdissolve1(face& s); - inline void tsspivot1(triface& t, face& s); - inline void sstpivot1(face& s, triface& t); - - // Primitives for interacting subfaces and segments. - inline void ssbond(face& s, face& edge); - inline void ssbond1(face& s, face& edge); - inline void ssdissolve(face& s); - inline void sspivot(face& s, face& edge); - - // Primitives for points. - inline int pointmark(point pt); - inline void setpointmark(point pt, int value); - inline enum verttype pointtype(point pt); - inline void setpointtype(point pt, enum verttype value); - inline int pointgeomtag(point pt); - inline void setpointgeomtag(point pt, int value); - inline REAL pointgeomuv(point pt, int i); - inline void setpointgeomuv(point pt, int i, REAL value); - inline void pinfect(point pt); - inline void puninfect(point pt); - inline bool pinfected(point pt); - inline void pmarktest(point pt); - inline void punmarktest(point pt); - inline bool pmarktested(point pt); - inline void pmarktest2(point pt); - inline void punmarktest2(point pt); - inline bool pmarktest2ed(point pt); - inline void pmarktest3(point pt); - inline void punmarktest3(point pt); - inline bool pmarktest3ed(point pt); - inline tetrahedron point2tet(point pt); - inline void setpoint2tet(point pt, tetrahedron value); - inline shellface point2sh(point pt); - inline void setpoint2sh(point pt, shellface value); - inline point point2ppt(point pt); - inline void setpoint2ppt(point pt, point value); - inline tetrahedron point2bgmtet(point pt); - inline void setpoint2bgmtet(point pt, tetrahedron value); - inline void setpointinsradius(point pt, REAL value); - inline REAL getpointinsradius(point pt); - inline bool issteinerpoint(point pt); - - // Advanced primitives. - inline void point2tetorg(point pt, triface& t); - inline void point2shorg(point pa, face& s); - inline point farsorg(face& seg); - inline point farsdest(face& seg); - -//============================================================================// -// // -// Memory managment // -// // -//============================================================================// - - void tetrahedrondealloc(tetrahedron*); - tetrahedron *tetrahedrontraverse(); - tetrahedron *alltetrahedrontraverse(); - void shellfacedealloc(memorypool*, shellface*); - shellface *shellfacetraverse(memorypool*); - void pointdealloc(point); - point pointtraverse(); - - void makeindex2pointmap(point*&); - void makepoint2submap(memorypool*, int*&, face*&); - void maketetrahedron(triface*); - void maketetrahedron2(triface*, point, point, point, point); - void makeshellface(memorypool*, face*); - void makepoint(point*, enum verttype); - - void initializepools(); - -//============================================================================// -// // -// Advanced geometric predicates and calculations // -// // -// the routine insphere_s() implements a simplified symbolic perturbation // -// scheme from Edelsbrunner, et al [*]. Hence the point-in-sphere test never // -// returns a zero. The idea is to perturb the weights of vertices in 4D. // -// // -// The routine tri_edge_test() determines whether or not a triangle and an // -// edge intersect in 3D. If they do cross, their intersection type is also // -// reported. This test is a combination of n 3D orientation tests (3 < n < 9).// -// It uses the robust orient3d() test to make the branch decisions. // -// // -// There are several routines to calculate geometrical quantities, e.g., // -// circumcenters, angles, dihedral angles, face normals, face areas, etc. // -// They are implemented using floating-point arithmetics. // -// // -//============================================================================// - - // Symbolic perturbations (robust) - REAL insphere_s(REAL*, REAL*, REAL*, REAL*, REAL*); - REAL orient4d_s(REAL*, REAL*, REAL*, REAL*, REAL*, - REAL, REAL, REAL, REAL, REAL); - - // An embedded 2-dimensional geometric predicate (non-robust) - REAL incircle3d(point pa, point pb, point pc, point pd); + (void*)((pool)->toparray[(index) >> (pool)->log2objectsperblock] \ + + ((index) & (pool)->objectsperblockmark) * (pool)->objectbytes) + + //============================================================================// + // // + // Memorypool // + // // + // A structure for memory allocation. (It is written by J. Shewchuk) // + // // + // firstblock is the first block of items. nowblock is the block from which // + // items are currently being allocated. nextitem points to the next slab // + // of free memory for an item. deaditemstack is the head of a linked list // + // (stack) of deallocated items that can be recycled. unallocateditems is // + // the number of items that remain to be allocated from nowblock. // + // // + // Traversal is the process of walking through the entire list of items, and // + // is separate from allocation. Note that a traversal will visit items on // + // the "deaditemstack" stack as well as live items. pathblock points to // + // the block currently being traversed. pathitem points to the next item // + // to be traversed. pathitemsleft is the number of items that remain to // + // be traversed in pathblock. // + // // + //============================================================================// + + class memorypool + { + + public: + void **firstblock, **nowblock; + void* nextitem; + void* deaditemstack; + void** pathblock; + void* pathitem; + int alignbytes; + int itembytes, itemwords; + int itemsperblock; + long items, maxitems; + int unallocateditems; + int pathitemsleft; + + memorypool(); + memorypool(int, int, int, int); + ~memorypool(); + + void poolinit(int, int, int, int); + void restart(); + void* alloc(); + void dealloc(void*); + void traversalinit(); + void* traverse(); + }; + + //============================================================================// + // // + // badface // + // // + // Despite of its name, a 'badface' can be used to represent one of the // + // following objects: // + // - a face of a tetrahedron which is (possibly) non-Delaunay; // + // - an encroached subsegment or subface; // + // - a bad-quality tetrahedron, i.e, has too large radius-edge ratio; // + // - a sliver, i.e., has good radius-edge ratio but nearly zero volume; // + // - a recently flipped face (saved for undoing the flip later). // + // // + //============================================================================// + + class badface + { + public: + triface tt; + face ss; + REAL key, cent[6]; // circumcenter or cos(dihedral angles) at 6 edges. + point forg, fdest, fapex, foppo, noppo; + badface* nextitem; + badface() : key(0), forg(0), fdest(0), fapex(0), foppo(0), noppo(0), nextitem(0) {} + void init() + { + key = 0.; + for (int k = 0; k < 6; k++) cent[k] = 0.; + tt.tet = NULL; + tt.ver = 0; + ss.sh = NULL; + ss.shver = 0; + forg = fdest = fapex = foppo = noppo = NULL; + nextitem = NULL; + } + }; + + //============================================================================// + // // + // insertvertexflags // + // // + // A collection of flags that pass to the routine insertvertex(). // + // // + //============================================================================// + + class insertvertexflags + { + + public: + int iloc; // input/output. + int bowywat, lawson; + int splitbdflag, validflag, respectbdflag; + int rejflag, chkencflag, cdtflag; + int assignmeshsize; + int sloc, sbowywat; + + // Used by Delaunay refinement. + int collect_inial_cavity_flag; + int ignore_near_vertex; + int check_insert_radius; + int refineflag; // 0, 1, 2, 3 + triface refinetet; + face refinesh; + int smlenflag; // for useinsertradius. + REAL smlen; // for useinsertradius. + point parentpt; + + void init() + { + iloc = bowywat = lawson = 0; + splitbdflag = validflag = respectbdflag = 0; + rejflag = chkencflag = cdtflag = 0; + assignmeshsize = 0; + sloc = sbowywat = 0; + + collect_inial_cavity_flag = 0; + ignore_near_vertex = 0; + check_insert_radius = 0; + refineflag = 0; + refinetet.tet = NULL; + refinesh.sh = NULL; + smlenflag = 0; + smlen = 0.0; + parentpt = NULL; + } - // Triangle-edge intersection test (robust) - int tri_edge_2d(point, point, point, point, point, point, int, int*, int*); - int tri_edge_tail(point,point,point,point,point,point,REAL,REAL,int,int*,int*); - int tri_edge_test(point, point, point, point, point, point, int, int*, int*); + insertvertexflags() { init(); } + }; + + //============================================================================// + // // + // flipconstraints // + // // + // A structure of a collection of data (options and parameters) which pass // + // to the edge flip function flipnm(). // + // // + //============================================================================// + + class flipconstraints + { + + public: + // Elementary flip flags. + int enqflag; // (= flipflag) + int chkencflag; + + // Control flags + int unflip; // Undo the performed flips. + int collectnewtets; // Collect the new tets created by flips. + int collectencsegflag; + + // Optimization flags. + int noflip_in_surface; // do not flip edges (not segment) in surface. + int remove_ndelaunay_edge; // Remove a non-Delaunay edge. + REAL bak_tetprism_vol; // The value to be minimized. + REAL tetprism_vol_sum; + int remove_large_angle; // Remove a large dihedral angle at edge. + REAL cosdihed_in; // The input cosine of the dihedral angle (> 0). + REAL cosdihed_out; // The improved cosine of the dihedral angle. + REAL max_asp_out; // Max asp ratio after the improvement of dihedral angle. + + // Boundary recovery flags. + int checkflipeligibility; + point seg[2]; // A constraining edge to be recovered. + point fac[3]; // A constraining face to be recovered. + point remvert; // A vertex to be removed. + + flipconstraints() + { + enqflag = 0; + chkencflag = 0; + + unflip = 0; + collectnewtets = 0; + collectencsegflag = 0; + + noflip_in_surface = 0; + remove_ndelaunay_edge = 0; + bak_tetprism_vol = 0.0; + tetprism_vol_sum = 0.0; + remove_large_angle = 0; + cosdihed_in = 0.0; + cosdihed_out = 0.0; + max_asp_out = 0.0; + + checkflipeligibility = 0; + seg[0] = NULL; + fac[0] = NULL; + remvert = NULL; + } + }; + + //============================================================================// + // // + // optparameters // + // // + // Optimization options and parameters. // + // // + //============================================================================// + + class optparameters + { + + public: + // The one of goals of optimization. + int max_min_volume; // Maximize the minimum volume. + int min_max_aspectratio; // Minimize the maximum aspect ratio. + int min_max_dihedangle; // Minimize the maximum dihedral angle. + + // The initial and improved value. + REAL initval, imprval; + + int numofsearchdirs; + REAL searchstep; + int maxiter; // Maximum smoothing iterations (disabled by -1). + int smthiter; // Performed iterations. + + optparameters() + { + max_min_volume = 0; + min_max_aspectratio = 0; + min_max_dihedangle = 0; + + initval = imprval = 0.0; + + numofsearchdirs = 10; + searchstep = 0.01; + maxiter = -1; // Unlimited smoothing iterations. + smthiter = 0; + } + }; + + //============================================================================// + // // + // Labels (enumeration declarations) used by TetGen. // + // // + //============================================================================// + + // Labels that signify the type of a vertex. + enum verttype { + UNUSEDVERTEX, + DUPLICATEDVERTEX, + RIDGEVERTEX, /*ACUTEVERTEX,*/ + FACETVERTEX, + VOLVERTEX, + FREESEGVERTEX, + FREEFACETVERTEX, + FREEVOLVERTEX, + NREGULARVERTEX, + DEADVERTEX + }; + + // Labels that signify the result of triangle-triangle intersection test. + enum interresult { + DISJOINT, + INTERSECT, + SHAREVERT, + SHAREEDGE, + SHAREFACE, + TOUCHEDGE, + TOUCHFACE, + ACROSSVERT, + ACROSSEDGE, + ACROSSFACE, + SELF_INTERSECT + }; + + // Labels that signify the result of point location. + enum locateresult { + UNKNOWN, + OUTSIDE, + INTETRAHEDRON, + ONFACE, + ONEDGE, + ONVERTEX, + ENCVERTEX, + ENCSEGMENT, + ENCSUBFACE, + NEARVERTEX, + NONREGULAR, + INSTAR, + BADELEMENT, + NULLCAVITY, + SHARPCORNER, + FENSEDIN, + NONCOPLANAR, + SELF_ENCROACH + }; + + //============================================================================// + // // + // Variables of TetGen // + // // + //============================================================================// + + // Pointer to the input data (a set of nodes, a PLC, or a mesh). + tetgenio *in, *addin; + + // Pointer to the switches and parameters. + tetgenbehavior* b; + + // Pointer to a background mesh (contains size specification map). + tetgenmesh* bgm; + + // Memorypools to store mesh elements (points, tetrahedra, subfaces, and + // segments) and extra pointers between tetrahedra, subfaces, and segments. + memorypool *tetrahedrons, *subfaces, *subsegs, *points; + memorypool *tet2subpool, *tet2segpool; + + // Memorypools to store bad-quality (or encroached) elements. + memorypool *badtetrahedrons, *badsubfacs, *badsubsegs; + memorypool *split_subfaces_pool, *split_segments_pool; + arraypool *unsplit_badtets, *unsplit_subfaces, *unsplit_segments; + arraypool* check_tets_list; + + badface *stack_enc_segments, *stack_enc_subfaces; + + // Bad quality subfaces are ordered by priority queues. + badface* queuefront[64]; + badface* queuetail[64]; + int nextnonemptyq[64]; + int firstnonemptyq, recentq; + + // Bad quality tetrahedra are ordered by priority queues. + memorypool* badqual_tets_pool; + badface* bt_queuefront[64]; + badface* bt_queuetail[64]; + int bt_nextnonemptyq[64]; + int bt_firstnonemptyq, bt_recentq; + + // A memorypool to store faces to be flipped. + memorypool* flippool; + arraypool *later_unflip_queue, *unflipqueue; + badface *flipstack, *unflip_queue_front, *unflip_queue_tail; + + // Arrays used for point insertion (the Bowyer-Watson algorithm). + arraypool *cavetetlist, *cavebdrylist, *caveoldtetlist; + arraypool* cave_oldtet_list; // only tetrahedron's + arraypool *cavetetshlist, *cavetetseglist, *cavetetvertlist; + arraypool *caveencshlist, *caveencseglist; + arraypool *caveshlist, *caveshbdlist, *cavesegshlist; + triface _bw_faces[4096]; // _bw_faces[64][64]; + + // Stacks used for CDT construction and boundary recovery. + arraypool *subsegstack, *subfacstack, *subvertstack; + arraypool *skipped_segment_list, *skipped_facet_list; + + // Arrays of encroached segments and subfaces (for mesh refinement). + arraypool *encseglist, *encshlist; + + // The map between facets to their vertices (for mesh refinement). + int number_of_facets; + int* idx2facetlist; + point* facetverticeslist; + int* idx_segment_facet_list; // segment-to-facet map. + int* segment_facet_list; + int* idx_ridge_vertex_facet_list; // vertex-to-facet map. + int* ridge_vertex_facet_list; + + // The map between segments to their endpoints (for mesh refinement). + int segmentendpointslist_length; + point* segmentendpointslist; + double* segment_info_list; + int* idx_segment_ridge_vertex_list; // are two ridge vertices form a segment? + point* segment_ridge_vertex_list; + + // The infinite vertex. + point dummypoint; + // The recently visited tetrahedron, subface. + triface recenttet; + face recentsh; + + // PI is the ratio of a circle's circumference to its diameter. + static REAL PI; + + // The list of subdomains. (-A option). + int subdomains; // Number of subdomains. + int* subdomain_markers; + + // Various variables. + int numpointattrib; // Number of point attributes. + int numelemattrib; // Number of tetrahedron attributes. + int sizeoftensor; // Number of REALs per metric tensor. + int pointmtrindex; // Index to find the metric tensor of a point. + int pointparamindex; // Index to find the u,v coordinates of a point. + int point2simindex; // Index to find a simplex adjacent to a point. + int pointmarkindex; // Index to find boundary marker of a point. + int pointinsradiusindex; // Index to find the insertion radius of a point. + int elemattribindex; // Index to find attributes of a tetrahedron. + int polarindex; // Index to find the polar plane parameters. + int volumeboundindex; // Index to find volume bound of a tetrahedron. + int elemmarkerindex; // Index to find marker of a tetrahedron. + int shmarkindex; // Index to find boundary marker of a subface. + int areaboundindex; // Index to find area bound of a subface. + int checksubsegflag; // Are there segments in the tetrahedralization yet? + int checksubfaceflag; // Are there subfaces in the tetrahedralization yet? + int boundary_recovery_flag; + int checkconstraints; // Are there variant (node, seg, facet) constraints? + int nonconvex; // Is current mesh non-convex? + int autofliplinklevel; // The increase of link levels, default is 1. + int useinsertradius; // Save the insertion radius for Steiner points. + long samples; // Number of random samples for point location. + unsigned long randomseed; // Current random number seed. + REAL cosmaxdihed, cosmindihed; // The cosine values of max/min dihedral. + REAL cossmtdihed; // The cosine value of a bad dihedral to be smoothed. + REAL cosslidihed; // The cosine value of the max dihedral of a sliver. + REAL cos_large_dihed; // The cosine value of large dihedral (135 degree). + REAL opt_max_sliver_asp_ratio; // = 10 x b->opt_max_asp_ratio. + REAL minfaceang, minfacetdihed; // The minimum input (dihedral) angles. + REAL cos_facet_separate_ang_tol; + REAL cos_collinear_ang_tol; + REAL tetprism_vol_sum; // The total volume of tetrahedral-prisms (in 4D). + REAL longest; // The longest possible edge length. + REAL minedgelength; // = longest * b->epsion. + REAL xmax, xmin, ymax, ymin, zmax, zmin; // Bounding box of points. + + // Options for mesh refinement. + REAL big_radius_edge_ratio; // calculated by qualitystatistics(). + REAL smallest_insradius; // Save the smallest insertion radius. + long elem_limit; + long insert_point_count; // number of attempted insertions. + long report_refine_progress; // the next report event. + long last_point_count; // number of points after last report event. + long last_insertion_count; // number of insertions after last report event. + + // Counters. + long insegments; // Number of input segments. + long hullsize; // Number of exterior boundary faces. + long meshedges; // Number of mesh edges. + long meshhulledges; // Number of boundary mesh edges. + long steinerleft; // Number of Steiner points not yet used. + long dupverts; // Are there duplicated vertices? + long unuverts; // Are there unused vertices? + long duplicated_facets_count; // Are there duplicated facets.? + long nonregularcount; // Are there non-regular vertices? + long st_segref_count, st_facref_count, st_volref_count; // Steiner points. + long fillregioncount, cavitycount, cavityexpcount; + long flip14count, flip26count, flipn2ncount; + long flip23count, flip32count, flip44count, flip41count; + long flip31count, flip22count; + long opt_flips_count, opt_collapse_count, opt_smooth_count; + long recover_delaunay_count; + unsigned long totalworkmemory; // Total memory used by working arrays. + + //============================================================================// + // // + // Mesh manipulation primitives // + // // + //============================================================================// + + // Fast lookup tables for mesh manipulation primitives. + static int bondtbl[12][12], fsymtbl[12][12]; + static int esymtbl[12], enexttbl[12], eprevtbl[12]; + static int enextesymtbl[12], eprevesymtbl[12]; + static int eorgoppotbl[12], edestoppotbl[12]; + static int facepivot1[12], facepivot2[12][12]; + static int orgpivot[12], destpivot[12], apexpivot[12], oppopivot[12]; + static int tsbondtbl[12][6], stbondtbl[12][6]; + static int tspivottbl[12][6], stpivottbl[12][6]; + static int ver2edge[12], edge2ver[6], epivot[12]; + static int sorgpivot[6], sdestpivot[6], sapexpivot[6]; + static int snextpivot[6]; + + void inittables(); + + // Primitives for tetrahedra. + inline tetrahedron encode(triface& t); + inline tetrahedron encode2(tetrahedron* ptr, int ver); + inline void decode(tetrahedron ptr, triface& t); + inline tetrahedron* decode_tet_only(tetrahedron ptr); + inline int decode_ver_only(tetrahedron ptr); + inline void bond(triface& t1, triface& t2); + inline void dissolve(triface& t); + inline void esym(triface& t1, triface& t2); + inline void esymself(triface& t); + inline void enext(triface& t1, triface& t2); + inline void enextself(triface& t); + inline void eprev(triface& t1, triface& t2); + inline void eprevself(triface& t); + inline void enextesym(triface& t1, triface& t2); + inline void enextesymself(triface& t); + inline void eprevesym(triface& t1, triface& t2); + inline void eprevesymself(triface& t); + inline void eorgoppo(triface& t1, triface& t2); + inline void eorgoppoself(triface& t); + inline void edestoppo(triface& t1, triface& t2); + inline void edestoppoself(triface& t); + inline void fsym(triface& t1, triface& t2); + inline void fsymself(triface& t); + inline void fnext(triface& t1, triface& t2); + inline void fnextself(triface& t); + inline point org(triface& t); + inline point dest(triface& t); + inline point apex(triface& t); + inline point oppo(triface& t); + inline void setorg(triface& t, point p); + inline void setdest(triface& t, point p); + inline void setapex(triface& t, point p); + inline void setoppo(triface& t, point p); + inline REAL elemattribute(tetrahedron* ptr, int attnum); + inline void setelemattribute(tetrahedron* ptr, int attnum, REAL value); + inline REAL* get_polar(tetrahedron* ptr); + inline REAL get_volume(tetrahedron* ptr); + inline REAL volumebound(tetrahedron* ptr); + inline void setvolumebound(tetrahedron* ptr, REAL value); + inline int elemindex(tetrahedron* ptr); + inline void setelemindex(tetrahedron* ptr, int value); + inline int elemmarker(tetrahedron* ptr); + inline void setelemmarker(tetrahedron* ptr, int value); + inline void infect(triface& t); + inline void uninfect(triface& t); + inline bool infected(triface& t); + inline void marktest(triface& t); + inline void unmarktest(triface& t); + inline bool marktested(triface& t); + inline void markface(triface& t); + inline void unmarkface(triface& t); + inline bool facemarked(triface& t); + inline void markedge(triface& t); + inline void unmarkedge(triface& t); + inline bool edgemarked(triface& t); + inline void marktest2(triface& t); + inline void unmarktest2(triface& t); + inline bool marktest2ed(triface& t); + inline int elemcounter(triface& t); + inline void setelemcounter(triface& t, int value); + inline void increaseelemcounter(triface& t); + inline void decreaseelemcounter(triface& t); + inline bool ishulltet(triface& t); + inline bool isdeadtet(triface& t); + + // Primitives for subfaces and subsegments. + inline void sdecode(shellface sptr, face& s); + inline shellface sencode(face& s); + inline shellface sencode2(shellface* sh, int shver); + inline void spivot(face& s1, face& s2); + inline void spivotself(face& s); + inline void sbond(face& s1, face& s2); + inline void sbond1(face& s1, face& s2); + inline void sdissolve(face& s); + inline point sorg(face& s); + inline point sdest(face& s); + inline point sapex(face& s); + inline void setsorg(face& s, point pointptr); + inline void setsdest(face& s, point pointptr); + inline void setsapex(face& s, point pointptr); + inline void sesym(face& s1, face& s2); + inline void sesymself(face& s); + inline void senext(face& s1, face& s2); + inline void senextself(face& s); + inline void senext2(face& s1, face& s2); + inline void senext2self(face& s); + inline REAL areabound(face& s); + inline void setareabound(face& s, REAL value); + inline int shellmark(face& s); + inline void setshellmark(face& s, int value); + inline void sinfect(face& s); + inline void suninfect(face& s); + inline bool sinfected(face& s); + inline void smarktest(face& s); + inline void sunmarktest(face& s); + inline bool smarktested(face& s); + inline void smarktest2(face& s); + inline void sunmarktest2(face& s); + inline bool smarktest2ed(face& s); + inline void smarktest3(face& s); + inline void sunmarktest3(face& s); + inline bool smarktest3ed(face& s); + inline void setfacetindex(face& f, int value); + inline int getfacetindex(face& f); + inline bool isdeadsh(face& s); + + // Primitives for interacting tetrahedra and subfaces. + inline void tsbond(triface& t, face& s); + inline void tsdissolve(triface& t); + inline void stdissolve(face& s); + inline void tspivot(triface& t, face& s); + inline void stpivot(face& s, triface& t); + + // Primitives for interacting tetrahedra and segments. + inline void tssbond1(triface& t, face& seg); + inline void sstbond1(face& s, triface& t); + inline void tssdissolve1(triface& t); + inline void sstdissolve1(face& s); + inline void tsspivot1(triface& t, face& s); + inline void sstpivot1(face& s, triface& t); + + // Primitives for interacting subfaces and segments. + inline void ssbond(face& s, face& edge); + inline void ssbond1(face& s, face& edge); + inline void ssdissolve(face& s); + inline void sspivot(face& s, face& edge); + + // Primitives for points. + inline int pointmark(point pt); + inline void setpointmark(point pt, int value); + inline enum verttype pointtype(point pt); + inline void setpointtype(point pt, enum verttype value); + inline int pointgeomtag(point pt); + inline void setpointgeomtag(point pt, int value); + inline REAL pointgeomuv(point pt, int i); + inline void setpointgeomuv(point pt, int i, REAL value); + inline void pinfect(point pt); + inline void puninfect(point pt); + inline bool pinfected(point pt); + inline void pmarktest(point pt); + inline void punmarktest(point pt); + inline bool pmarktested(point pt); + inline void pmarktest2(point pt); + inline void punmarktest2(point pt); + inline bool pmarktest2ed(point pt); + inline void pmarktest3(point pt); + inline void punmarktest3(point pt); + inline bool pmarktest3ed(point pt); + inline tetrahedron point2tet(point pt); + inline void setpoint2tet(point pt, tetrahedron value); + inline shellface point2sh(point pt); + inline void setpoint2sh(point pt, shellface value); + inline point point2ppt(point pt); + inline void setpoint2ppt(point pt, point value); + inline tetrahedron point2bgmtet(point pt); + inline void setpoint2bgmtet(point pt, tetrahedron value); + inline void setpointinsradius(point pt, REAL value); + inline REAL getpointinsradius(point pt); + inline bool issteinerpoint(point pt); + + // Advanced primitives. + inline void point2tetorg(point pt, triface& t); + inline void point2shorg(point pa, face& s); + inline point farsorg(face& seg); + inline point farsdest(face& seg); + + //============================================================================// + // // + // Memory managment // + // // + //============================================================================// + + void tetrahedrondealloc(tetrahedron*); + tetrahedron* tetrahedrontraverse(); + tetrahedron* alltetrahedrontraverse(); + void shellfacedealloc(memorypool*, shellface*); + shellface* shellfacetraverse(memorypool*); + void pointdealloc(point); + point pointtraverse(); + + void makeindex2pointmap(point*&); + void makepoint2submap(memorypool*, int*&, face*&); + void maketetrahedron(triface*); + void maketetrahedron2(triface*, point, point, point, point); + void makeshellface(memorypool*, face*); + void makepoint(point*, enum verttype); + + void initializepools(); + + //============================================================================// + // // + // Advanced geometric predicates and calculations // + // // + // the routine insphere_s() implements a simplified symbolic perturbation // + // scheme from Edelsbrunner, et al [*]. Hence the point-in-sphere test never // + // returns a zero. The idea is to perturb the weights of vertices in 4D. // + // // + // The routine tri_edge_test() determines whether or not a triangle and an // + // edge intersect in 3D. If they do cross, their intersection type is also // + // reported. This test is a combination of n 3D orientation tests (3 < n < 9).// + // It uses the robust orient3d() test to make the branch decisions. // + // // + // There are several routines to calculate geometrical quantities, e.g., // + // circumcenters, angles, dihedral angles, face normals, face areas, etc. // + // They are implemented using floating-point arithmetics. // + // // + //============================================================================// + + // Symbolic perturbations (robust) + REAL insphere_s(REAL*, REAL*, REAL*, REAL*, REAL*); + REAL orient4d_s(REAL*, REAL*, REAL*, REAL*, REAL*, REAL, REAL, REAL, REAL, REAL); + + // An embedded 2-dimensional geometric predicate (non-robust) + REAL incircle3d(point pa, point pb, point pc, point pd); + + // Triangle-edge intersection test (robust) + int tri_edge_2d(point, point, point, point, point, point, int, int*, int*); + int tri_edge_tail(point, point, point, point, point, point, REAL, REAL, int, int*, int*); + int tri_edge_test(point, point, point, point, point, point, int, int*, int*); // Triangle-triangle intersection test (robust) - int tri_edge_inter_tail(point, point, point, point, point, REAL, REAL); - int tri_tri_inter(point, point, point, point, point, point); - - // Linear algebra functions - inline REAL dot(REAL* v1, REAL* v2); - inline void cross(REAL* v1, REAL* v2, REAL* n); - bool lu_decmp(REAL lu[4][4], int n, int* ps, REAL* d, int N); - void lu_solve(REAL lu[4][4], int n, int* ps, REAL* b, int N); - - // Geometric calculations (non-robust) - REAL orient3dfast(REAL *pa, REAL *pb, REAL *pc, REAL *pd); - inline REAL norm2(REAL x, REAL y, REAL z); - inline REAL distance(REAL* p1, REAL* p2); - inline REAL distance2(REAL* p1, REAL* p2); - void facenormal(point pa, point pb, point pc, REAL *n, int pivot, REAL *lav); - REAL facedihedral(REAL* pa, REAL* pb, REAL* pc1, REAL* pc2); - REAL triarea(REAL* pa, REAL* pb, REAL* pc); - REAL interiorangle(REAL* o, REAL* p1, REAL* p2, REAL* n); - REAL cos_interiorangle(REAL* o, REAL* p1, REAL* p2); - void projpt2edge(REAL* p, REAL* e1, REAL* e2, REAL* prj); - void projpt2face(REAL* p, REAL* f1, REAL* f2, REAL* f3, REAL* prj); - bool circumsphere(REAL*, REAL*, REAL*, REAL*, REAL* cent, REAL* radius); - bool orthosphere(REAL*,REAL*,REAL*,REAL*,REAL,REAL,REAL,REAL,REAL*,REAL*); - void planelineint(REAL*, REAL*, REAL*, REAL*, REAL*, REAL*, REAL*); - int linelineint(REAL*, REAL*, REAL*, REAL*, REAL*, REAL*, REAL*, REAL*); - REAL tetprismvol(REAL* pa, REAL* pb, REAL* pc, REAL* pd); - bool calculateabovepoint(arraypool*, point*, point*, point*); - void calculateabovepoint4(point, point, point, point); - -//============================================================================// -// // -// Local mesh transformations // -// // -// A local transformation replaces a set of tetrahedra with another set that // -// partitions the same space and boundaries. // -// // -// In 3D, the most straightforward local transformations are the elementary // -// flips performed within the convex hull of five vertices: 2-to-3, 3-to-2, // -// 1-to-4, and 4-to-1 flips. The numbers indicate the number of tetrahedra // -// before and after each flip. The 1-to-4 and 4-to-1 flip involve inserting // -// or deleting a vertex, respectively. // -// // -// There are complex local transformations that are a combination of element- // -// ary flips. For example, a 4-to-4 flip, which replaces two coplanar edges, // -// combines a 2-to-3 flip and a 3-to-2 flip. Note that the first 2-to-3 flip // -// will temporarily create a degenerate tetrahedron removed immediately by // -// the followed 3-to-2 flip. More generally, an n-to-m flip, where n > 3, // -// m = (n - 2) * 2, which removes an edge, can be done by first performing a // -// sequence of (n - 3) 2-to-3 flips followed by a 3-to-2 flip. // -// // -// The routines flip23(), flip32(), and flip41() perform the three elementray // -// flips. The flip14() is available inside the routine insertpoint(). // -// // -// The routines flipnm() and flipnm_post() implement a generalized edge flip // -// algorithm that uses elementary flips. // -// // -// The routine insertpoint() implements the Bowyer-Watson's cavity algorithm // -// to insert a vertex. It works for arbitrary tetrahedralization, either // -// Delaunay, or constrained Delaunay, or non-Delaunay. // -// // -//============================================================================// - - void flippush(badface*&, triface*); - - // The elementary flips. - void flip23(triface*, int, flipconstraints* fc); - void flip32(triface*, int, flipconstraints* fc); - void flip41(triface*, int, flipconstraints* fc); - - // A generalized edge flip. - int flipnm(triface*, int n, int level, int, flipconstraints* fc); - int flipnm_post(triface*, int n, int nn, int, flipconstraints* fc); - - // Point insertion. - int insertpoint(point, triface*, face*, face*, insertvertexflags*); - void insertpoint_abort(face*, insertvertexflags*); - -//============================================================================// -// // -// Delaunay tetrahedralization // -// // -// The routine incrementaldelaunay() implemented two incremental algorithms // -// for constructing Delaunay tetrahedralizations (DTs): the Bowyer-Watson // -// (B-W) algorithm and the incremental flip algorithm of Edelsbrunner and // -// Shah, "Incremental topological flipping works for regular triangulation," // -// Algorithmica, 15:233-241, 1996. // -// // -// The routine incrementalflip() implements the flip algorithm of [Edelsbrun- // -// ner and Shah, 1996]. It flips a queue of locally non-Delaunay faces (in // -// arbitrary order). The success is guaranteed when the Delaunay tetrahedra- // -// lization is constructed incrementally by adding one vertex at a time. // -// // -// The routine locate() finds a tetrahedron contains a new point in current // -// DT. It uses a simple stochastic walk algorithm: starting from an arbitrary // -// tetrahedron in DT, it finds the destination by visit one tetrahedron at a // -// time, randomly chooses a tetrahedron if there are more than one choices. // -// This algorithm terminates due to Edelsbrunner's acyclic theorem. // -// // -// Choose a good starting tetrahedron is crucial to the speed of the walk. // -// TetGen initially uses the "jump-and-walk" algorithm of Muecke, E.P., Saias,// -// I., and Zhu, B. "Fast Randomized Point Location Without Preprocessing." In // -// Proceedings of the 12th ACM Symposium on Computational Geometry, 274-283, // -// 1996. It first randomly samples several tetrahedra in the DT and then // -// choosing the closet one to start walking. // -// // -// The above algorithm slows download dramatically as the number of points // -// grows -- reported in Amenta, N., Choi, S. and Rote, G., "Incremental // -// construction con {BRIO}," In Proceedings of 19th ACM Symposium on Computa- // -// tional Geometry, 211-219, 2003. On the other hand, Liu and Snoeyink showed // -// that the point location could be made in constant time if the points are // -// pre-sorted so that the nearby points in space have nearby indices, then // -// adding the points in this order. They sorted the points along the 3D // -// Hilbert curve. // -// // -// The routine hilbert_sort3() sorts a set of 3D points along the 3D Hilbert // -// curve. It recursively splits a point set according to the Hilbert indices // -// mapped to the subboxes of the bounding box of the point set. The Hilbert // -// indices is calculated by Butz's algorithm in 1971. An excellent exposition // -// of this algorithm can be found in the paper of Hamilton, C., "Compact // -// Hilbert Indices", Technical Report CS-2006-07, Computer Science, Dalhousie // -// University, 2006 (the Section 2). My implementation also referenced Steven // -// Witham's performance of "Hilbert walk" (hopefully, it is still available // -// at http://www.tiac.net/~sw/2008/10/Hilbert/). // -// // -// TetGen sorts the points using the method in the paper of Boissonnat,J.-D., // -// Devillers, O. and Hornus, S. "Incremental Construction of the Delaunay // -// Triangulation and the Delaunay Graph in Medium Dimension," In Proceedings // -// of the 25th ACM Symposium on Computational Geometry, 2009. It first // -// randomly sorts the points into subgroups using the Biased Randomized // -// Insertion Ordering (BRIO) of Amenta et al 2003, then sorts the points in // -// each subgroup along the 3D Hilbert curve. Inserting points in this order // -// ensure a randomized "sprinkling" of the points over the domain, while // -// sorting of each subset provides locality. // -// // -//============================================================================// - - void transfernodes(); - - // Point sorting. - int transgc[8][3][8], tsb1mod3[8]; - void hilbert_init(int n); - int hilbert_split(point* vertexarray, int arraysize, int gc0, int gc1, - REAL, REAL, REAL, REAL, REAL, REAL); - void hilbert_sort3(point* vertexarray, int arraysize, int e, int d, - REAL, REAL, REAL, REAL, REAL, REAL, int depth); - void brio_multiscale_sort(point*,int,int threshold,REAL ratio,int* depth); - - // Point location. - unsigned long randomnation(unsigned int choices); - void randomsample(point searchpt, triface *searchtet); - enum locateresult locate(point searchpt, triface *searchtet, int chkencflag = 0); - - // Incremental Delaunay construction. - enum locateresult locate_dt(point searchpt, triface *searchtet); - int insert_vertex_bw(point, triface*, insertvertexflags*); - void initialdelaunay(point pa, point pb, point pc, point pd); - void incrementaldelaunay(clock_t&); - -//============================================================================// -// // -// Surface triangulation // -// // -//============================================================================// - - void flipshpush(face*); - void flip22(face*, int, int); - void flip31(face*, int); - long lawsonflip(); - int sinsertvertex(point newpt, face*, face*, int iloc, int bowywat, int); - int sremovevertex(point delpt, face*, face*, int lawson); - - enum locateresult slocate(point, face*, int, int, int); - enum interresult sscoutsegment(face*, point, int, int, int); - void scarveholes(int, REAL*); - int triangulate(int, arraypool*, arraypool*, int, REAL*); - - void unifysegments(); - void identifyinputedges(point*); - void mergefacets(); - void meshsurface(); - - -//============================================================================// -// // -// Constrained Delaunay tetrahedralization // -// // -// A constrained Delaunay tetrahedralization (CDT) is a variation of a Delau- // -// nay tetrahedralization (DT) that respects the boundary of a 3D PLC (mesh // -// domain). A crucial difference between a CDT and a DT is that triangles in // -// the PLC's polygons are not required to be locally Delaunay, which frees // -// the CDT to respect the PLC's polygons better. CDTs have optimal properties // -// similar to those of DTs. // -// // -// Steiner Points and Steiner CDTs. It is well-known that even a simple 3D // -// polyhedron may not have a tetrahedralization which only uses its vertices. // -// Some extra points, so-called "Steiner points" are needed to form a tetrah- // -// edralization of such polyhedron. A Steiner CDT of a 3D PLC is a CDT // -// containing Steiner points. TetGen generates Steiner CDTs. // -// // -// The routine constraineddelaunay() creates a (Steiner) CDT of the PLC // -// (including Steiner points). It has two steps, (1) segment recovery and (2) // -// facet (polygon) recovery. // -// // -// The routine delaunizesegments() implements the segment recovery algorithm // -// of Si, H., and Gaertner, K. "Meshing Piecewise Linear Complexes by // -// Constrained Delaunay Tetrahedralizations," In Proceedings of the 14th // -// International Meshing Roundtable, 147--163, 2005. It adds Steiner points // -// into non-Delaunay segments until all subsegments appear together in a DT. // -// The running time of this algorithm is proportional to the number of // -// Steiner points. // -// // -// There are two incremental facet recovery algorithms: the cavity re- // -// triangulation algorithm of Si, H., and Gaertner, K. "3D Boundary Recovery // -// by Constrained Delaunay Tetrahedralization," International Journal for // -// Numerical Methods in Engineering, 85:1341-1364, 2011, and the flip // -// algorithm of Shewchuk, J. "Updating and Constructing Constrained Delaunay // -// and Constrained Regular Triangulations by Flips." In Proceedings of the // -// 19th ACM Symposium on Computational Geometry, 86-95, 2003. // -// // -// Although no Steiner point is needed in step (2), a facet with non-coplanar // -// vertices might need Steiner points. It is discussed in the paper of Si, H.,// -// and Shewchuk, J., "Incrementally Constructing and Updating Constrained // -// Delaunay Tetrahedralizations with Finite Precision Coordinates." In // -// Proceedings of the 21th International Meshing Roundtable, 2012. // -// // -// Our implementation of the facet recovery algorithms recovers a "missing // -// region" at a time. Each missing region is a subset of connected interiors // -// of a polygon. The routine formcavity() creates the cavity of crossing // -// tetrahedra of the missing region. The cavity re-triangulation algorithm is // -// implemented by three subroutines, delaunizecavity(), fillcavity(), and // -// carvecavity(). Since it may fail due to non-coplanar vertices, the // -// subroutine restorecavity() is used to restore the original cavity. // -// // -// The routine flipinsertfacet() implements the flip algorithm. The sub- // -// routine flipcertify() is used to maintain the priority queue of flips. // -// The routine refineregion() is called when the facet recovery algorithm // -// fails to recover a missing region. It inserts Steiner points to refine the // -// missing region. To avoid inserting Steiner points very close to existing // -// segments. The classical encroachment rules of the Delaunay refinement // -// algorithm are used to choose the Steiner points. The routine // -// constrainedfacets() does the facet recovery by using either the cavity re- // -// triangulation algorithm (default) or the flip algorithm. It results in a // -// CDT of the (modified) PLC (including Steiner points). // -// // -//============================================================================// - - enum interresult finddirection(triface* searchtet, point endpt); - enum interresult scoutsegment(point, point, face*, triface*, point*, - arraypool*); - int getsteinerptonsegment(face* seg, point refpt, point steinpt); - void delaunizesegments(); - - int scoutsubface(face* searchsh,triface* searchtet,int shflag); - void formregion(face*, arraypool*, arraypool*, arraypool*); - int scoutcrossedge(triface& crosstet, arraypool*, arraypool*); - bool formcavity(triface*, arraypool*, arraypool*, arraypool*, arraypool*, - arraypool*, arraypool*); - // Facet recovery by cavity re-triangulation [Si and Gaertner 2011]. - void delaunizecavity(arraypool*, arraypool*, arraypool*, arraypool*, - arraypool*, arraypool*); - bool fillcavity(arraypool*, arraypool*, arraypool*, arraypool*, - arraypool*, arraypool*, triface* crossedge); - void carvecavity(arraypool*, arraypool*, arraypool*); - void restorecavity(arraypool*, arraypool*, arraypool*, arraypool*); - // Facet recovery by flips [Shewchuk 2003]. - void flipcertify(triface *chkface, badface **pqueue, point, point, point); - void flipinsertfacet(arraypool*, arraypool*, arraypool*, arraypool*); - - int insertpoint_cdt(point, triface*, face*, face*, insertvertexflags*, - arraypool*, arraypool*, arraypool*, arraypool*, - arraypool*, arraypool*); - void refineregion(face&, arraypool*, arraypool*, arraypool*, arraypool*, - arraypool*, arraypool*); - void constrainedfacets(); - - void constraineddelaunay(clock_t&); - -//============================================================================// -// // -// Constrained tetrahedralizations. // -// // -//============================================================================// - - void sort_2pts(point p1, point p2, point ppt[2]); - void sort_3pts(point p1, point p2, point p3, point ppt[3]); - - bool is_collinear_at(point mid, point left, point right); - bool is_segment(point p1, point p2); - bool valid_constrained_f23(triface&, point pd, point pe); - bool valid_constrained_f32(triface*, point pa, point pb); - - int checkflipeligibility(int fliptype, point, point, point, point, point, - int level, int edgepivot, flipconstraints* fc); - - int removeedgebyflips(triface*, flipconstraints*); - int removefacebyflips(triface*, flipconstraints*); - - int recoveredgebyflips(point, point, face*, triface*, int fullsearch, int& idir); - int add_steinerpt_in_schoenhardtpoly(triface*, int, int, int chkencflag); - int add_steinerpt_in_segment(face*, int searchlevel, int& idir); - int add_steinerpt_to_recover_edge(point, point, face*, int, int, int& idir); - int recoversegments(arraypool*, int fullsearch, int steinerflag); - - int recoverfacebyflips(point,point,point,face*,triface*,int&,point*,point*); - int recoversubfaces(arraypool*, int steinerflag); - - int getvertexstar(int, point searchpt, arraypool*, arraypool*, arraypool*); - int getedge(point, point, triface*); - int reduceedgesatvertex(point startpt, arraypool* endptlist); - int removevertexbyflips(point steinerpt); - - int smoothpoint(point smtpt, arraypool*, int ccw, optparameters *opm); - int suppressbdrysteinerpoint(point steinerpt); - int suppresssteinerpoints(); - - void recoverboundary(clock_t&); - -//============================================================================// -// // -// Mesh reconstruction // -// // -//============================================================================// - - void carveholes(); - - void reconstructmesh(); - - int search_face(point p0, point p1, point p2, triface &tetloop); - int search_edge(point p0, point p1, triface &tetloop); - int scout_point(point, triface*, int randflag); - REAL getpointmeshsize(point, triface*, int iloc); - void interpolatemeshsize(); - - void insertconstrainedpoints(point *insertarray, int arylen, int rejflag); - void insertconstrainedpoints(tetgenio *addio); - - void collectremovepoints(arraypool *remptlist); - void meshcoarsening(); - -//============================================================================// -// // -// Mesh refinement // -// // -// The purpose of mesh refinement is to obtain a tetrahedral mesh with well- // -// -shaped tetrahedra and appropriate mesh size. It is necessary to insert // -// new Steiner points to achieve this property. The questions are (1) how to // -// choose the Steiner points? and (2) how to insert them? // -// // -// Delaunay refinement is a technique first developed by Chew [1989] and // -// Ruppert [1993, 1995] to generate quality triangular meshes in the plane. // -// It provides guarantee on the smallest angle of the triangles. Rupper's // -// algorithm guarantees that the mesh is size-optimal (to within a constant // -// factor) among all meshes with the same quality. // -// Shewchuk generalized Ruppert's algorithm into 3D in his PhD thesis // -// [Shewchuk 1997]. A short version of his algorithm appears in "Tetrahedral // -// Mesh Generation by Delaunay Refinement," In Proceedings of the 14th ACM // -// Symposium on Computational Geometry, 86-95, 1998. It guarantees that all // -// tetrahedra of the output mesh have a "radius-edge ratio" (equivalent to // -// the minimal face angle) bounded. However, it does not remove slivers, a // -// type of very flat tetrahedra which can have no small face angles but have // -// very small (and large) dihedral angles. Moreover, it may not terminate if // -// the input PLC contains "sharp features", e.g., two edges (or two facets) // -// meet at an acute angle (or dihedral angle). // -// // -// TetGen uses the basic Delaunay refinement scheme to insert Steiner points. // -// While it always maintains a constrained Delaunay mesh. The algorithm is // -// described in Si, H., "Adaptive Constrained Delaunay Mesh Generation," // -// International Journal for Numerical Methods in Engineering, 75:856-880. // -// This algorithm always terminates and sharp features are easily preserved. // -// The mesh has good quality (same as Shewchuk's Delaunay refinement algori- // -// thm) in the bulk of the mesh domain. Moreover, it supports the generation // -// of adaptive mesh according to a (isotropic) mesh sizing function. // -// // -//============================================================================// - - void makesegmentendpointsmap(); - REAL set_ridge_vertex_protecting_ball(point); - REAL get_min_angle_at_ridge_vertex(face* seg); - REAL get_min_diahedral_angle(face* seg); - void create_segment_info_list(); - - void makefacetverticesmap(); - void create_segment_facet_map(); - - int ridge_vertices_adjacent(point, point); - int facet_ridge_vertex_adjacent(face *, point); - int segsegadjacent(face *, face *); - int segfacetadjacent(face *checkseg, face *checksh); - int facetfacetadjacent(face *, face *); - bool is_sharp_segment(face* seg); - bool does_seg_contain_acute_vertex(face* seg); - bool create_a_shorter_edge(point steinerpt, point nearpt); - - void enqueuesubface(memorypool*, face*); - void enqueuetetrahedron(triface*); - - bool check_encroachment(point pa, point pb, point checkpt); - bool check_enc_segment(face *chkseg, point *pencpt); - bool get_steiner_on_segment(face* seg, point encpt, point newpt); - bool split_segment(face *splitseg, point encpt, REAL *param, int qflag, int, int*); - void repairencsegs(REAL *param, int qflag, int chkencflag); - - bool get_subface_ccent(face *chkfac, REAL *ccent); - bool check_enc_subface(face *chkfac, point *pencpt, REAL *ccent, REAL *radius); - bool check_subface(face *chkfac, REAL *ccent, REAL radius, REAL *param); - void enqueue_subface(face *bface, point encpt, REAL *ccent, REAL *param); - badface* top_subface(); - void dequeue_subface(); - void parallel_shift(point pa, point pb, point pc, point pt, REAL* ppt); - enum locateresult locate_on_surface(point searchpt, face* searchsh); - bool split_subface(face *splitfac, point encpt, REAL *ccent, REAL*, int, int, int*); - void repairencfacs(REAL *param, int qflag, int chkencflag); - - bool check_tetrahedron(triface *chktet, REAL* param, int& qflag); - bool checktet4split(triface *chktet, REAL* param, int& qflag); - enum locateresult locate_point_walk(point searchpt, triface*, int chkencflag); - bool split_tetrahedron(triface*, REAL*, int, int, insertvertexflags &ivf); - void repairbadtets(REAL queratio, int chkencflag); - - void delaunayrefinement(); - -//============================================================================// -// // -// Mesh optimization // -// // -//============================================================================// - - long lawsonflip3d(flipconstraints *fc); - void recoverdelaunay(); - - int get_seg_laplacian_center(point mesh_vert, REAL target[3]); - int get_surf_laplacian_center(point mesh_vert, REAL target[3]); - int get_laplacian_center(point mesh_vert, REAL target[3]); - bool move_vertex(point mesh_vert, REAL target[3]); - void smooth_vertices(); - - bool get_tet(point, point, point, point, triface *); - bool get_tetqual(triface *chktet, point oppo_pt, badface *bf); - bool get_tetqual(point, point, point, point, badface *bf); - void enqueue_badtet(badface *bf); - badface* top_badtet(); - void dequeue_badtet(); - - bool add_steinerpt_to_repair(badface *bf, bool bSmooth); - bool flip_edge_to_improve(triface *sliver_edge, REAL& improved_cosmaxd); - bool repair_tet(badface *bf, bool bFlips, bool bSmooth, bool bSteiners); - long repair_badqual_tets(bool bFlips, bool bSmooth, bool bSteiners); - void improve_mesh(); - -//============================================================================// -// // -// Mesh check and statistics // -// // -//============================================================================// - - // Mesh validations. - int check_mesh(int topoflag); - int check_shells(); - int check_segments(); - int check_delaunay(int perturb = 1); - int check_regular(int); - int check_conforming(int); - - // Mesh statistics. - void printfcomma(unsigned long n); - void qualitystatistics(); - void memorystatistics(); - void statistics(); - -//============================================================================// -// // -// Mesh output // -// // -//============================================================================// - - void jettisonnodes(); - void highorder(); - void indexelements(); - void numberedges(); - void outnodes(tetgenio*); - void outmetrics(tetgenio*); - void outelements(tetgenio*); - void outfaces(tetgenio*); - void outhullfaces(tetgenio*); - void outsubfaces(tetgenio*); - void outedges(tetgenio*); - void outsubsegments(tetgenio*); - void outneighbors(tetgenio*); - void outvoronoi(tetgenio*); - void outsmesh(char*); - void outmesh2medit(char*); - void outmesh2vtk(char*, int); - void out_surfmesh_vtk(char*, int); - void out_intersected_facets(); - - - + int tri_edge_inter_tail(point, point, point, point, point, REAL, REAL); + int tri_tri_inter(point, point, point, point, point, point); + + // Linear algebra functions + inline REAL dot(REAL* v1, REAL* v2); + inline void cross(REAL* v1, REAL* v2, REAL* n); + bool lu_decmp(REAL lu[4][4], int n, int* ps, REAL* d, int N); + void lu_solve(REAL lu[4][4], int n, int* ps, REAL* b, int N); + + // Geometric calculations (non-robust) + REAL orient3dfast(REAL* pa, REAL* pb, REAL* pc, REAL* pd); + inline REAL norm2(REAL x, REAL y, REAL z); + inline REAL distance(REAL* p1, REAL* p2); + inline REAL distance2(REAL* p1, REAL* p2); + void facenormal(point pa, point pb, point pc, REAL* n, int pivot, REAL* lav); + REAL facedihedral(REAL* pa, REAL* pb, REAL* pc1, REAL* pc2); + REAL triarea(REAL* pa, REAL* pb, REAL* pc); + REAL interiorangle(REAL* o, REAL* p1, REAL* p2, REAL* n); + REAL cos_interiorangle(REAL* o, REAL* p1, REAL* p2); + void projpt2edge(REAL* p, REAL* e1, REAL* e2, REAL* prj); + void projpt2face(REAL* p, REAL* f1, REAL* f2, REAL* f3, REAL* prj); + bool circumsphere(REAL*, REAL*, REAL*, REAL*, REAL* cent, REAL* radius); + bool orthosphere(REAL*, REAL*, REAL*, REAL*, REAL, REAL, REAL, REAL, REAL*, REAL*); + void planelineint(REAL*, REAL*, REAL*, REAL*, REAL*, REAL*, REAL*); + int linelineint(REAL*, REAL*, REAL*, REAL*, REAL*, REAL*, REAL*, REAL*); + REAL tetprismvol(REAL* pa, REAL* pb, REAL* pc, REAL* pd); + bool calculateabovepoint(arraypool*, point*, point*, point*); + void calculateabovepoint4(point, point, point, point); + + //============================================================================// + // // + // Local mesh transformations // + // // + // A local transformation replaces a set of tetrahedra with another set that // + // partitions the same space and boundaries. // + // // + // In 3D, the most straightforward local transformations are the elementary // + // flips performed within the convex hull of five vertices: 2-to-3, 3-to-2, // + // 1-to-4, and 4-to-1 flips. The numbers indicate the number of tetrahedra // + // before and after each flip. The 1-to-4 and 4-to-1 flip involve inserting // + // or deleting a vertex, respectively. // + // // + // There are complex local transformations that are a combination of element- // + // ary flips. For example, a 4-to-4 flip, which replaces two coplanar edges, // + // combines a 2-to-3 flip and a 3-to-2 flip. Note that the first 2-to-3 flip // + // will temporarily create a degenerate tetrahedron removed immediately by // + // the followed 3-to-2 flip. More generally, an n-to-m flip, where n > 3, // + // m = (n - 2) * 2, which removes an edge, can be done by first performing a // + // sequence of (n - 3) 2-to-3 flips followed by a 3-to-2 flip. // + // // + // The routines flip23(), flip32(), and flip41() perform the three elementray // + // flips. The flip14() is available inside the routine insertpoint(). // + // // + // The routines flipnm() and flipnm_post() implement a generalized edge flip // + // algorithm that uses elementary flips. // + // // + // The routine insertpoint() implements the Bowyer-Watson's cavity algorithm // + // to insert a vertex. It works for arbitrary tetrahedralization, either // + // Delaunay, or constrained Delaunay, or non-Delaunay. // + // // + //============================================================================// + + void flippush(badface*&, triface*); + + // The elementary flips. + void flip23(triface*, int, flipconstraints* fc); + void flip32(triface*, int, flipconstraints* fc); + void flip41(triface*, int, flipconstraints* fc); + + // A generalized edge flip. + int flipnm(triface*, int n, int level, int, flipconstraints* fc); + int flipnm_post(triface*, int n, int nn, int, flipconstraints* fc); + + // Point insertion. + int insertpoint(point, triface*, face*, face*, insertvertexflags*); + void insertpoint_abort(face*, insertvertexflags*); + + //============================================================================// + // // + // Delaunay tetrahedralization // + // // + // The routine incrementaldelaunay() implemented two incremental algorithms // + // for constructing Delaunay tetrahedralizations (DTs): the Bowyer-Watson // + // (B-W) algorithm and the incremental flip algorithm of Edelsbrunner and // + // Shah, "Incremental topological flipping works for regular triangulation," // + // Algorithmica, 15:233-241, 1996. // + // // + // The routine incrementalflip() implements the flip algorithm of [Edelsbrun- // + // ner and Shah, 1996]. It flips a queue of locally non-Delaunay faces (in // + // arbitrary order). The success is guaranteed when the Delaunay tetrahedra- // + // lization is constructed incrementally by adding one vertex at a time. // + // // + // The routine locate() finds a tetrahedron contains a new point in current // + // DT. It uses a simple stochastic walk algorithm: starting from an arbitrary // + // tetrahedron in DT, it finds the destination by visit one tetrahedron at a // + // time, randomly chooses a tetrahedron if there are more than one choices. // + // This algorithm terminates due to Edelsbrunner's acyclic theorem. // + // // + // Choose a good starting tetrahedron is crucial to the speed of the walk. // + // TetGen initially uses the "jump-and-walk" algorithm of Muecke, E.P., Saias,// + // I., and Zhu, B. "Fast Randomized Point Location Without Preprocessing." In // + // Proceedings of the 12th ACM Symposium on Computational Geometry, 274-283, // + // 1996. It first randomly samples several tetrahedra in the DT and then // + // choosing the closet one to start walking. // + // // + // The above algorithm slows download dramatically as the number of points // + // grows -- reported in Amenta, N., Choi, S. and Rote, G., "Incremental // + // construction con {BRIO}," In Proceedings of 19th ACM Symposium on Computa- // + // tional Geometry, 211-219, 2003. On the other hand, Liu and Snoeyink showed // + // that the point location could be made in constant time if the points are // + // pre-sorted so that the nearby points in space have nearby indices, then // + // adding the points in this order. They sorted the points along the 3D // + // Hilbert curve. // + // // + // The routine hilbert_sort3() sorts a set of 3D points along the 3D Hilbert // + // curve. It recursively splits a point set according to the Hilbert indices // + // mapped to the subboxes of the bounding box of the point set. The Hilbert // + // indices is calculated by Butz's algorithm in 1971. An excellent exposition // + // of this algorithm can be found in the paper of Hamilton, C., "Compact // + // Hilbert Indices", Technical Report CS-2006-07, Computer Science, Dalhousie // + // University, 2006 (the Section 2). My implementation also referenced Steven // + // Witham's performance of "Hilbert walk" (hopefully, it is still available // + // at http://www.tiac.net/~sw/2008/10/Hilbert/). // + // // + // TetGen sorts the points using the method in the paper of Boissonnat,J.-D., // + // Devillers, O. and Hornus, S. "Incremental Construction of the Delaunay // + // Triangulation and the Delaunay Graph in Medium Dimension," In Proceedings // + // of the 25th ACM Symposium on Computational Geometry, 2009. It first // + // randomly sorts the points into subgroups using the Biased Randomized // + // Insertion Ordering (BRIO) of Amenta et al 2003, then sorts the points in // + // each subgroup along the 3D Hilbert curve. Inserting points in this order // + // ensure a randomized "sprinkling" of the points over the domain, while // + // sorting of each subset provides locality. // + // // + //============================================================================// + + void transfernodes(); + + // Point sorting. + int transgc[8][3][8], tsb1mod3[8]; + void hilbert_init(int n); + int hilbert_split(point* vertexarray, int arraysize, int gc0, int gc1, REAL, REAL, REAL, REAL, REAL, REAL); + void hilbert_sort3(point* vertexarray, int arraysize, int e, int d, REAL, REAL, REAL, REAL, REAL, REAL, int depth); + void brio_multiscale_sort(point*, int, int threshold, REAL ratio, int* depth); + + // Point location. + unsigned long randomnation(unsigned int choices); + void randomsample(point searchpt, triface* searchtet); + enum locateresult locate(point searchpt, triface* searchtet, int chkencflag = 0); + + // Incremental Delaunay construction. + enum locateresult locate_dt(point searchpt, triface* searchtet); + int insert_vertex_bw(point, triface*, insertvertexflags*); + void initialdelaunay(point pa, point pb, point pc, point pd); + void incrementaldelaunay(clock_t&); + + //============================================================================// + // // + // Surface triangulation // + // // + //============================================================================// + + void flipshpush(face*); + void flip22(face*, int, int); + void flip31(face*, int); + long lawsonflip(); + int sinsertvertex(point newpt, face*, face*, int iloc, int bowywat, int); + int sremovevertex(point delpt, face*, face*, int lawson); + + enum locateresult slocate(point, face*, int, int, int); + enum interresult sscoutsegment(face*, point, int, int, int); + void scarveholes(int, REAL*); + int triangulate(int, arraypool*, arraypool*, int, REAL*); + + void unifysegments(); + void identifyinputedges(point*); + void mergefacets(); + void meshsurface(); + + //============================================================================// + // // + // Constrained Delaunay tetrahedralization // + // // + // A constrained Delaunay tetrahedralization (CDT) is a variation of a Delau- // + // nay tetrahedralization (DT) that respects the boundary of a 3D PLC (mesh // + // domain). A crucial difference between a CDT and a DT is that triangles in // + // the PLC's polygons are not required to be locally Delaunay, which frees // + // the CDT to respect the PLC's polygons better. CDTs have optimal properties // + // similar to those of DTs. // + // // + // Steiner Points and Steiner CDTs. It is well-known that even a simple 3D // + // polyhedron may not have a tetrahedralization which only uses its vertices. // + // Some extra points, so-called "Steiner points" are needed to form a tetrah- // + // edralization of such polyhedron. A Steiner CDT of a 3D PLC is a CDT // + // containing Steiner points. TetGen generates Steiner CDTs. // + // // + // The routine constraineddelaunay() creates a (Steiner) CDT of the PLC // + // (including Steiner points). It has two steps, (1) segment recovery and (2) // + // facet (polygon) recovery. // + // // + // The routine delaunizesegments() implements the segment recovery algorithm // + // of Si, H., and Gaertner, K. "Meshing Piecewise Linear Complexes by // + // Constrained Delaunay Tetrahedralizations," In Proceedings of the 14th // + // International Meshing Roundtable, 147--163, 2005. It adds Steiner points // + // into non-Delaunay segments until all subsegments appear together in a DT. // + // The running time of this algorithm is proportional to the number of // + // Steiner points. // + // // + // There are two incremental facet recovery algorithms: the cavity re- // + // triangulation algorithm of Si, H., and Gaertner, K. "3D Boundary Recovery // + // by Constrained Delaunay Tetrahedralization," International Journal for // + // Numerical Methods in Engineering, 85:1341-1364, 2011, and the flip // + // algorithm of Shewchuk, J. "Updating and Constructing Constrained Delaunay // + // and Constrained Regular Triangulations by Flips." In Proceedings of the // + // 19th ACM Symposium on Computational Geometry, 86-95, 2003. // + // // + // Although no Steiner point is needed in step (2), a facet with non-coplanar // + // vertices might need Steiner points. It is discussed in the paper of Si, H.,// + // and Shewchuk, J., "Incrementally Constructing and Updating Constrained // + // Delaunay Tetrahedralizations with Finite Precision Coordinates." In // + // Proceedings of the 21th International Meshing Roundtable, 2012. // + // // + // Our implementation of the facet recovery algorithms recovers a "missing // + // region" at a time. Each missing region is a subset of connected interiors // + // of a polygon. The routine formcavity() creates the cavity of crossing // + // tetrahedra of the missing region. The cavity re-triangulation algorithm is // + // implemented by three subroutines, delaunizecavity(), fillcavity(), and // + // carvecavity(). Since it may fail due to non-coplanar vertices, the // + // subroutine restorecavity() is used to restore the original cavity. // + // // + // The routine flipinsertfacet() implements the flip algorithm. The sub- // + // routine flipcertify() is used to maintain the priority queue of flips. // + // The routine refineregion() is called when the facet recovery algorithm // + // fails to recover a missing region. It inserts Steiner points to refine the // + // missing region. To avoid inserting Steiner points very close to existing // + // segments. The classical encroachment rules of the Delaunay refinement // + // algorithm are used to choose the Steiner points. The routine // + // constrainedfacets() does the facet recovery by using either the cavity re- // + // triangulation algorithm (default) or the flip algorithm. It results in a // + // CDT of the (modified) PLC (including Steiner points). // + // // + //============================================================================// + + enum interresult finddirection(triface* searchtet, point endpt); + enum interresult scoutsegment(point, point, face*, triface*, point*, arraypool*); + int getsteinerptonsegment(face* seg, point refpt, point steinpt); + void delaunizesegments(); + + int scoutsubface(face* searchsh, triface* searchtet, int shflag); + void formregion(face*, arraypool*, arraypool*, arraypool*); + int scoutcrossedge(triface& crosstet, arraypool*, arraypool*); + bool formcavity(triface*, arraypool*, arraypool*, arraypool*, arraypool*, arraypool*, arraypool*); + // Facet recovery by cavity re-triangulation [Si and Gaertner 2011]. + void delaunizecavity(arraypool*, arraypool*, arraypool*, arraypool*, arraypool*, arraypool*); + bool fillcavity(arraypool*, arraypool*, arraypool*, arraypool*, arraypool*, arraypool*, triface* crossedge); + void carvecavity(arraypool*, arraypool*, arraypool*); + void restorecavity(arraypool*, arraypool*, arraypool*, arraypool*); + // Facet recovery by flips [Shewchuk 2003]. + void flipcertify(triface* chkface, badface** pqueue, point, point, point); + void flipinsertfacet(arraypool*, arraypool*, arraypool*, arraypool*); + + int insertpoint_cdt(point, triface*, face*, face*, insertvertexflags*, arraypool*, arraypool*, arraypool*, + arraypool*, arraypool*, arraypool*); + void refineregion(face&, arraypool*, arraypool*, arraypool*, arraypool*, arraypool*, arraypool*); + void constrainedfacets(); + + void constraineddelaunay(clock_t&); + + //============================================================================// + // // + // Constrained tetrahedralizations. // + // // + //============================================================================// + + void sort_2pts(point p1, point p2, point ppt[2]); + void sort_3pts(point p1, point p2, point p3, point ppt[3]); + + bool is_collinear_at(point mid, point left, point right); + bool is_segment(point p1, point p2); + bool valid_constrained_f23(triface&, point pd, point pe); + bool valid_constrained_f32(triface*, point pa, point pb); + + int checkflipeligibility(int fliptype, point, point, point, point, point, int level, int edgepivot, + flipconstraints* fc); + + int removeedgebyflips(triface*, flipconstraints*); + int removefacebyflips(triface*, flipconstraints*); + + int recoveredgebyflips(point, point, face*, triface*, int fullsearch, int& idir); + int add_steinerpt_in_schoenhardtpoly(triface*, int, int, int chkencflag); + int add_steinerpt_in_segment(face*, int searchlevel, int& idir); + int add_steinerpt_to_recover_edge(point, point, face*, int, int, int& idir); + int recoversegments(arraypool*, int fullsearch, int steinerflag); + + int recoverfacebyflips(point, point, point, face*, triface*, int&, point*, point*); + int recoversubfaces(arraypool*, int steinerflag); + + int getvertexstar(int, point searchpt, arraypool*, arraypool*, arraypool*); + int getedge(point, point, triface*); + int reduceedgesatvertex(point startpt, arraypool* endptlist); + int removevertexbyflips(point steinerpt); + + int smoothpoint(point smtpt, arraypool*, int ccw, optparameters* opm); + int suppressbdrysteinerpoint(point steinerpt); + int suppresssteinerpoints(); + + void recoverboundary(clock_t&); + + //============================================================================// + // // + // Mesh reconstruction // + // // + //============================================================================// + + void carveholes(); + + void reconstructmesh(); + + int search_face(point p0, point p1, point p2, triface& tetloop); + int search_edge(point p0, point p1, triface& tetloop); + int scout_point(point, triface*, int randflag); + REAL getpointmeshsize(point, triface*, int iloc); + void interpolatemeshsize(); + + void insertconstrainedpoints(point* insertarray, int arylen, int rejflag); + void insertconstrainedpoints(tetgenio* addio); + + void collectremovepoints(arraypool* remptlist); + void meshcoarsening(); + + //============================================================================// + // // + // Mesh refinement // + // // + // The purpose of mesh refinement is to obtain a tetrahedral mesh with well- // + // -shaped tetrahedra and appropriate mesh size. It is necessary to insert // + // new Steiner points to achieve this property. The questions are (1) how to // + // choose the Steiner points? and (2) how to insert them? // + // // + // Delaunay refinement is a technique first developed by Chew [1989] and // + // Ruppert [1993, 1995] to generate quality triangular meshes in the plane. // + // It provides guarantee on the smallest angle of the triangles. Rupper's // + // algorithm guarantees that the mesh is size-optimal (to within a constant // + // factor) among all meshes with the same quality. // + // Shewchuk generalized Ruppert's algorithm into 3D in his PhD thesis // + // [Shewchuk 1997]. A short version of his algorithm appears in "Tetrahedral // + // Mesh Generation by Delaunay Refinement," In Proceedings of the 14th ACM // + // Symposium on Computational Geometry, 86-95, 1998. It guarantees that all // + // tetrahedra of the output mesh have a "radius-edge ratio" (equivalent to // + // the minimal face angle) bounded. However, it does not remove slivers, a // + // type of very flat tetrahedra which can have no small face angles but have // + // very small (and large) dihedral angles. Moreover, it may not terminate if // + // the input PLC contains "sharp features", e.g., two edges (or two facets) // + // meet at an acute angle (or dihedral angle). // + // // + // TetGen uses the basic Delaunay refinement scheme to insert Steiner points. // + // While it always maintains a constrained Delaunay mesh. The algorithm is // + // described in Si, H., "Adaptive Constrained Delaunay Mesh Generation," // + // International Journal for Numerical Methods in Engineering, 75:856-880. // + // This algorithm always terminates and sharp features are easily preserved. // + // The mesh has good quality (same as Shewchuk's Delaunay refinement algori- // + // thm) in the bulk of the mesh domain. Moreover, it supports the generation // + // of adaptive mesh according to a (isotropic) mesh sizing function. // + // // + //============================================================================// + + void makesegmentendpointsmap(); + REAL set_ridge_vertex_protecting_ball(point); + REAL get_min_angle_at_ridge_vertex(face* seg); + REAL get_min_diahedral_angle(face* seg); + void create_segment_info_list(); + + void makefacetverticesmap(); + void create_segment_facet_map(); + + int ridge_vertices_adjacent(point, point); + int facet_ridge_vertex_adjacent(face*, point); + int segsegadjacent(face*, face*); + int segfacetadjacent(face* checkseg, face* checksh); + int facetfacetadjacent(face*, face*); + bool is_sharp_segment(face* seg); + bool does_seg_contain_acute_vertex(face* seg); + bool create_a_shorter_edge(point steinerpt, point nearpt); + + void enqueuesubface(memorypool*, face*); + void enqueuetetrahedron(triface*); + + bool check_encroachment(point pa, point pb, point checkpt); + bool check_enc_segment(face* chkseg, point* pencpt); + bool get_steiner_on_segment(face* seg, point encpt, point newpt); + bool split_segment(face* splitseg, point encpt, REAL* param, int qflag, int, int*); + void repairencsegs(REAL* param, int qflag, int chkencflag); + + bool get_subface_ccent(face* chkfac, REAL* ccent); + bool check_enc_subface(face* chkfac, point* pencpt, REAL* ccent, REAL* radius); + bool check_subface(face* chkfac, REAL* ccent, REAL radius, REAL* param); + void enqueue_subface(face* bface, point encpt, REAL* ccent, REAL* param); + badface* top_subface(); + void dequeue_subface(); + void parallel_shift(point pa, point pb, point pc, point pt, REAL* ppt); + enum locateresult locate_on_surface(point searchpt, face* searchsh); + bool split_subface(face* splitfac, point encpt, REAL* ccent, REAL*, int, int, int*); + void repairencfacs(REAL* param, int qflag, int chkencflag); + + bool check_tetrahedron(triface* chktet, REAL* param, int& qflag); + bool checktet4split(triface* chktet, REAL* param, int& qflag); + enum locateresult locate_point_walk(point searchpt, triface*, int chkencflag); + bool split_tetrahedron(triface*, REAL*, int, int, insertvertexflags& ivf); + void repairbadtets(REAL queratio, int chkencflag); + + void delaunayrefinement(); + + //============================================================================// + // // + // Mesh optimization // + // // + //============================================================================// + + long lawsonflip3d(flipconstraints* fc); + void recoverdelaunay(); + + int get_seg_laplacian_center(point mesh_vert, REAL target[3]); + int get_surf_laplacian_center(point mesh_vert, REAL target[3]); + int get_laplacian_center(point mesh_vert, REAL target[3]); + bool move_vertex(point mesh_vert, REAL target[3]); + void smooth_vertices(); + + bool get_tet(point, point, point, point, triface*); + bool get_tetqual(triface* chktet, point oppo_pt, badface* bf); + bool get_tetqual(point, point, point, point, badface* bf); + void enqueue_badtet(badface* bf); + badface* top_badtet(); + void dequeue_badtet(); + + bool add_steinerpt_to_repair(badface* bf, bool bSmooth); + bool flip_edge_to_improve(triface* sliver_edge, REAL& improved_cosmaxd); + bool repair_tet(badface* bf, bool bFlips, bool bSmooth, bool bSteiners); + long repair_badqual_tets(bool bFlips, bool bSmooth, bool bSteiners); + void improve_mesh(); + + //============================================================================// + // // + // Mesh check and statistics // + // // + //============================================================================// + + // Mesh validations. + int check_mesh(int topoflag); + int check_shells(); + int check_segments(); + int check_delaunay(int perturb = 1); + int check_regular(int); + int check_conforming(int); + + // Mesh statistics. + void printfcomma(unsigned long n); + void qualitystatistics(); + void memorystatistics(); + void statistics(); + + //============================================================================// + // // + // Mesh output // + // // + //============================================================================// + + void jettisonnodes(); + void highorder(); + void indexelements(); + void numberedges(); + void outnodes(tetgenio*); + void outmetrics(tetgenio*); + void outelements(tetgenio*); + void outfaces(tetgenio*); + void outhullfaces(tetgenio*); + void outsubfaces(tetgenio*); + void outedges(tetgenio*); + void outsubsegments(tetgenio*); + void outneighbors(tetgenio*); + void outvoronoi(tetgenio*); + void outsmesh(char*); + void outmesh2medit(char*); + void outmesh2vtk(char*, int); + void out_surfmesh_vtk(char*, int); + void out_intersected_facets(); + + //============================================================================// + // // + // Constructor & destructor // + // // + //============================================================================// + + void initializetetgenmesh() + { + in = addin = NULL; + b = NULL; + bgm = NULL; + + tetrahedrons = subfaces = subsegs = points = NULL; + tet2segpool = tet2subpool = NULL; + dummypoint = NULL; + + badtetrahedrons = badsubfacs = badsubsegs = NULL; + split_segments_pool = split_subfaces_pool = NULL; + unsplit_badtets = unsplit_subfaces = unsplit_segments = NULL; + check_tets_list = NULL; + badqual_tets_pool = NULL; + + stack_enc_segments = stack_enc_subfaces = NULL; + + flippool = NULL; + flipstack = unflip_queue_front = unflip_queue_tail = NULL; + later_unflip_queue = unflipqueue = NULL; + + cavetetlist = cavebdrylist = caveoldtetlist = NULL; + cave_oldtet_list = NULL; + cavetetshlist = cavetetseglist = cavetetvertlist = NULL; + caveencshlist = caveencseglist = NULL; + caveshlist = caveshbdlist = cavesegshlist = NULL; + + subsegstack = subfacstack = subvertstack = NULL; + skipped_segment_list = skipped_facet_list = NULL; + + encseglist = encshlist = NULL; + + number_of_facets = 0; + idx2facetlist = NULL; + facetverticeslist = NULL; + idx_segment_facet_list = NULL; + segment_facet_list = NULL; + idx_ridge_vertex_facet_list = NULL; + ridge_vertex_facet_list = NULL; + + segmentendpointslist_length = 0; + segmentendpointslist = NULL; + segment_info_list = NULL; + idx_segment_ridge_vertex_list = NULL; + segment_ridge_vertex_list = NULL; + + subdomains = 0; + subdomain_markers = NULL; + + numpointattrib = numelemattrib = 0; + sizeoftensor = 0; + pointmtrindex = 0; + pointparamindex = 0; + pointmarkindex = 0; + point2simindex = 0; + pointinsradiusindex = 0; + elemattribindex = 0; + polarindex = 0; + volumeboundindex = 0; + shmarkindex = 0; + areaboundindex = 0; + checksubsegflag = 0; + checksubfaceflag = 0; + boundary_recovery_flag = 0; + checkconstraints = 0; + nonconvex = 0; + autofliplinklevel = 1; + useinsertradius = 0; + samples = 0l; + randomseed = 1l; + minfaceang = minfacetdihed = PI; + cos_facet_separate_ang_tol = cos(179.9 / 180. * PI); + cos_collinear_ang_tol = cos(179.9 / 180. * PI); + tetprism_vol_sum = 0.0; + longest = minedgelength = 0.0; + xmax = xmin = ymax = ymin = zmax = zmin = 0.0; + + smallest_insradius = 1.e+30; + big_radius_edge_ratio = 100.0; + elem_limit = 0; + insert_point_count = 0l; + report_refine_progress = 0l; + last_point_count = 0l; + last_insertion_count = 0l; + + insegments = 0l; + hullsize = 0l; + meshedges = meshhulledges = 0l; + steinerleft = -1; + dupverts = 0l; + unuverts = 0l; + duplicated_facets_count = 0l; + nonregularcount = 0l; + st_segref_count = st_facref_count = st_volref_count = 0l; + fillregioncount = cavitycount = cavityexpcount = 0l; + flip14count = flip26count = flipn2ncount = 0l; + flip23count = flip32count = flip44count = flip41count = 0l; + flip22count = flip31count = 0l; + recover_delaunay_count = 0l; + opt_flips_count = opt_collapse_count = opt_smooth_count = 0l; + totalworkmemory = 0l; + + } // tetgenmesh() + + void freememory() + { + if (bgm != NULL) + { + delete bgm; + } -//============================================================================// -// // -// Constructor & destructor // -// // -//============================================================================// + if (points != (memorypool*)NULL) + { + delete points; + delete[] dummypoint; + } + if (tetrahedrons != (memorypool*)NULL) + { + delete tetrahedrons; + } + if (subfaces != (memorypool*)NULL) + { + delete subfaces; + delete subsegs; + } + if (tet2segpool != NULL) + { + delete tet2segpool; + delete tet2subpool; + } - void initializetetgenmesh() - { - in = addin = NULL; - b = NULL; - bgm = NULL; - - tetrahedrons = subfaces = subsegs = points = NULL; - tet2segpool = tet2subpool = NULL; - dummypoint = NULL; - - badtetrahedrons = badsubfacs = badsubsegs = NULL; - split_segments_pool = split_subfaces_pool = NULL; - unsplit_badtets = unsplit_subfaces = unsplit_segments = NULL; - check_tets_list = NULL; - badqual_tets_pool = NULL; - - stack_enc_segments = stack_enc_subfaces = NULL; - - flippool = NULL; - flipstack = unflip_queue_front = unflip_queue_tail = NULL; - later_unflip_queue = unflipqueue = NULL; - - cavetetlist = cavebdrylist = caveoldtetlist = NULL; - cave_oldtet_list = NULL; - cavetetshlist = cavetetseglist = cavetetvertlist = NULL; - caveencshlist = caveencseglist = NULL; - caveshlist = caveshbdlist = cavesegshlist = NULL; - - subsegstack = subfacstack = subvertstack = NULL; - skipped_segment_list = skipped_facet_list = NULL; - - encseglist = encshlist = NULL; - - number_of_facets = 0; - idx2facetlist = NULL; - facetverticeslist = NULL; - idx_segment_facet_list = NULL; - segment_facet_list = NULL; - idx_ridge_vertex_facet_list = NULL; - ridge_vertex_facet_list = NULL; - - segmentendpointslist_length = 0; - segmentendpointslist = NULL; - segment_info_list = NULL; - idx_segment_ridge_vertex_list = NULL; - segment_ridge_vertex_list = NULL; - - subdomains = 0; - subdomain_markers = NULL; - - numpointattrib = numelemattrib = 0; - sizeoftensor = 0; - pointmtrindex = 0; - pointparamindex = 0; - pointmarkindex = 0; - point2simindex = 0; - pointinsradiusindex = 0; - elemattribindex = 0; - polarindex = 0; - volumeboundindex = 0; - shmarkindex = 0; - areaboundindex = 0; - checksubsegflag = 0; - checksubfaceflag = 0; - boundary_recovery_flag = 0; - checkconstraints = 0; - nonconvex = 0; - autofliplinklevel = 1; - useinsertradius = 0; - samples = 0l; - randomseed = 1l; - minfaceang = minfacetdihed = PI; - cos_facet_separate_ang_tol = cos(179.9/180.*PI); - cos_collinear_ang_tol = cos(179.9/180.*PI); - tetprism_vol_sum = 0.0; - longest = minedgelength = 0.0; - xmax = xmin = ymax = ymin = zmax = zmin = 0.0; - - smallest_insradius = 1.e+30; - big_radius_edge_ratio = 100.0; - elem_limit = 0; - insert_point_count = 0l; - report_refine_progress = 0l; - last_point_count = 0l; - last_insertion_count = 0l; - - insegments = 0l; - hullsize = 0l; - meshedges = meshhulledges = 0l; - steinerleft = -1; - dupverts = 0l; - unuverts = 0l; - duplicated_facets_count = 0l; - nonregularcount = 0l; - st_segref_count = st_facref_count = st_volref_count = 0l; - fillregioncount = cavitycount = cavityexpcount = 0l; - flip14count = flip26count = flipn2ncount = 0l; - flip23count = flip32count = flip44count = flip41count = 0l; - flip22count = flip31count = 0l; - recover_delaunay_count = 0l; - opt_flips_count = opt_collapse_count = opt_smooth_count = 0l; - totalworkmemory = 0l; - - } // tetgenmesh() - - void freememory() - { - if (bgm != NULL) { - delete bgm; - } + if (badtetrahedrons) + { + delete badtetrahedrons; + } + if (badsubfacs) + { + delete badsubfacs; + } + if (badsubsegs) + { + delete badsubsegs; + } + if (unsplit_badtets) + { + delete unsplit_badtets; + } + if (check_tets_list) + { + delete check_tets_list; + } - if (points != (memorypool *) NULL) { - delete points; - delete [] dummypoint; - } - if (tetrahedrons != (memorypool *) NULL) { - delete tetrahedrons; - } - if (subfaces != (memorypool *) NULL) { - delete subfaces; - delete subsegs; - } - if (tet2segpool != NULL) { - delete tet2segpool; - delete tet2subpool; - } + if (flippool != NULL) + { + delete flippool; + delete later_unflip_queue; + delete unflipqueue; + } - if (badtetrahedrons) { - delete badtetrahedrons; - } - if (badsubfacs) { - delete badsubfacs; - } - if (badsubsegs) { - delete badsubsegs; - } - if (unsplit_badtets) { - delete unsplit_badtets; - } - if (check_tets_list) { - delete check_tets_list; - } + if (cavetetlist != NULL) + { + delete cavetetlist; + delete cavebdrylist; + delete caveoldtetlist; + delete cavetetvertlist; + delete cave_oldtet_list; + } - if (flippool != NULL) { - delete flippool; - delete later_unflip_queue; - delete unflipqueue; - } + if (caveshlist != NULL) + { + delete caveshlist; + delete caveshbdlist; + delete cavesegshlist; + delete cavetetshlist; + delete cavetetseglist; + delete caveencshlist; + delete caveencseglist; + } - if (cavetetlist != NULL) { - delete cavetetlist; - delete cavebdrylist; - delete caveoldtetlist; - delete cavetetvertlist; - delete cave_oldtet_list; - } + if (subsegstack != NULL) + { + delete subsegstack; + delete subfacstack; + delete subvertstack; + } - if (caveshlist != NULL) { - delete caveshlist; - delete caveshbdlist; - delete cavesegshlist; - delete cavetetshlist; - delete cavetetseglist; - delete caveencshlist; - delete caveencseglist; - } + if (idx2facetlist != NULL) + { + delete[] idx2facetlist; + delete[] facetverticeslist; + delete[] idx_segment_facet_list; + delete[] segment_facet_list; + delete[] idx_ridge_vertex_facet_list; + delete[] ridge_vertex_facet_list; + } - if (subsegstack != NULL) { - delete subsegstack; - delete subfacstack; - delete subvertstack; - } + if (segmentendpointslist != NULL) + { + delete[] segmentendpointslist; + delete[] idx_segment_ridge_vertex_list; + delete[] segment_ridge_vertex_list; + } - if (idx2facetlist != NULL) { - delete [] idx2facetlist; - delete [] facetverticeslist; - delete [] idx_segment_facet_list; - delete [] segment_facet_list; - delete [] idx_ridge_vertex_facet_list; - delete [] ridge_vertex_facet_list; - } + if (segment_info_list != NULL) + { + delete[] segment_info_list; + } - if (segmentendpointslist != NULL) { - delete [] segmentendpointslist; - delete [] idx_segment_ridge_vertex_list; - delete [] segment_ridge_vertex_list; - } + if (subdomain_markers != NULL) + { + delete[] subdomain_markers; + } - if (segment_info_list != NULL) { - delete [] segment_info_list; + initializetetgenmesh(); } - if (subdomain_markers != NULL) { - delete [] subdomain_markers; + tetgenmesh() + { + initializetetgenmesh(); } - initializetetgenmesh(); - } - - tetgenmesh() - { - initializetetgenmesh(); - } - - ~tetgenmesh() - { - freememory(); - } // ~tetgenmesh() + ~tetgenmesh() + { + freememory(); + } // ~tetgenmesh() -}; // End of class tetgenmesh. +}; // End of class tetgenmesh. //============================================================================// // // @@ -2468,14 +2547,12 @@ class tetgenmesh { // // //============================================================================// -void tetrahedralize(tetgenbehavior *b, tetgenio *in, tetgenio *out, - tetgenio *addin = NULL, tetgenio *bgmin = NULL); +void tetrahedralize(tetgenbehavior* b, tetgenio* in, tetgenio* out, tetgenio* addin = NULL, tetgenio* bgmin = NULL); #ifdef TETLIBRARY -void tetrahedralize(char *switches, tetgenio *in, tetgenio *out, - tetgenio *addin = NULL, tetgenio *bgmin = NULL); +void tetrahedralize(char* switches, tetgenio* in, tetgenio* out, tetgenio* addin = NULL, tetgenio* bgmin = NULL); -#endif // #ifdef TETLIBRARY +#endif // #ifdef TETLIBRARY //============================================================================// // // @@ -2483,45 +2560,41 @@ void tetrahedralize(char *switches, tetgenio *in, tetgenio *out, // // //============================================================================// - -inline void terminatetetgen(tetgenmesh *m, int x) +inline void terminatetetgen(tetgenmesh* m, int x) { #ifdef TETLIBRARY - throw x; + throw x; #else - switch (x) { - case 1: // Out of memory. - printf("Error: Out of memory.\n"); - break; - case 2: // Encounter an internal error. - printf("Please report this bug to Hang.Si@wias-berlin.de. Include\n"); - printf(" the message above, your input data set, and the exact\n"); - printf(" command line you used to run this program, thank you.\n"); - break; - case 3: - printf("The input surface mesh contain self-intersections. Program stopped.\n"); - //printf("Hint: use -d option to detect all self-intersections.\n"); - break; - case 4: - printf("A very small input feature size was detected. Program stopped.\n"); - if (m) { - printf("Hint: use -T option to set a smaller tolerance. Current is %g\n", - m->b->epsilon); - } - break; - case 5: - printf("Two very close input facets were detected. Program stopped.\n"); - printf("Hint: use -Y option to avoid adding Steiner points in boundary.\n"); - break; - case 10: - printf("An input error was detected. Program stopped.\n"); - break; - case 200: - printf("Boundary contains Steiner points (-YY option). Program stopped.\n"); - break; - } // switch (x) - exit(x); -#endif // #ifdef TETLIBRARY + switch (x) + { + case 1: // Out of memory. + printf("Error: Out of memory.\n"); + break; + case 2: // Encounter an internal error. + printf("Please report this bug to Hang.Si@wias-berlin.de. Include\n"); + printf(" the message above, your input data set, and the exact\n"); + printf(" command line you used to run this program, thank you.\n"); + break; + case 3: + printf("The input surface mesh contain self-intersections. Program stopped.\n"); + //printf("Hint: use -d option to detect all self-intersections.\n"); + break; + case 4: + printf("A very small input feature size was detected. Program stopped.\n"); + if (m) + { + printf("Hint: use -T option to set a smaller tolerance. Current is %g\n", m->b->epsilon); + } + break; + case 5: + printf("Two very close input facets were detected. Program stopped.\n"); + printf("Hint: use -Y option to avoid adding Steiner points in boundary.\n"); + break; + case 10: printf("An input error was detected. Program stopped.\n"); break; + case 200: printf("Boundary contains Steiner points (-YY option). Program stopped.\n"); break; + } // switch (x) + exit(x); +#endif // #ifdef TETLIBRARY } //============================================================================// @@ -2530,373 +2603,424 @@ inline void terminatetetgen(tetgenmesh *m, int x) // // //============================================================================// -// encode() compress a handle into a single pointer. It relies on the +// encode() compress a handle into a single pointer. It relies on the // assumption that all addresses of tetrahedra are aligned to sixteen- // byte boundaries, so that the last four significant bits are zero. -inline tetgenmesh::tetrahedron tetgenmesh::encode(triface& t) { - return (tetrahedron) ((uintptr_t) (t).tet | (uintptr_t) (t).ver); +inline tetgenmesh::tetrahedron tetgenmesh::encode(triface& t) +{ + return (tetrahedron)((uintptr_t)(t).tet | (uintptr_t)(t).ver); } -inline tetgenmesh::tetrahedron tetgenmesh::encode2(tetrahedron* ptr, int ver) { - return (tetrahedron) ((uintptr_t) (ptr) | (uintptr_t) (ver)); +inline tetgenmesh::tetrahedron tetgenmesh::encode2(tetrahedron* ptr, int ver) +{ + return (tetrahedron)((uintptr_t)(ptr) | (uintptr_t)(ver)); } // decode() converts a pointer to a handle. The version is extracted from // the four least significant bits of the pointer. -inline void tetgenmesh::decode(tetrahedron ptr, triface& t) { - (t).ver = (int) ((uintptr_t) (ptr) & (uintptr_t) 15); - (t).tet = (tetrahedron *) ((uintptr_t) (ptr) ^ (uintptr_t) (t).ver); +inline void tetgenmesh::decode(tetrahedron ptr, triface& t) +{ + (t).ver = (int)((uintptr_t)(ptr) & (uintptr_t)15); + (t).tet = (tetrahedron*)((uintptr_t)(ptr) ^ (uintptr_t)(t).ver); } inline tetgenmesh::tetrahedron* tetgenmesh::decode_tet_only(tetrahedron ptr) { - return (tetrahedron *) ((((uintptr_t) ptr) >> 4) << 4); + return (tetrahedron*)((((uintptr_t)ptr) >> 4) << 4); } inline int tetgenmesh::decode_ver_only(tetrahedron ptr) { - return (int) ((uintptr_t) (ptr) & (uintptr_t) 15); + return (int)((uintptr_t)(ptr) & (uintptr_t)15); } -// bond() connects two tetrahedra together. (t1,v1) and (t2,v2) must -// refer to the same face and the same edge. +// bond() connects two tetrahedra together. (t1,v1) and (t2,v2) must +// refer to the same face and the same edge. -inline void tetgenmesh::bond(triface& t1, triface& t2) { - t1.tet[t1.ver & 3] = encode2(t2.tet, bondtbl[t1.ver][t2.ver]); - t2.tet[t2.ver & 3] = encode2(t1.tet, bondtbl[t2.ver][t1.ver]); +inline void tetgenmesh::bond(triface& t1, triface& t2) +{ + t1.tet[t1.ver & 3] = encode2(t2.tet, bondtbl[t1.ver][t2.ver]); + t2.tet[t2.ver & 3] = encode2(t1.tet, bondtbl[t2.ver][t1.ver]); } - // dissolve() a bond (from one side). -inline void tetgenmesh::dissolve(triface& t) { - t.tet[t.ver & 3] = NULL; +inline void tetgenmesh::dissolve(triface& t) +{ + t.tet[t.ver & 3] = NULL; } // enext() finds the next edge (counterclockwise) in the same face. -inline void tetgenmesh::enext(triface& t1, triface& t2) { - t2.tet = t1.tet; - t2.ver = enexttbl[t1.ver]; // (t1.ver + 4) % 12; +inline void tetgenmesh::enext(triface& t1, triface& t2) +{ + t2.tet = t1.tet; + t2.ver = enexttbl[t1.ver]; // (t1.ver + 4) % 12; } -inline void tetgenmesh::enextself(triface& t) { - t.ver = enexttbl[t.ver]; // (t.ver + 4) % 12; +inline void tetgenmesh::enextself(triface& t) +{ + t.ver = enexttbl[t.ver]; // (t.ver + 4) % 12; } // eprev() finds the next edge (clockwise) in the same face. -inline void tetgenmesh::eprev(triface& t1, triface& t2) { - t2.tet = t1.tet; - t2.ver = eprevtbl[t1.ver]; // (t1.ver + 8) % 12; +inline void tetgenmesh::eprev(triface& t1, triface& t2) +{ + t2.tet = t1.tet; + t2.ver = eprevtbl[t1.ver]; // (t1.ver + 8) % 12; } -inline void tetgenmesh::eprevself(triface& t) { - t.ver = eprevtbl[t.ver]; // (t.ver + 8) % 12; +inline void tetgenmesh::eprevself(triface& t) +{ + t.ver = eprevtbl[t.ver]; // (t.ver + 8) % 12; } // esym() finds the reversed edge. It is in the other face of the // same tetrahedron. -inline void tetgenmesh::esym(triface& t1, triface& t2) { - (t2).tet = (t1).tet; - (t2).ver = esymtbl[(t1).ver]; +inline void tetgenmesh::esym(triface& t1, triface& t2) +{ + (t2).tet = (t1).tet; + (t2).ver = esymtbl[(t1).ver]; } -inline void tetgenmesh::esymself(triface& t) { - (t).ver = esymtbl[(t).ver]; +inline void tetgenmesh::esymself(triface& t) +{ + (t).ver = esymtbl[(t).ver]; } // enextesym() finds the reversed edge of the next edge. It is in the other -// face of the same tetrahedron. It is the combination esym() * enext(). +// face of the same tetrahedron. It is the combination esym() * enext(). -inline void tetgenmesh::enextesym(triface& t1, triface& t2) { - t2.tet = t1.tet; - t2.ver = enextesymtbl[t1.ver]; +inline void tetgenmesh::enextesym(triface& t1, triface& t2) +{ + t2.tet = t1.tet; + t2.ver = enextesymtbl[t1.ver]; } -inline void tetgenmesh::enextesymself(triface& t) { - t.ver = enextesymtbl[t.ver]; +inline void tetgenmesh::enextesymself(triface& t) +{ + t.ver = enextesymtbl[t.ver]; } // eprevesym() finds the reversed edge of the previous edge. -inline void tetgenmesh::eprevesym(triface& t1, triface& t2) { - t2.tet = t1.tet; - t2.ver = eprevesymtbl[t1.ver]; +inline void tetgenmesh::eprevesym(triface& t1, triface& t2) +{ + t2.tet = t1.tet; + t2.ver = eprevesymtbl[t1.ver]; } -inline void tetgenmesh::eprevesymself(triface& t) { - t.ver = eprevesymtbl[t.ver]; +inline void tetgenmesh::eprevesymself(triface& t) +{ + t.ver = eprevesymtbl[t.ver]; } // eorgoppo() Finds the opposite face of the origin of the current edge. // Return the opposite edge of the current edge. -inline void tetgenmesh::eorgoppo(triface& t1, triface& t2) { - t2.tet = t1.tet; - t2.ver = eorgoppotbl[t1.ver]; +inline void tetgenmesh::eorgoppo(triface& t1, triface& t2) +{ + t2.tet = t1.tet; + t2.ver = eorgoppotbl[t1.ver]; } -inline void tetgenmesh::eorgoppoself(triface& t) { - t.ver = eorgoppotbl[t.ver]; +inline void tetgenmesh::eorgoppoself(triface& t) +{ + t.ver = eorgoppotbl[t.ver]; } -// edestoppo() Finds the opposite face of the destination of the current +// edestoppo() Finds the opposite face of the destination of the current // edge. Return the opposite edge of the current edge. -inline void tetgenmesh::edestoppo(triface& t1, triface& t2) { - t2.tet = t1.tet; - t2.ver = edestoppotbl[t1.ver]; +inline void tetgenmesh::edestoppo(triface& t1, triface& t2) +{ + t2.tet = t1.tet; + t2.ver = edestoppotbl[t1.ver]; } -inline void tetgenmesh::edestoppoself(triface& t) { - t.ver = edestoppotbl[t.ver]; +inline void tetgenmesh::edestoppoself(triface& t) +{ + t.ver = edestoppotbl[t.ver]; } // fsym() finds the adjacent tetrahedron at the same face and the same edge. -inline void tetgenmesh::fsym(triface& t1, triface& t2) { - decode((t1).tet[(t1).ver & 3], t2); - t2.ver = fsymtbl[t1.ver][t2.ver]; +inline void tetgenmesh::fsym(triface& t1, triface& t2) +{ + decode((t1).tet[(t1).ver & 3], t2); + t2.ver = fsymtbl[t1.ver][t2.ver]; } - #define fsymself(t) \ - t1ver = (t).ver; \ - decode((t).tet[(t).ver & 3], (t));\ - (t).ver = fsymtbl[t1ver][(t).ver] + t1ver = (t).ver; \ + decode((t).tet[(t).ver & 3], (t)); \ + (t).ver = fsymtbl[t1ver][(t).ver] // fnext() finds the next face while rotating about an edge according to // a right-hand rule. The face is in the adjacent tetrahedron. It is // the combination: fsym() * esym(). -inline void tetgenmesh::fnext(triface& t1, triface& t2) { - decode(t1.tet[facepivot1[t1.ver]], t2); - t2.ver = facepivot2[t1.ver][t2.ver]; +inline void tetgenmesh::fnext(triface& t1, triface& t2) +{ + decode(t1.tet[facepivot1[t1.ver]], t2); + t2.ver = facepivot2[t1.ver][t2.ver]; } - #define fnextself(t) \ - t1ver = (t).ver; \ - decode((t).tet[facepivot1[(t).ver]], (t)); \ - (t).ver = facepivot2[t1ver][(t).ver] - + t1ver = (t).ver; \ + decode((t).tet[facepivot1[(t).ver]], (t)); \ + (t).ver = facepivot2[t1ver][(t).ver] // The following primtives get or set the origin, destination, face apex, // or face opposite of an ordered tetrahedron. -inline tetgenmesh::point tetgenmesh::org(triface& t) { - return (point) (t).tet[orgpivot[(t).ver]]; +inline tetgenmesh::point tetgenmesh::org(triface& t) +{ + return (point)(t).tet[orgpivot[(t).ver]]; } -inline tetgenmesh::point tetgenmesh:: dest(triface& t) { - return (point) (t).tet[destpivot[(t).ver]]; +inline tetgenmesh::point tetgenmesh::dest(triface& t) +{ + return (point)(t).tet[destpivot[(t).ver]]; } -inline tetgenmesh::point tetgenmesh:: apex(triface& t) { - return (point) (t).tet[apexpivot[(t).ver]]; +inline tetgenmesh::point tetgenmesh::apex(triface& t) +{ + return (point)(t).tet[apexpivot[(t).ver]]; } -inline tetgenmesh::point tetgenmesh:: oppo(triface& t) { - return (point) (t).tet[oppopivot[(t).ver]]; +inline tetgenmesh::point tetgenmesh::oppo(triface& t) +{ + return (point)(t).tet[oppopivot[(t).ver]]; } -inline void tetgenmesh:: setorg(triface& t, point p) { - (t).tet[orgpivot[(t).ver]] = (tetrahedron) (p); +inline void tetgenmesh::setorg(triface& t, point p) +{ + (t).tet[orgpivot[(t).ver]] = (tetrahedron)(p); } -inline void tetgenmesh:: setdest(triface& t, point p) { - (t).tet[destpivot[(t).ver]] = (tetrahedron) (p); +inline void tetgenmesh::setdest(triface& t, point p) +{ + (t).tet[destpivot[(t).ver]] = (tetrahedron)(p); } -inline void tetgenmesh:: setapex(triface& t, point p) { - (t).tet[apexpivot[(t).ver]] = (tetrahedron) (p); +inline void tetgenmesh::setapex(triface& t, point p) +{ + (t).tet[apexpivot[(t).ver]] = (tetrahedron)(p); } -inline void tetgenmesh:: setoppo(triface& t, point p) { - (t).tet[oppopivot[(t).ver]] = (tetrahedron) (p); +inline void tetgenmesh::setoppo(triface& t, point p) +{ + (t).tet[oppopivot[(t).ver]] = (tetrahedron)(p); } #define setvertices(t, torg, tdest, tapex, toppo) \ - (t).tet[orgpivot[(t).ver]] = (tetrahedron) (torg);\ - (t).tet[destpivot[(t).ver]] = (tetrahedron) (tdest); \ - (t).tet[apexpivot[(t).ver]] = (tetrahedron) (tapex); \ - (t).tet[oppopivot[(t).ver]] = (tetrahedron) (toppo) - + (t).tet[orgpivot[(t).ver]] = (tetrahedron)(torg); \ + (t).tet[destpivot[(t).ver]] = (tetrahedron)(tdest); \ + (t).tet[apexpivot[(t).ver]] = (tetrahedron)(tapex); \ + (t).tet[oppopivot[(t).ver]] = (tetrahedron)(toppo) inline REAL* tetgenmesh::get_polar(tetrahedron* ptr) { - return &(((REAL *) (ptr))[polarindex]); + return &(((REAL*)(ptr))[polarindex]); } inline REAL tetgenmesh::get_volume(tetrahedron* ptr) { - return ((REAL *) (ptr))[polarindex + 4]; + return ((REAL*)(ptr))[polarindex + 4]; } // Check or set a tetrahedron's attributes. -inline REAL tetgenmesh::elemattribute(tetrahedron* ptr, int attnum) { - return ((REAL *) (ptr))[elemattribindex + attnum]; +inline REAL tetgenmesh::elemattribute(tetrahedron* ptr, int attnum) +{ + return ((REAL*)(ptr))[elemattribindex + attnum]; } -inline void tetgenmesh::setelemattribute(tetrahedron* ptr, int attnum, - REAL value) { - ((REAL *) (ptr))[elemattribindex + attnum] = value; +inline void tetgenmesh::setelemattribute(tetrahedron* ptr, int attnum, REAL value) +{ + ((REAL*)(ptr))[elemattribindex + attnum] = value; } // Check or set a tetrahedron's maximum volume bound. -inline REAL tetgenmesh::volumebound(tetrahedron* ptr) { - return ((REAL *) (ptr))[volumeboundindex]; +inline REAL tetgenmesh::volumebound(tetrahedron* ptr) +{ + return ((REAL*)(ptr))[volumeboundindex]; } -inline void tetgenmesh::setvolumebound(tetrahedron* ptr, REAL value) { - ((REAL *) (ptr))[volumeboundindex] = value; +inline void tetgenmesh::setvolumebound(tetrahedron* ptr, REAL value) +{ + ((REAL*)(ptr))[volumeboundindex] = value; } // Get or set a tetrahedron's index (only used for output). // These two routines use the reserved slot ptr[10]. -inline int tetgenmesh::elemindex(tetrahedron* ptr) { - int *iptr = (int *) &(ptr[10]); - return iptr[0]; +inline int tetgenmesh::elemindex(tetrahedron* ptr) +{ + int* iptr = (int*)&(ptr[10]); + return iptr[0]; } -inline void tetgenmesh::setelemindex(tetrahedron* ptr, int value) { - int *iptr = (int *) &(ptr[10]); - iptr[0] = value; +inline void tetgenmesh::setelemindex(tetrahedron* ptr, int value) +{ + int* iptr = (int*)&(ptr[10]); + iptr[0] = value; } -// Get or set a tetrahedron's marker. +// Get or set a tetrahedron's marker. // Set 'value = 0' cleans all the face/edge flags. -inline int tetgenmesh::elemmarker(tetrahedron* ptr) { - return ((int *) (ptr))[elemmarkerindex]; +inline int tetgenmesh::elemmarker(tetrahedron* ptr) +{ + return ((int*)(ptr))[elemmarkerindex]; } -inline void tetgenmesh::setelemmarker(tetrahedron* ptr, int value) { - ((int *) (ptr))[elemmarkerindex] = value; +inline void tetgenmesh::setelemmarker(tetrahedron* ptr, int value) +{ + ((int*)(ptr))[elemmarkerindex] = value; } // infect(), infected(), uninfect() -- primitives to flag or unflag a // tetrahedron. The last bit of the element marker is flagged (1) // or unflagged (0). -inline void tetgenmesh::infect(triface& t) { - ((int *) (t.tet))[elemmarkerindex] |= 1; +inline void tetgenmesh::infect(triface& t) +{ + ((int*)(t.tet))[elemmarkerindex] |= 1; } -inline void tetgenmesh::uninfect(triface& t) { - ((int *) (t.tet))[elemmarkerindex] &= ~1; +inline void tetgenmesh::uninfect(triface& t) +{ + ((int*)(t.tet))[elemmarkerindex] &= ~1; } -inline bool tetgenmesh::infected(triface& t) { - return (((int *) (t.tet))[elemmarkerindex] & 1) != 0; +inline bool tetgenmesh::infected(triface& t) +{ + return (((int*)(t.tet))[elemmarkerindex] & 1) != 0; } // marktest(), marktested(), unmarktest() -- primitives to flag or unflag a // tetrahedron. Use the second lowerest bit of the element marker. -inline void tetgenmesh::marktest(triface& t) { - ((int *) (t.tet))[elemmarkerindex] |= 2; +inline void tetgenmesh::marktest(triface& t) +{ + ((int*)(t.tet))[elemmarkerindex] |= 2; } -inline void tetgenmesh::unmarktest(triface& t) { - ((int *) (t.tet))[elemmarkerindex] &= ~2; +inline void tetgenmesh::unmarktest(triface& t) +{ + ((int*)(t.tet))[elemmarkerindex] &= ~2; } - -inline bool tetgenmesh::marktested(triface& t) { - return (((int *) (t.tet))[elemmarkerindex] & 2) != 0; + +inline bool tetgenmesh::marktested(triface& t) +{ + return (((int*)(t.tet))[elemmarkerindex] & 2) != 0; } // markface(), unmarkface(), facemarked() -- primitives to flag or unflag a // face of a tetrahedron. From the last 3rd to 6th bits are used for -// face markers, e.g., the last third bit corresponds to loc = 0. +// face markers, e.g., the last third bit corresponds to loc = 0. -inline void tetgenmesh::markface(triface& t) { - ((int *) (t.tet))[elemmarkerindex] |= (4 << (t.ver & 3)); +inline void tetgenmesh::markface(triface& t) +{ + ((int*)(t.tet))[elemmarkerindex] |= (4 << (t.ver & 3)); } -inline void tetgenmesh::unmarkface(triface& t) { - ((int *) (t.tet))[elemmarkerindex] &= ~(4 << (t.ver & 3)); +inline void tetgenmesh::unmarkface(triface& t) +{ + ((int*)(t.tet))[elemmarkerindex] &= ~(4 << (t.ver & 3)); } -inline bool tetgenmesh::facemarked(triface& t) { - return (((int *) (t.tet))[elemmarkerindex] & (4 << (t.ver & 3))) != 0; +inline bool tetgenmesh::facemarked(triface& t) +{ + return (((int*)(t.tet))[elemmarkerindex] & (4 << (t.ver & 3))) != 0; } // markedge(), unmarkedge(), edgemarked() -- primitives to flag or unflag an // edge of a tetrahedron. From the last 7th to 12th bits are used for -// edge markers, e.g., the last 7th bit corresponds to the 0th edge, etc. +// edge markers, e.g., the last 7th bit corresponds to the 0th edge, etc. // Remark: The last 7th bit is marked by 2^6 = 64. -inline void tetgenmesh::markedge(triface& t) { - ((int *) (t.tet))[elemmarkerindex] |= (int) (64 << ver2edge[(t).ver]); +inline void tetgenmesh::markedge(triface& t) +{ + ((int*)(t.tet))[elemmarkerindex] |= (int)(64 << ver2edge[(t).ver]); } -inline void tetgenmesh::unmarkedge(triface& t) { - ((int *) (t.tet))[elemmarkerindex] &= ~(int) (64 << ver2edge[(t).ver]); +inline void tetgenmesh::unmarkedge(triface& t) +{ + ((int*)(t.tet))[elemmarkerindex] &= ~(int)(64 << ver2edge[(t).ver]); } -inline bool tetgenmesh::edgemarked(triface& t) { - return (((int *) (t.tet))[elemmarkerindex] & - (int) (64 << ver2edge[(t).ver])) != 0; +inline bool tetgenmesh::edgemarked(triface& t) +{ + return (((int*)(t.tet))[elemmarkerindex] & (int)(64 << ver2edge[(t).ver])) != 0; } // marktest2(), unmarktest2(), marktest2ed() -- primitives to flag and unflag // a tetrahedron. The 13th bit (2^12 = 4096) is used for this flag. -inline void tetgenmesh::marktest2(triface& t) { - ((int *) (t.tet))[elemmarkerindex] |= (int) (4096); +inline void tetgenmesh::marktest2(triface& t) +{ + ((int*)(t.tet))[elemmarkerindex] |= (int)(4096); } -inline void tetgenmesh::unmarktest2(triface& t) { - ((int *) (t.tet))[elemmarkerindex] &= ~(int) (4096); +inline void tetgenmesh::unmarktest2(triface& t) +{ + ((int*)(t.tet))[elemmarkerindex] &= ~(int)(4096); } -inline bool tetgenmesh::marktest2ed(triface& t) { - return (((int *) (t.tet))[elemmarkerindex] & (int) (4096)) != 0; +inline bool tetgenmesh::marktest2ed(triface& t) +{ + return (((int*)(t.tet))[elemmarkerindex] & (int)(4096)) != 0; } // elemcounter(), setelemcounter() -- primitives to read or ser a (small) // integer counter in this tet. It is saved from the 16th bit. On 32 bit -// system, the range of the counter is [0, 2^15 = 32768]. +// system, the range of the counter is [0, 2^15 = 32768]. -inline int tetgenmesh::elemcounter(triface& t) { - return (((int *) (t.tet))[elemmarkerindex]) >> 16; +inline int tetgenmesh::elemcounter(triface& t) +{ + return (((int*)(t.tet))[elemmarkerindex]) >> 16; } -inline void tetgenmesh::setelemcounter(triface& t, int value) { - int c = ((int *) (t.tet))[elemmarkerindex]; - // Clear the old counter while keep the other flags. - c &= 65535; // sum_{i=0^15} 2^i - c |= (value << 16); - ((int *) (t.tet))[elemmarkerindex] = c; +inline void tetgenmesh::setelemcounter(triface& t, int value) +{ + int c = ((int*)(t.tet))[elemmarkerindex]; + // Clear the old counter while keep the other flags. + c &= 65535; // sum_{i=0^15} 2^i + c |= (value << 16); + ((int*)(t.tet))[elemmarkerindex] = c; } -inline void tetgenmesh::increaseelemcounter(triface& t) { - int c = elemcounter(t); - setelemcounter(t, c + 1); +inline void tetgenmesh::increaseelemcounter(triface& t) +{ + int c = elemcounter(t); + setelemcounter(t, c + 1); } -inline void tetgenmesh::decreaseelemcounter(triface& t) { - int c = elemcounter(t); - setelemcounter(t, c - 1); +inline void tetgenmesh::decreaseelemcounter(triface& t) +{ + int c = elemcounter(t); + setelemcounter(t, c - 1); } // ishulltet() tests if t is a hull tetrahedron. -inline bool tetgenmesh::ishulltet(triface& t) { - return (point) (t).tet[7] == dummypoint; +inline bool tetgenmesh::ishulltet(triface& t) +{ + return (point)(t).tet[7] == dummypoint; } // isdeadtet() tests if t is a tetrahedron is dead. -inline bool tetgenmesh::isdeadtet(triface& t) { - return ((t.tet == NULL) || (t.tet[4] == NULL)); +inline bool tetgenmesh::isdeadtet(triface& t) +{ + return ((t.tet == NULL) || (t.tet[4] == NULL)); } //============================================================================// @@ -2913,35 +3037,38 @@ inline bool tetgenmesh::isdeadtet(triface& t) { // bits of each pointer by 'sencode()'. 'sdecode()' decodes a pointer, // extracting an edge version and a pointer to the beginning of a subface. -inline void tetgenmesh::sdecode(shellface sptr, face& s) { - s.shver = (int) ((uintptr_t) (sptr) & (uintptr_t) 7); - s.sh = (shellface *) ((uintptr_t) (sptr) ^ (uintptr_t) (s.shver)); +inline void tetgenmesh::sdecode(shellface sptr, face& s) +{ + s.shver = (int)((uintptr_t)(sptr) & (uintptr_t)7); + s.sh = (shellface*)((uintptr_t)(sptr) ^ (uintptr_t)(s.shver)); } -inline tetgenmesh::shellface tetgenmesh::sencode(face& s) { - return (shellface) ((uintptr_t) s.sh | (uintptr_t) s.shver); +inline tetgenmesh::shellface tetgenmesh::sencode(face& s) +{ + return (shellface)((uintptr_t)s.sh | (uintptr_t)s.shver); } -inline tetgenmesh::shellface tetgenmesh::sencode2(shellface *sh, int shver) { - return (shellface) ((uintptr_t) sh | (uintptr_t) shver); +inline tetgenmesh::shellface tetgenmesh::sencode2(shellface* sh, int shver) +{ + return (shellface)((uintptr_t)sh | (uintptr_t)shver); } // sbond() bonds two subfaces (s1) and (s2) together. s1 and s2 must refer // to the same edge. No requirement is needed on their orientations. -inline void tetgenmesh::sbond(face& s1, face& s2) +inline void tetgenmesh::sbond(face& s1, face& s2) { - s1.sh[s1.shver >> 1] = sencode(s2); - s2.sh[s2.shver >> 1] = sencode(s1); + s1.sh[s1.shver >> 1] = sencode(s2); + s2.sh[s2.shver >> 1] = sencode(s1); } // sbond1() bonds s1 <== s2, i.e., after bonding, s1 is pointing to s2, // but s2 is not pointing to s1. s1 and s2 must refer to the same edge. // No requirement is needed on their orientations. -inline void tetgenmesh::sbond1(face& s1, face& s2) +inline void tetgenmesh::sbond1(face& s1, face& s2) { - s1.sh[s1.shver >> 1] = sencode(s2); + s1.sh[s1.shver >> 1] = sencode(s2); } // Dissolve a subface bond (from one side). Note that the other subface @@ -2949,227 +3076,216 @@ inline void tetgenmesh::sbond1(face& s1, face& s2) inline void tetgenmesh::sdissolve(face& s) { - s.sh[s.shver >> 1] = NULL; + s.sh[s.shver >> 1] = NULL; } // spivot() finds the adjacent subface (s2) for a given subface (s1). // s1 and s2 share at the same edge. -inline void tetgenmesh::spivot(face& s1, face& s2) +inline void tetgenmesh::spivot(face& s1, face& s2) { - shellface sptr = s1.sh[s1.shver >> 1]; - sdecode(sptr, s2); + shellface sptr = s1.sh[s1.shver >> 1]; + sdecode(sptr, s2); } -inline void tetgenmesh::spivotself(face& s) +inline void tetgenmesh::spivotself(face& s) { - shellface sptr = s.sh[s.shver >> 1]; - sdecode(sptr, s); + shellface sptr = s.sh[s.shver >> 1]; + sdecode(sptr, s); } // These primitives determine or set the origin, destination, or apex // of a subface with respect to the edge version. -inline tetgenmesh::point tetgenmesh::sorg(face& s) +inline tetgenmesh::point tetgenmesh::sorg(face& s) { - return (point) s.sh[sorgpivot[s.shver]]; + return (point)s.sh[sorgpivot[s.shver]]; } -inline tetgenmesh::point tetgenmesh::sdest(face& s) +inline tetgenmesh::point tetgenmesh::sdest(face& s) { - return (point) s.sh[sdestpivot[s.shver]]; + return (point)s.sh[sdestpivot[s.shver]]; } -inline tetgenmesh::point tetgenmesh::sapex(face& s) +inline tetgenmesh::point tetgenmesh::sapex(face& s) { - return (point) s.sh[sapexpivot[s.shver]]; + return (point)s.sh[sapexpivot[s.shver]]; } -inline void tetgenmesh::setsorg(face& s, point pointptr) +inline void tetgenmesh::setsorg(face& s, point pointptr) { - s.sh[sorgpivot[s.shver]] = (shellface) pointptr; + s.sh[sorgpivot[s.shver]] = (shellface)pointptr; } -inline void tetgenmesh::setsdest(face& s, point pointptr) +inline void tetgenmesh::setsdest(face& s, point pointptr) { - s.sh[sdestpivot[s.shver]] = (shellface) pointptr; + s.sh[sdestpivot[s.shver]] = (shellface)pointptr; } -inline void tetgenmesh::setsapex(face& s, point pointptr) +inline void tetgenmesh::setsapex(face& s, point pointptr) { - s.sh[sapexpivot[s.shver]] = (shellface) pointptr; + s.sh[sapexpivot[s.shver]] = (shellface)pointptr; } -#define setshvertices(s, pa, pb, pc)\ - setsorg(s, pa);\ - setsdest(s, pb);\ - setsapex(s, pc) +#define setshvertices(s, pa, pb, pc) \ + setsorg(s, pa); \ + setsdest(s, pb); \ + setsapex(s, pc) // sesym() reserves the direction of the lead edge. -inline void tetgenmesh::sesym(face& s1, face& s2) +inline void tetgenmesh::sesym(face& s1, face& s2) { - s2.sh = s1.sh; - s2.shver = (s1.shver ^ 1); // Inverse the last bit. + s2.sh = s1.sh; + s2.shver = (s1.shver ^ 1); // Inverse the last bit. } -inline void tetgenmesh::sesymself(face& s) +inline void tetgenmesh::sesymself(face& s) { - s.shver ^= 1; + s.shver ^= 1; } // senext() finds the next edge (counterclockwise) in the same orientation // of this face. -inline void tetgenmesh::senext(face& s1, face& s2) +inline void tetgenmesh::senext(face& s1, face& s2) { - s2.sh = s1.sh; - s2.shver = snextpivot[s1.shver]; + s2.sh = s1.sh; + s2.shver = snextpivot[s1.shver]; } -inline void tetgenmesh::senextself(face& s) +inline void tetgenmesh::senextself(face& s) { - s.shver = snextpivot[s.shver]; + s.shver = snextpivot[s.shver]; } -inline void tetgenmesh::senext2(face& s1, face& s2) +inline void tetgenmesh::senext2(face& s1, face& s2) { - s2.sh = s1.sh; - s2.shver = snextpivot[snextpivot[s1.shver]]; + s2.sh = s1.sh; + s2.shver = snextpivot[snextpivot[s1.shver]]; } -inline void tetgenmesh::senext2self(face& s) +inline void tetgenmesh::senext2self(face& s) { - s.shver = snextpivot[snextpivot[s.shver]]; + s.shver = snextpivot[snextpivot[s.shver]]; } - // Check or set a subface's maximum area bound. -inline REAL tetgenmesh::areabound(face& s) +inline REAL tetgenmesh::areabound(face& s) { - return ((REAL *) (s.sh))[areaboundindex]; + return ((REAL*)(s.sh))[areaboundindex]; } -inline void tetgenmesh::setareabound(face& s, REAL value) +inline void tetgenmesh::setareabound(face& s, REAL value) { - ((REAL *) (s.sh))[areaboundindex] = value; + ((REAL*)(s.sh))[areaboundindex] = value; } // These two primitives read or set a shell marker. Shell markers are used // to hold user boundary information. -inline int tetgenmesh::shellmark(face& s) +inline int tetgenmesh::shellmark(face& s) { - return ((int *) (s.sh))[shmarkindex]; + return ((int*)(s.sh))[shmarkindex]; } -inline void tetgenmesh::setshellmark(face& s, int value) +inline void tetgenmesh::setshellmark(face& s, int value) { - ((int *) (s.sh))[shmarkindex] = value; + ((int*)(s.sh))[shmarkindex] = value; } - - // sinfect(), sinfected(), suninfect() -- primitives to flag or unflag a // subface. The last bit of ((int *) ((s).sh))[shmarkindex+1] is flagged. -inline void tetgenmesh::sinfect(face& s) +inline void tetgenmesh::sinfect(face& s) { - ((int *) ((s).sh))[shmarkindex+1] = - (((int *) ((s).sh))[shmarkindex+1] | (int) 1); + ((int*)((s).sh))[shmarkindex + 1] = (((int*)((s).sh))[shmarkindex + 1] | (int)1); } -inline void tetgenmesh::suninfect(face& s) +inline void tetgenmesh::suninfect(face& s) { - ((int *) ((s).sh))[shmarkindex+1] = - (((int *) ((s).sh))[shmarkindex+1] & ~(int) 1); + ((int*)((s).sh))[shmarkindex + 1] = (((int*)((s).sh))[shmarkindex + 1] & ~(int)1); } // Test a subface for viral infection. -inline bool tetgenmesh::sinfected(face& s) +inline bool tetgenmesh::sinfected(face& s) { - return (((int *) ((s).sh))[shmarkindex+1] & (int) 1) != 0; + return (((int*)((s).sh))[shmarkindex + 1] & (int)1) != 0; } // smarktest(), smarktested(), sunmarktest() -- primitives to flag or unflag // a subface. The last 2nd bit of the integer is flagged. -inline void tetgenmesh::smarktest(face& s) +inline void tetgenmesh::smarktest(face& s) { - ((int *) ((s).sh))[shmarkindex+1] = - (((int *)((s).sh))[shmarkindex+1] | (int) 2); + ((int*)((s).sh))[shmarkindex + 1] = (((int*)((s).sh))[shmarkindex + 1] | (int)2); } -inline void tetgenmesh::sunmarktest(face& s) +inline void tetgenmesh::sunmarktest(face& s) { - ((int *) ((s).sh))[shmarkindex+1] = - (((int *)((s).sh))[shmarkindex+1] & ~(int)2); + ((int*)((s).sh))[shmarkindex + 1] = (((int*)((s).sh))[shmarkindex + 1] & ~(int)2); } -inline bool tetgenmesh::smarktested(face& s) +inline bool tetgenmesh::smarktested(face& s) { - return ((((int *) ((s).sh))[shmarkindex+1] & (int) 2) != 0); + return ((((int*)((s).sh))[shmarkindex + 1] & (int)2) != 0); } -// smarktest2(), smarktest2ed(), sunmarktest2() -- primitives to flag or +// smarktest2(), smarktest2ed(), sunmarktest2() -- primitives to flag or // unflag a subface. The last 3rd bit of the integer is flagged. -inline void tetgenmesh::smarktest2(face& s) +inline void tetgenmesh::smarktest2(face& s) { - ((int *) ((s).sh))[shmarkindex+1] = - (((int *)((s).sh))[shmarkindex+1] | (int) 4); + ((int*)((s).sh))[shmarkindex + 1] = (((int*)((s).sh))[shmarkindex + 1] | (int)4); } -inline void tetgenmesh::sunmarktest2(face& s) +inline void tetgenmesh::sunmarktest2(face& s) { - ((int *) ((s).sh))[shmarkindex+1] = - (((int *)((s).sh))[shmarkindex+1] & ~(int)4); + ((int*)((s).sh))[shmarkindex + 1] = (((int*)((s).sh))[shmarkindex + 1] & ~(int)4); } -inline bool tetgenmesh::smarktest2ed(face& s) +inline bool tetgenmesh::smarktest2ed(face& s) { - return ((((int *) ((s).sh))[shmarkindex+1] & (int) 4) != 0); + return ((((int*)((s).sh))[shmarkindex + 1] & (int)4) != 0); } // The last 4th bit of ((int *) ((s).sh))[shmarkindex+1] is flagged. -inline void tetgenmesh::smarktest3(face& s) +inline void tetgenmesh::smarktest3(face& s) { - ((int *) ((s).sh))[shmarkindex+1] = - (((int *)((s).sh))[shmarkindex+1] | (int) 8); + ((int*)((s).sh))[shmarkindex + 1] = (((int*)((s).sh))[shmarkindex + 1] | (int)8); } -inline void tetgenmesh::sunmarktest3(face& s) +inline void tetgenmesh::sunmarktest3(face& s) { - ((int *) ((s).sh))[shmarkindex+1] = - (((int *)((s).sh))[shmarkindex+1] & ~(int)8); + ((int*)((s).sh))[shmarkindex + 1] = (((int*)((s).sh))[shmarkindex + 1] & ~(int)8); } -inline bool tetgenmesh::smarktest3ed(face& s) +inline bool tetgenmesh::smarktest3ed(face& s) { - return ((((int *) ((s).sh))[shmarkindex+1] & (int) 8) != 0); + return ((((int*)((s).sh))[shmarkindex + 1] & (int)8) != 0); } - // Each facet has a unique index (automatically indexed). Starting from '0'. -// We save this index in the same field of the shell type. +// We save this index in the same field of the shell type. inline void tetgenmesh::setfacetindex(face& s, int value) { - ((int *) (s.sh))[shmarkindex + 2] = value; + ((int*)(s.sh))[shmarkindex + 2] = value; } inline int tetgenmesh::getfacetindex(face& s) { - return ((int *) (s.sh))[shmarkindex + 2]; + return ((int*)(s.sh))[shmarkindex + 2]; } // Tests if the subface (subsegment) s is dead. -inline bool tetgenmesh::isdeadsh(face& s) { - return ((s.sh == NULL) || (s.sh[3] == NULL)); +inline bool tetgenmesh::isdeadsh(face& s) +{ + return ((s.sh == NULL) || (s.sh[3] == NULL)); } //============================================================================// @@ -3180,26 +3296,26 @@ inline bool tetgenmesh::isdeadsh(face& s) { // tsbond() bond a tetrahedron (t) and a subface (s) together. // Note that t and s must be the same face and the same edge. Moreover, -// t and s have the same orientation. +// t and s have the same orientation. // Since the edge number in t and in s can be any number in {0,1,2}. We bond // the edge in s which corresponds to t's 0th edge, and vice versa. inline void tetgenmesh::tsbond(triface& t, face& s) { - if ((t).tet[9] == NULL) { - // Allocate space for this tet. - (t).tet[9] = (tetrahedron) tet2subpool->alloc(); - // Initialize. - for (int i = 0; i < 4; i++) { - ((shellface *) (t).tet[9])[i] = NULL; + if ((t).tet[9] == NULL) + { + // Allocate space for this tet. + (t).tet[9] = (tetrahedron)tet2subpool->alloc(); + // Initialize. + for (int i = 0; i < 4; i++) + { + ((shellface*)(t).tet[9])[i] = NULL; + } } - } - // Bond t <== s. - ((shellface *) (t).tet[9])[(t).ver & 3] = - sencode2((s).sh, tsbondtbl[t.ver][s.shver]); - // Bond s <== t. - s.sh[9 + ((s).shver & 1)] = - (shellface) encode2((t).tet, stbondtbl[t.ver][s.shver]); + // Bond t <== s. + ((shellface*)(t).tet[9])[(t).ver & 3] = sencode2((s).sh, tsbondtbl[t.ver][s.shver]); + // Bond s <== t. + s.sh[9 + ((s).shver & 1)] = (shellface)encode2((t).tet, stbondtbl[t.ver][s.shver]); } // tspivot() finds a subface (s) abutting on the given tetrahdera (t). @@ -3207,54 +3323,55 @@ inline void tetgenmesh::tsbond(triface& t, face& s) // the subface s, and s and t must be at the same edge wth the same // orientation. -inline void tetgenmesh::tspivot(triface& t, face& s) +inline void tetgenmesh::tspivot(triface& t, face& s) { - if ((t).tet[9] == NULL) { - (s).sh = NULL; - return; - } - // Get the attached subface s. - sdecode(((shellface *) (t).tet[9])[(t).ver & 3], (s)); - (s).shver = tspivottbl[t.ver][s.shver]; + if ((t).tet[9] == NULL) + { + (s).sh = NULL; + return; + } + // Get the attached subface s. + sdecode(((shellface*)(t).tet[9])[(t).ver & 3], (s)); + (s).shver = tspivottbl[t.ver][s.shver]; } // Quickly check if the handle (t, v) is a subface. -#define issubface(t) \ - ((t).tet[9] && ((t).tet[9])[(t).ver & 3]) +#define issubface(t) ((t).tet[9] && ((t).tet[9])[(t).ver & 3]) // stpivot() finds a tetrahedron (t) abutting a given subface (s). // Return the t (if it exists) with the same edge and the same // orientation of s. -inline void tetgenmesh::stpivot(face& s, triface& t) +inline void tetgenmesh::stpivot(face& s, triface& t) { - decode((tetrahedron) s.sh[9 + (s.shver & 1)], t); - if ((t).tet == NULL) { - return; - } - (t).ver = stpivottbl[t.ver][s.shver]; + decode((tetrahedron)s.sh[9 + (s.shver & 1)], t); + if ((t).tet == NULL) + { + return; + } + (t).ver = stpivottbl[t.ver][s.shver]; } // Quickly check if this subface is attached to a tetrahedron. -#define isshtet(s) \ - ((s).sh[9 + ((s).shver & 1)]) +#define isshtet(s) ((s).sh[9 + ((s).shver & 1)]) // tsdissolve() dissolve a bond (from the tetrahedron side). -inline void tetgenmesh::tsdissolve(triface& t) +inline void tetgenmesh::tsdissolve(triface& t) { - if ((t).tet[9] != NULL) { - ((shellface *) (t).tet[9])[(t).ver & 3] = NULL; - } + if ((t).tet[9] != NULL) + { + ((shellface*)(t).tet[9])[(t).ver & 3] = NULL; + } } // stdissolve() dissolve a bond (from the subface side). -inline void tetgenmesh::stdissolve(face& s) +inline void tetgenmesh::stdissolve(face& s) { - (s).sh[9] = NULL; - (s).sh[10] = NULL; + (s).sh[9] = NULL; + (s).sh[10] = NULL; } //============================================================================// @@ -3265,36 +3382,35 @@ inline void tetgenmesh::stdissolve(face& s) // ssbond() bond a subface to a subsegment. -inline void tetgenmesh::ssbond(face& s, face& edge) +inline void tetgenmesh::ssbond(face& s, face& edge) { - s.sh[6 + (s.shver >> 1)] = sencode(edge); - edge.sh[0] = sencode(s); + s.sh[6 + (s.shver >> 1)] = sencode(edge); + edge.sh[0] = sencode(s); } -inline void tetgenmesh::ssbond1(face& s, face& edge) +inline void tetgenmesh::ssbond1(face& s, face& edge) { - s.sh[6 + (s.shver >> 1)] = sencode(edge); - //edge.sh[0] = sencode(s); + s.sh[6 + (s.shver >> 1)] = sencode(edge); + //edge.sh[0] = sencode(s); } // ssdisolve() dissolve a bond (from the subface side) -inline void tetgenmesh::ssdissolve(face& s) +inline void tetgenmesh::ssdissolve(face& s) { - s.sh[6 + (s.shver >> 1)] = NULL; + s.sh[6 + (s.shver >> 1)] = NULL; } // sspivot() finds a subsegment abutting a subface. -inline void tetgenmesh::sspivot(face& s, face& edge) +inline void tetgenmesh::sspivot(face& s, face& edge) { - sdecode((shellface) s.sh[6 + (s.shver >> 1)], edge); + sdecode((shellface)s.sh[6 + (s.shver >> 1)], edge); } // Quickly check if the edge is a subsegment. -#define isshsubseg(s) \ - ((s).sh[6 + ((s).shver >> 1)]) +#define isshsubseg(s) ((s).sh[6 + ((s).shver >> 1)]) //============================================================================// // // @@ -3304,51 +3420,56 @@ inline void tetgenmesh::sspivot(face& s, face& edge) inline void tetgenmesh::tssbond1(triface& t, face& s) { - if ((t).tet[8] == NULL) { - // Allocate space for this tet. - (t).tet[8] = (tetrahedron) tet2segpool->alloc(); - // Initialization. - for (int i = 0; i < 6; i++) { - ((shellface *) (t).tet[8])[i] = NULL; + if ((t).tet[8] == NULL) + { + // Allocate space for this tet. + (t).tet[8] = (tetrahedron)tet2segpool->alloc(); + // Initialization. + for (int i = 0; i < 6; i++) + { + ((shellface*)(t).tet[8])[i] = NULL; + } } - } - ((shellface *) (t).tet[8])[ver2edge[(t).ver]] = sencode((s)); + ((shellface*)(t).tet[8])[ver2edge[(t).ver]] = sencode((s)); } -inline void tetgenmesh::sstbond1(face& s, triface& t) +inline void tetgenmesh::sstbond1(face& s, triface& t) { - ((tetrahedron *) (s).sh)[9] = encode(t); + ((tetrahedron*)(s).sh)[9] = encode(t); } inline void tetgenmesh::tssdissolve1(triface& t) { - if ((t).tet[8] != NULL) { - ((shellface *) (t).tet[8])[ver2edge[(t).ver]] = NULL; - } + if ((t).tet[8] != NULL) + { + ((shellface*)(t).tet[8])[ver2edge[(t).ver]] = NULL; + } } -inline void tetgenmesh::sstdissolve1(face& s) +inline void tetgenmesh::sstdissolve1(face& s) { - ((tetrahedron *) (s).sh)[9] = NULL; + ((tetrahedron*)(s).sh)[9] = NULL; } inline void tetgenmesh::tsspivot1(triface& t, face& s) { - if ((t).tet[8] != NULL) { - sdecode(((shellface *) (t).tet[8])[ver2edge[(t).ver]], s); - } else { - (s).sh = NULL; - } + if ((t).tet[8] != NULL) + { + sdecode(((shellface*)(t).tet[8])[ver2edge[(t).ver]], s); + } + else + { + (s).sh = NULL; + } } // Quickly check whether 't' is a segment or not. -#define issubseg(t) \ - ((t).tet[8] && ((t).tet[8])[ver2edge[(t).ver]]) +#define issubseg(t) ((t).tet[8] && ((t).tet[8])[ver2edge[(t).ver]]) -inline void tetgenmesh::sstpivot1(face& s, triface& t) +inline void tetgenmesh::sstpivot1(face& s, triface& t) { - decode((tetrahedron) s.sh[9], t); + decode((tetrahedron)s.sh[9], t); } //============================================================================// @@ -3357,183 +3478,217 @@ inline void tetgenmesh::sstpivot1(face& s, triface& t) // // //============================================================================// -inline int tetgenmesh::pointmark(point pt) { - return ((int *) (pt))[pointmarkindex]; +inline int tetgenmesh::pointmark(point pt) +{ + return ((int*)(pt))[pointmarkindex]; } -inline void tetgenmesh::setpointmark(point pt, int value) { - ((int *) (pt))[pointmarkindex] = value; +inline void tetgenmesh::setpointmark(point pt, int value) +{ + ((int*)(pt))[pointmarkindex] = value; } - // These two primitives set and read the type of the point. -inline enum tetgenmesh::verttype tetgenmesh::pointtype(point pt) { - return (enum verttype) (((int *) (pt))[pointmarkindex + 1] >> (int) 8); +inline enum tetgenmesh::verttype tetgenmesh::pointtype(point pt) +{ + return (enum verttype)(((int*)(pt))[pointmarkindex + 1] >> (int)8); } -inline void tetgenmesh::setpointtype(point pt, enum verttype value) { - ((int *) (pt))[pointmarkindex + 1] = - ((int) value << 8) + (((int *) (pt))[pointmarkindex + 1] & (int) 255); +inline void tetgenmesh::setpointtype(point pt, enum verttype value) +{ + ((int*)(pt))[pointmarkindex + 1] = ((int)value << 8) + (((int*)(pt))[pointmarkindex + 1] & (int)255); } // pinfect(), puninfect(), pinfected() -- primitives to flag or unflag // a point. The last bit of the integer '[pointindex+1]' is flagged. -inline void tetgenmesh::pinfect(point pt) { - ((int *) (pt))[pointmarkindex + 1] |= (int) 1; +inline void tetgenmesh::pinfect(point pt) +{ + ((int*)(pt))[pointmarkindex + 1] |= (int)1; } -inline void tetgenmesh::puninfect(point pt) { - ((int *) (pt))[pointmarkindex + 1] &= ~(int) 1; +inline void tetgenmesh::puninfect(point pt) +{ + ((int*)(pt))[pointmarkindex + 1] &= ~(int)1; } -inline bool tetgenmesh::pinfected(point pt) { - return (((int *) (pt))[pointmarkindex + 1] & (int) 1) != 0; +inline bool tetgenmesh::pinfected(point pt) +{ + return (((int*)(pt))[pointmarkindex + 1] & (int)1) != 0; } -// pmarktest(), punmarktest(), pmarktested() -- more primitives to -// flag or unflag a point. +// pmarktest(), punmarktest(), pmarktested() -- more primitives to +// flag or unflag a point. -inline void tetgenmesh::pmarktest(point pt) { - ((int *) (pt))[pointmarkindex + 1] |= (int) 2; +inline void tetgenmesh::pmarktest(point pt) +{ + ((int*)(pt))[pointmarkindex + 1] |= (int)2; } -inline void tetgenmesh::punmarktest(point pt) { - ((int *) (pt))[pointmarkindex + 1] &= ~(int) 2; +inline void tetgenmesh::punmarktest(point pt) +{ + ((int*)(pt))[pointmarkindex + 1] &= ~(int)2; } -inline bool tetgenmesh::pmarktested(point pt) { - return (((int *) (pt))[pointmarkindex + 1] & (int) 2) != 0; +inline bool tetgenmesh::pmarktested(point pt) +{ + return (((int*)(pt))[pointmarkindex + 1] & (int)2) != 0; } -inline void tetgenmesh::pmarktest2(point pt) { - ((int *) (pt))[pointmarkindex + 1] |= (int) 4; +inline void tetgenmesh::pmarktest2(point pt) +{ + ((int*)(pt))[pointmarkindex + 1] |= (int)4; } -inline void tetgenmesh::punmarktest2(point pt) { - ((int *) (pt))[pointmarkindex + 1] &= ~(int) 4; +inline void tetgenmesh::punmarktest2(point pt) +{ + ((int*)(pt))[pointmarkindex + 1] &= ~(int)4; } -inline bool tetgenmesh::pmarktest2ed(point pt) { - return (((int *) (pt))[pointmarkindex + 1] & (int) 4) != 0; +inline bool tetgenmesh::pmarktest2ed(point pt) +{ + return (((int*)(pt))[pointmarkindex + 1] & (int)4) != 0; } -inline void tetgenmesh::pmarktest3(point pt) { - ((int *) (pt))[pointmarkindex + 1] |= (int) 8; +inline void tetgenmesh::pmarktest3(point pt) +{ + ((int*)(pt))[pointmarkindex + 1] |= (int)8; } -inline void tetgenmesh::punmarktest3(point pt) { - ((int *) (pt))[pointmarkindex + 1] &= ~(int) 8; +inline void tetgenmesh::punmarktest3(point pt) +{ + ((int*)(pt))[pointmarkindex + 1] &= ~(int)8; } -inline bool tetgenmesh::pmarktest3ed(point pt) { - return (((int *) (pt))[pointmarkindex + 1] & (int) 8) != 0; +inline bool tetgenmesh::pmarktest3ed(point pt) +{ + return (((int*)(pt))[pointmarkindex + 1] & (int)8) != 0; } // Read and set the geometry tag of the point (used by -s option). -inline int tetgenmesh::pointgeomtag(point pt) { - return ((int *) (pt))[pointmarkindex + 2]; +inline int tetgenmesh::pointgeomtag(point pt) +{ + return ((int*)(pt))[pointmarkindex + 2]; } -inline void tetgenmesh::setpointgeomtag(point pt, int value) { - ((int *) (pt))[pointmarkindex + 2] = value; +inline void tetgenmesh::setpointgeomtag(point pt, int value) +{ + ((int*)(pt))[pointmarkindex + 2] = value; } // Read and set the u,v coordinates of the point (used by -s option). -inline REAL tetgenmesh::pointgeomuv(point pt, int i) { - return pt[pointparamindex + i]; +inline REAL tetgenmesh::pointgeomuv(point pt, int i) +{ + return pt[pointparamindex + i]; } -inline void tetgenmesh::setpointgeomuv(point pt, int i, REAL value) { - pt[pointparamindex + i] = value; +inline void tetgenmesh::setpointgeomuv(point pt, int i, REAL value) +{ + pt[pointparamindex + i] = value; } - - // These following primitives set and read a pointer to a tetrahedron // a subface/subsegment, a point, or a tet of background mesh. -inline tetgenmesh::tetrahedron tetgenmesh::point2tet(point pt) { - return ((tetrahedron *) (pt))[point2simindex]; +inline tetgenmesh::tetrahedron tetgenmesh::point2tet(point pt) +{ + return ((tetrahedron*)(pt))[point2simindex]; } -inline void tetgenmesh::setpoint2tet(point pt, tetrahedron value) { - ((tetrahedron *) (pt))[point2simindex] = value; +inline void tetgenmesh::setpoint2tet(point pt, tetrahedron value) +{ + ((tetrahedron*)(pt))[point2simindex] = value; } -inline tetgenmesh::point tetgenmesh::point2ppt(point pt) { - return (point) ((tetrahedron *) (pt))[point2simindex + 1]; +inline tetgenmesh::point tetgenmesh::point2ppt(point pt) +{ + return (point)((tetrahedron*)(pt))[point2simindex + 1]; } -inline void tetgenmesh::setpoint2ppt(point pt, point value) { - ((tetrahedron *) (pt))[point2simindex + 1] = (tetrahedron) value; +inline void tetgenmesh::setpoint2ppt(point pt, point value) +{ + ((tetrahedron*)(pt))[point2simindex + 1] = (tetrahedron)value; } -inline tetgenmesh::shellface tetgenmesh::point2sh(point pt) { - return (shellface) ((tetrahedron *) (pt))[point2simindex + 2]; +inline tetgenmesh::shellface tetgenmesh::point2sh(point pt) +{ + return (shellface)((tetrahedron*)(pt))[point2simindex + 2]; } -inline void tetgenmesh::setpoint2sh(point pt, shellface value) { - ((tetrahedron *) (pt))[point2simindex + 2] = (tetrahedron) value; +inline void tetgenmesh::setpoint2sh(point pt, shellface value) +{ + ((tetrahedron*)(pt))[point2simindex + 2] = (tetrahedron)value; } - -inline tetgenmesh::tetrahedron tetgenmesh::point2bgmtet(point pt) { - return ((tetrahedron *) (pt))[point2simindex + 3]; +inline tetgenmesh::tetrahedron tetgenmesh::point2bgmtet(point pt) +{ + return ((tetrahedron*)(pt))[point2simindex + 3]; } -inline void tetgenmesh::setpoint2bgmtet(point pt, tetrahedron value) { - ((tetrahedron *) (pt))[point2simindex + 3] = value; +inline void tetgenmesh::setpoint2bgmtet(point pt, tetrahedron value) +{ + ((tetrahedron*)(pt))[point2simindex + 3] = value; } - // The primitives for saving and getting the insertion radius. inline void tetgenmesh::setpointinsradius(point pt, REAL value) { - pt[pointinsradiusindex] = value; + pt[pointinsradiusindex] = value; } inline REAL tetgenmesh::getpointinsradius(point pt) { - return pt[pointinsradiusindex]; + return pt[pointinsradiusindex]; } -inline bool tetgenmesh::issteinerpoint(point pt) { - return (pointtype(pt) == FREESEGVERTEX) || (pointtype(pt) == FREEFACETVERTEX) - || (pointtype(pt) == FREEVOLVERTEX); +inline bool tetgenmesh::issteinerpoint(point pt) +{ + return (pointtype(pt) == FREESEGVERTEX) || (pointtype(pt) == FREEFACETVERTEX) || (pointtype(pt) == FREEVOLVERTEX); } // point2tetorg() Get the tetrahedron whose origin is the point. inline void tetgenmesh::point2tetorg(point pa, triface& searchtet) { - decode(point2tet(pa), searchtet); - if ((point) searchtet.tet[4] == pa) { - searchtet.ver = 11; - } else if ((point) searchtet.tet[5] == pa) { - searchtet.ver = 3; - } else if ((point) searchtet.tet[6] == pa) { - searchtet.ver = 7; - } else { - searchtet.ver = 0; - } + decode(point2tet(pa), searchtet); + if ((point)searchtet.tet[4] == pa) + { + searchtet.ver = 11; + } + else if ((point)searchtet.tet[5] == pa) + { + searchtet.ver = 3; + } + else if ((point)searchtet.tet[6] == pa) + { + searchtet.ver = 7; + } + else + { + searchtet.ver = 0; + } } // point2shorg() Get the subface/segment whose origin is the point. inline void tetgenmesh::point2shorg(point pa, face& searchsh) { - sdecode(point2sh(pa), searchsh); - if ((point) searchsh.sh[3] == pa) { - searchsh.shver = 0; - } else if ((point) searchsh.sh[4] == pa) { - searchsh.shver = (searchsh.sh[5] != NULL ? 2 : 1); - } else { - searchsh.shver = 4; - } + sdecode(point2sh(pa), searchsh); + if ((point)searchsh.sh[3] == pa) + { + searchsh.shver = 0; + } + else if ((point)searchsh.sh[4] == pa) + { + searchsh.shver = (searchsh.sh[5] != NULL ? 2 : 1); + } + else + { + searchsh.shver = 4; + } } // farsorg() Return the origin of the subsegment. @@ -3541,32 +3696,34 @@ inline void tetgenmesh::point2shorg(point pa, face& searchsh) inline tetgenmesh::point tetgenmesh::farsorg(face& s) { - face travesh, neighsh; + face travesh, neighsh; - travesh = s; - while (1) { - senext2(travesh, neighsh); - spivotself(neighsh); - if (neighsh.sh == NULL) break; - if (sorg(neighsh) != sorg(travesh)) sesymself(neighsh); - senext2(neighsh, travesh); - } - return sorg(travesh); + travesh = s; + while (1) + { + senext2(travesh, neighsh); + spivotself(neighsh); + if (neighsh.sh == NULL) break; + if (sorg(neighsh) != sorg(travesh)) sesymself(neighsh); + senext2(neighsh, travesh); + } + return sorg(travesh); } -inline tetgenmesh::point tetgenmesh::farsdest(face& s) +inline tetgenmesh::point tetgenmesh::farsdest(face& s) { - face travesh, neighsh; + face travesh, neighsh; - travesh = s; - while (1) { - senext(travesh, neighsh); - spivotself(neighsh); - if (neighsh.sh == NULL) break; - if (sdest(neighsh) != sdest(travesh)) sesymself(neighsh); - senext(neighsh, travesh); - } - return sdest(travesh); + travesh = s; + while (1) + { + senext(travesh, neighsh); + spivotself(neighsh); + if (neighsh.sh == NULL) break; + if (sdest(neighsh) != sdest(travesh)) sesymself(neighsh); + senext(neighsh, travesh); + } + return sdest(travesh); } /////////////////////////////////////////////////////////////////////////////// @@ -3576,38 +3733,34 @@ inline tetgenmesh::point tetgenmesh::farsdest(face& s) /////////////////////////////////////////////////////////////////////////////// // dot() returns the dot product: v1 dot v2. -inline REAL tetgenmesh::dot(REAL* v1, REAL* v2) +inline REAL tetgenmesh::dot(REAL* v1, REAL* v2) { - return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]; + return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]; } // cross() computes the cross product: n = v1 cross v2. -inline void tetgenmesh::cross(REAL* v1, REAL* v2, REAL* n) +inline void tetgenmesh::cross(REAL* v1, REAL* v2, REAL* n) { - n[0] = v1[1] * v2[2] - v2[1] * v1[2]; - n[1] = -(v1[0] * v2[2] - v2[0] * v1[2]); - n[2] = v1[0] * v2[1] - v2[0] * v1[1]; + n[0] = v1[1] * v2[2] - v2[1] * v1[2]; + n[1] = -(v1[0] * v2[2] - v2[0] * v1[2]); + n[2] = v1[0] * v2[1] - v2[0] * v1[1]; } // distance() computes the Euclidean distance between two points. inline REAL tetgenmesh::distance(REAL* p1, REAL* p2) { - return sqrt((p2[0] - p1[0]) * (p2[0] - p1[0]) + - (p2[1] - p1[1]) * (p2[1] - p1[1]) + - (p2[2] - p1[2]) * (p2[2] - p1[2])); + return sqrt((p2[0] - p1[0]) * (p2[0] - p1[0]) + (p2[1] - p1[1]) * (p2[1] - p1[1]) + + (p2[2] - p1[2]) * (p2[2] - p1[2])); } inline REAL tetgenmesh::distance2(REAL* p1, REAL* p2) { - return norm2(p2[0] - p1[0], p2[1] - p1[1], p2[2] - p1[2]); + return norm2(p2[0] - p1[0], p2[1] - p1[1], p2[2] - p1[2]); } inline REAL tetgenmesh::norm2(REAL x, REAL y, REAL z) { - return (x) * (x) + (y) * (y) + (z) * (z); + return (x) * (x) + (y) * (y) + (z) * (z); } - - -#endif // #ifndef tetgenH - +#endif // #ifndef tetgenH From 2073d905e511eae03fde62c7b5be84ee78a5d3d6 Mon Sep 17 00:00:00 2001 From: arlauwer Date: Fri, 19 Jan 2024 20:33:45 +0100 Subject: [PATCH 13/51] fixed traversal edge case changed search tree to use vertices instead of centroids --- SKIRT/core/TetraMeshSnapshot.cpp | 87 ++++++++++------------------- SKIRT/core/TetraMeshSnapshot.hpp | 10 ++-- SKIRT/core/TetraMeshSpatialGrid.cpp | 13 +---- SKIRT/core/TetraMeshSpatialGrid.hpp | 2 +- SKIRT/core/Tetrahedron.cpp | 25 ++++----- SKIRT/core/Tetrahedron.hpp | 8 +-- 6 files changed, 52 insertions(+), 93 deletions(-) diff --git a/SKIRT/core/TetraMeshSnapshot.cpp b/SKIRT/core/TetraMeshSnapshot.cpp index b4ba21a3..fe0770a2 100644 --- a/SKIRT/core/TetraMeshSnapshot.cpp +++ b/SKIRT/core/TetraMeshSnapshot.cpp @@ -144,7 +144,6 @@ class TetraMeshSnapshot::Node Node* _up; // ptr to the parent node Node* _left; // ptr to the left child node Node* _right; // ptr to the right child node - std::vector tetra; // returns the square of its argument static double sqr(double x) { return x * x; } @@ -316,16 +315,15 @@ namespace void TetraMeshSnapshot::buildMesh(const TetraMeshSpatialGrid& grid) { tetgenio in, out; - tetgenio::facet* f; - tetgenio::polygon* p; + // tetgenio::facet* f; + // tetgenio::polygon* p; in.firstnumber = 0; in.numberofpoints = 8 + 0; in.pointlist = new REAL[in.numberofpoints * 3]; - // in.tetunsuitable = &grid.needsSubdivide; in.tetunsuitable = [&grid](double* pa, double* pb, double* pc, double* pd, double vol) { - return grid.needsSubdivide(pa, pb, pc, pd, vol); + return grid.tetUnsuitable(pa, pb, pc, pd, vol); }; // bottom half (zmin) @@ -405,6 +403,7 @@ void TetraMeshSnapshot::buildMesh(const TetraMeshSpatialGrid& grid) numVertices = out.numberofpoints; _vertices.resize(numVertices); + _vertexTetra.resize(numVertices); for (int i = 0; i < numVertices; i++) { double x = out.pointlist[3 * i + 0]; @@ -432,7 +431,10 @@ void TetraMeshSnapshot::buildMesh(const TetraMeshSpatialGrid& grid) for (int c = 0; c < 4; c++) { indices[c] = out.tetrahedronlist[4 * i + c]; + vertices[c] = _vertices[indices[c]]; + _vertexTetra[indices[c]].push_back(i); // add this Tetra to all 4 of its Vertices + neighbors[c] = out.neighborlist[4 * i + c]; } for (int e = 0; e < 6; e++) @@ -454,11 +456,6 @@ void TetraMeshSnapshot::buildMesh(const TetraMeshSpatialGrid& grid) _tetrahedra[i] = new Tetra(vertices, indices, neighbors, edges); } - for (int i = 0; i < numTetra; i++) - { - _centroids.push_back(new Vec(_tetrahedra[i]->centroid())); - } - log()->info("number of vertices " + std::to_string(numVertices)); log()->info("number of edges " + std::to_string(numEdges)); log()->info("number of tetrahedra " + std::to_string(numTetra)); @@ -539,7 +536,7 @@ TetraMeshSnapshot::Node* TetraMeshSnapshot::buildTree(vector::iterator firs { auto median = length >> 1; std::nth_element(first, first + median, last, [this, depth](int m1, int m2) { - return m1 != m2 && lessthan(*_centroids[m1], *_centroids[m2], depth % 3); + return m1 != m2 && lessthan(*_vertices[m1], *_vertices[m2], depth % 3); }); return new TetraMeshSnapshot::Node(*(first + median), depth, buildTree(first, first + median, depth + 1), buildTree(first + median + 1, last, depth + 1)); @@ -552,13 +549,13 @@ TetraMeshSnapshot::Node* TetraMeshSnapshot::buildTree(vector::iterator firs void TetraMeshSnapshot::buildSearchPerBlock() { // abort if there are no cells - if (!numTetra) return; + if (!numVertices) return; log()->info("Building data structures to accelerate searching the tetrahedralization"); // ------------- block lists ------------- - // _nb = max(3, min(250, static_cast(cbrt(numTetra)))); - _nb = 1; + _nb = max(3, min(250, static_cast(cbrt(numTetra)))); + // _nb = 1; _nb2 = _nb * _nb; _nb3 = _nb * _nb * _nb; @@ -566,20 +563,12 @@ void TetraMeshSnapshot::buildSearchPerBlock() _blocklists.resize(_nb3); // add the tetrahedron to the lists for all blocks it may overlap - for (int m = 0; m != numTetra; ++m) + for (int m = 0; m != numVertices; ++m) { - std::set vertexBlockIndices; // block index for every vertex without duplicates - - for (int v = 0; v < 4; ++v) - { - int i, j, k; - _extent.cellIndices(i, j, k, *_tetrahedra[m]->_vertices[v], _nb, _nb, _nb); - - int blockIndex = i * _nb2 + j * _nb + k; + int i, j, k; + _extent.cellIndices(i, j, k, *_vertices[m], _nb, _nb, _nb); - if (vertexBlockIndices.insert(blockIndex).second) // if block index has not already been used - _blocklists[blockIndex].push_back(m); - } + _blocklists[i * _nb2 + j * _nb + k].push_back(m); } // compile block list statistics @@ -791,54 +780,31 @@ int TetraMeshSnapshot::cellIndex(Position bfr) const _extent.cellIndices(i, j, k, bfr, _nb, _nb, _nb); int b = i * _nb2 + j * _nb + k; - // look for the closest site in this block, using the search tree if there is one + // look for the closest vertex in this block using the search tree Node* tree = _blocktrees[b]; if (tree) { - int m = tree->nearest(bfr, _centroids)->m(); - // find all neighboring tetrahedra - const Tetra* tetra = _tetrahedra[m]; - if (tetra->Tetra::inside(bfr)) return m; - for (int n = 0; n < 4; n++) - { - int neighbor = tetra->_neighbors[n]; - if (neighbor != -1 && _tetrahedra[neighbor]->Tetra::inside(bfr)) return neighbor; - } + int m = tree->nearest(bfr, _vertices)->m(); - // 2nd neighbors WIP - std::set second_neighbors; - for (int n = 0; n < 4; n++) - { - int neighbor = tetra->_neighbors[n]; - if (neighbor != -1) - { - for (int m = 0; m < 4; m++) - { - int second = _tetrahedra[neighbor]->_neighbors[m]; - if (second != -1) second_neighbors.insert(second); - } - } - } - for (int m : second_neighbors) + for (int t : _vertexTetra[m]) { - if (_tetrahedra[m]->Tetra::inside(bfr)) return m; + if (_tetrahedra[t]->Tetra::inside(bfr)) return t; } - // - log()->warning("cellIndex failed to find using centroid!!!"); + + log()->error("cellIndex failed to find the tetrahedron"); } // if no tree or tetrahedron was not found loop over all tetrahedra for (int i = 0; i < numTetra; i++) { - const Tetra* tetra = _tetrahedra[i]; - if (tetra->Tetra::inside(bfr)) return i; + if (_tetrahedra[i]->Tetra::inside(bfr)) return i; } return -1; } //////////////////////////////////////////////////////////////////// -const Array& TetraMeshSnapshot::properties(int m) const +const Array& TetraMeshSnapshot::properties(int /*m*/) const { // return _sites[m]->properties(); } @@ -869,7 +835,7 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator public: MySegmentGenerator(const TetraMeshSnapshot* grid) : _grid(grid) {} - // #define WRITE + #define WRITE #ifdef WRITE std::ofstream out; @@ -879,7 +845,10 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator if (!out.is_open()) out.open("data/photon.txt"); } - void stopwriting() { out.close(); } + void stopwriting() + { + out.close(); + } void write(const Vec& exit, double ds, int face) { diff --git a/SKIRT/core/TetraMeshSnapshot.hpp b/SKIRT/core/TetraMeshSnapshot.hpp index d0a87eaf..7081cddc 100644 --- a/SKIRT/core/TetraMeshSnapshot.hpp +++ b/SKIRT/core/TetraMeshSnapshot.hpp @@ -7,12 +7,12 @@ #define TETRAMESHSNAPSHOT_HPP #include "Array.hpp" -#include "Snapshot.hpp" -#include -#include "array" #include "MediumSystem.hpp" +#include "Snapshot.hpp" #include "TetraMeshSpatialGrid.hpp" #include "Tetrahedron.hpp" +#include +#include "array" class PathSegmentGenerator; class SiteListInterface; class SpatialGridPath; @@ -206,8 +206,6 @@ class TetraMeshSnapshot : public Snapshot en.wikipedia.org/wiki/Kd-tree). */ void buildSearchSingle(); - bool inTetrahedra(const Tetra* tetra) const; - //====================== Output ===================== public: @@ -413,7 +411,7 @@ class TetraMeshSnapshot : public Snapshot vector _tetrahedra; vector _edges; vector _vertices; - vector _centroids; + vector> _vertexTetra; // data members initialized when processing snapshot input, but only if a density policy has been set Array _rhov; // density for each cell (not normalized) diff --git a/SKIRT/core/TetraMeshSpatialGrid.cpp b/SKIRT/core/TetraMeshSpatialGrid.cpp index b986a925..d3c0d57b 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.cpp +++ b/SKIRT/core/TetraMeshSpatialGrid.cpp @@ -114,20 +114,12 @@ void TetraMeshSpatialGrid::setupSelfBefore() _mesh = new TetraMeshSnapshot(this, extent(), *this); } -bool TetraMeshSpatialGrid::needsSubdivide(double* pa, double* pb, double* pc, double* pd, double vol) const +bool TetraMeshSpatialGrid::tetUnsuitable(double* pa, double* pb, double* pc, double* pd, double vol) const { Vec a(pa[0], pa[1], pa[2]); Vec b(pb[0], pb[1], pb[2]); Vec c(pc[0], pc[1], pc[2]); Vec d(pd[0], pd[1], pd[2]); - Tetra tetra(&a, &b, &c, &d); - - // double x = 0, y = 0, z = 0; - // x += (pa[0] + pb[0] + pc[0] + pd[0]) * .25; - // y += (pa[1] + pb[1] + pc[1] + pd[1]) * .25; - // z += (pa[2] + pb[2] + pc[2] + pd[2]) * .25; - // double density = ms.dustMassDensity(Position(x, y, z)); - // if (vol * density > 0.01 * M) return true; // results for the sampled mass or number densities, if applicable double rho = 0.; // dust mass density @@ -147,7 +139,8 @@ bool TetraMeshSpatialGrid::needsSubdivide(double* pa, double* pb, double* pc, do double s = random()->uniform(); double t = random()->uniform(); double u = random()->uniform(); - Position bfr = tetra.generatePosition(s, t, u); + double r = Tetra::generateBarycentric(s, t, u); + Position bfr(r * a + u * b + t * c + s * d); if (_hasDustAny) { double rhoi = 0.; diff --git a/SKIRT/core/TetraMeshSpatialGrid.hpp b/SKIRT/core/TetraMeshSpatialGrid.hpp index 13e3b6cc..348d0ca8 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.hpp +++ b/SKIRT/core/TetraMeshSpatialGrid.hpp @@ -99,7 +99,7 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid, public DensityInCellInterfac //======================== Other Functions ======================= public: - bool needsSubdivide(double* pa, double* pb, double* pc, double* pd, double vol) const; + bool tetUnsuitable(double* pa, double* pb, double* pc, double* pd, double vol) const; /** This function returns the number of cells in the grid. */ int numCells() const override; diff --git a/SKIRT/core/Tetrahedron.cpp b/SKIRT/core/Tetrahedron.cpp index 5874e718..526e9952 100644 --- a/SKIRT/core/Tetrahedron.cpp +++ b/SKIRT/core/Tetrahedron.cpp @@ -50,15 +50,6 @@ Tetra::Tetra(const std::array& vertices, const std::array& indi //////////////////////////////////////////////////////////////////// -Tetra::Tetra(Vec* va, Vec* vb, Vec* vc, Vec* vd) : _vertices({va, vb, vc, vd}) -{ - _volume = 1 / 6. - * abs(Vec::dot(Vec::cross(*_vertices[1] - *_vertices[0], *_vertices[2] - *_vertices[0]), - *_vertices[3] - *_vertices[0])); -} - -//////////////////////////////////////////////////////////////////// - double Tetra::getProd(const Plucker& ray, int t1, int t2) const { int e = (std::min(t1, t2) == 0) ? std::max(t1, t2) - 1 : t1 + t2; @@ -85,7 +76,8 @@ bool Tetra::intersects(std::array& barycoords, const Plucker& ray, in barycoords[i] = prod; sum += prod; } - for (int i = 0; i < 3; i++) barycoords[i] /= sum; + if (sum != 0) + for (int i = 0; i < 3; i++) barycoords[i] /= sum; return true; } @@ -175,7 +167,7 @@ const Array& Tetra::properties() //////////////////////////////////////////////////////////////////// -Position Tetra::generatePosition(double s, double t, double u) const +double Tetra::generateBarycentric(double& s, double& t, double& u) { // https://vcg.isti.cnr.it/activities/OLD/geometryegraphics/pointintetraedro.html if (s + t > 1.0) @@ -198,9 +190,16 @@ Position Tetra::generatePosition(double s, double t, double u) const u = s + t + u - 1.0; s = 1 - t - tmp; } - double a = 1 - u - t - s; + return 1 - u - t - s; +} + +//////////////////////////////////////////////////////////////////// + +Position Tetra::generatePosition(double s, double t, double u) const +{ + double w = Tetra::generateBarycentric(s, t, u); - return Position(a * *_vertices[0] + u * *_vertices[1] + t * *_vertices[2] + s * *_vertices[3]); + return Position(w * *_vertices[0] + u * *_vertices[1] + t * *_vertices[2] + s * *_vertices[3]); } //////////////////////////////////////////////////////////////////// diff --git a/SKIRT/core/Tetrahedron.hpp b/SKIRT/core/Tetrahedron.hpp index 46c5412e..ded929f8 100644 --- a/SKIRT/core/Tetrahedron.hpp +++ b/SKIRT/core/Tetrahedron.hpp @@ -36,10 +36,8 @@ class Tetra : public Box std::array _neighbors; public: - Tetra(const std::array& vertices, const std::array& indices, const std::array& neighbors, - const std::array& edges); - - Tetra(Vec* va, Vec* vb, Vec* vc, Vec* vd); + Tetra(const std::array& vertices, const std::array& indices, + const std::array& neighbors, const std::array& edges); double getProd(const Plucker& ray, int t1, int t2) const; @@ -55,6 +53,8 @@ class Tetra : public Box const Array& properties(); + static double generateBarycentric(double& s, double& t, double& u); + Position generatePosition(double s, double t, double u) const; static std::array clockwiseVertices(int face); From a12c2fa81609c2ae76337c5002b682eb9ff7f411 Mon Sep 17 00:00:00 2001 From: arlauwer Date: Mon, 22 Jan 2024 20:42:53 +0100 Subject: [PATCH 14/51] functional centroid kd-tree using breadth-first search of neighbors --- SKIRT/core/TetraMeshSnapshot.cpp | 134 ++++++++++++++++++++-------- SKIRT/core/TetraMeshSnapshot.hpp | 7 +- SKIRT/core/TetraMeshSpatialGrid.cpp | 2 +- SKIRT/core/Tetrahedron.cpp | 1 - 4 files changed, 100 insertions(+), 44 deletions(-) diff --git a/SKIRT/core/TetraMeshSnapshot.cpp b/SKIRT/core/TetraMeshSnapshot.cpp index fe0770a2..01ddb0a2 100644 --- a/SKIRT/core/TetraMeshSnapshot.cpp +++ b/SKIRT/core/TetraMeshSnapshot.cpp @@ -23,8 +23,10 @@ #include "TextInFile.hpp" #include "Units.hpp" #include "tetgen.h" +#include #include -#include +#include +#include #include "container.hh" //////////////////////////////////////////////////////////////////// @@ -272,13 +274,31 @@ void TetraMeshSnapshot::setExtent(const Box& extent) //////////////////////////////////////////////////////////////////// -TetraMeshSnapshot::TetraMeshSnapshot(const SimulationItem* item, const Box& extent, const TetraMeshSpatialGrid& grid) +TetraMeshSnapshot::TetraMeshSnapshot(const TetraMeshSpatialGrid* grid, const Box& extent) { - // item will also contain the policies WIP - setContext(item); + setContext(grid); setExtent(extent); buildMesh(grid); buildSearchPerBlock(); + + int N = 1e6; + Position pos; + volatile int index = 0; + + auto start = std::chrono::high_resolution_clock::now(); + + for (int i = 0; i < N; i++) + { + pos = random()->position(_extent); + index += cellIndex(pos); + } + // 2994 + // 3135 + + auto end = std::chrono::high_resolution_clock::now(); + auto dur = std::chrono::duration_cast(end - start); + printf("dur: %ld\n", dur.count()); + printf("average checks: %f\n", index / (double)N); } //////////////////////////////////////////////////////////////////// @@ -312,20 +332,17 @@ namespace //////////////////////////////////////////////////////////////////// -void TetraMeshSnapshot::buildMesh(const TetraMeshSpatialGrid& grid) +void TetraMeshSnapshot::buildMesh(const TetraMeshSpatialGrid* grid) { tetgenio in, out; // tetgenio::facet* f; // tetgenio::polygon* p; in.firstnumber = 0; - in.numberofpoints = 8 + 0; + in.numberofpoints = 8; + // in.numberofpoints += 4; in.pointlist = new REAL[in.numberofpoints * 3]; - in.tetunsuitable = [&grid](double* pa, double* pb, double* pc, double* pd, double vol) { - return grid.tetUnsuitable(pa, pb, pc, pd, vol); - }; - // bottom half (zmin) in.pointlist[0] = _extent.xmin(); in.pointlist[1] = _extent.ymin(); @@ -352,6 +369,13 @@ void TetraMeshSnapshot::buildMesh(const TetraMeshSpatialGrid& grid) in.pointlist[12 + i * 3 + 2] = _extent.zmax(); } + // for (int i = 0; i < 4; i++) + // { + // in.pointlist[24 + i * 3 + 0] = in.pointlist[i * 3 + 0] + _extent.xwidth() * 0.2; + // in.pointlist[24 + i * 3 + 1] = in.pointlist[i * 3 + 1] + _extent.ywidth() * 0.2; + // in.pointlist[24 + i * 3 + 2] = in.pointlist[i * 3 + 2] + _extent.zwidth() * 0.2; + // } + /* psc */ // in.numberofpointattributes = 1; // in.pointattributelist = new REAL[in.numberofpoints]; @@ -373,6 +397,7 @@ void TetraMeshSnapshot::buildMesh(const TetraMeshSpatialGrid& grid) // in.pointattributelist[8] = 50.; in.numberoffacets = 6; + // in.numberoffacets += 1; in.facetlist = new tetgenio::facet[in.numberoffacets]; addFacet(&in.facetlist[0], {0, 1, 2, 3}); // Facet 1. bottom addFacet(&in.facetlist[1], {4, 5, 6, 7}); // Facet 2. top @@ -380,6 +405,7 @@ void TetraMeshSnapshot::buildMesh(const TetraMeshSpatialGrid& grid) addFacet(&in.facetlist[3], {1, 5, 6, 2}); // Facet 4. right addFacet(&in.facetlist[4], {2, 6, 7, 3}); // Facet 5. back addFacet(&in.facetlist[5], {3, 7, 4, 0}); // Facet 6. left + // addFacet(&in.facetlist[6], {8, 9, 10, 11}); // Facet 6. left tetgenbehavior behavior; behavior.plc = 1; // -p PLC @@ -393,9 +419,13 @@ void TetraMeshSnapshot::buildMesh(const TetraMeshSpatialGrid& grid) // parameters // behavior.minratio = 5.0; // -q quality behavior.maxvolume = 0.05 * _extent.volume(); // -a max volume - // behavior.weighted_param = + // behavior.weighted_param = ; // behavior.mindihedral = 5.0; // -q/ minimal angle + in.tetunsuitable = [grid](double* pa, double* pb, double* pc, double* pd, double vol) { + return grid->tetUnsuitable(pa, pb, pc, pd, vol); + }; + tetrahedralize(&behavior, &in, &out); numTetra = out.numberoftetrahedra; @@ -403,7 +433,6 @@ void TetraMeshSnapshot::buildMesh(const TetraMeshSpatialGrid& grid) numVertices = out.numberofpoints; _vertices.resize(numVertices); - _vertexTetra.resize(numVertices); for (int i = 0; i < numVertices; i++) { double x = out.pointlist[3 * i + 0]; @@ -431,10 +460,7 @@ void TetraMeshSnapshot::buildMesh(const TetraMeshSpatialGrid& grid) for (int c = 0; c < 4; c++) { indices[c] = out.tetrahedronlist[4 * i + c]; - vertices[c] = _vertices[indices[c]]; - _vertexTetra[indices[c]].push_back(i); // add this Tetra to all 4 of its Vertices - neighbors[c] = out.neighborlist[4 * i + c]; } for (int e = 0; e < 6; e++) @@ -456,6 +482,11 @@ void TetraMeshSnapshot::buildMesh(const TetraMeshSpatialGrid& grid) _tetrahedra[i] = new Tetra(vertices, indices, neighbors, edges); } + for (int i = 0; i < numTetra; i++) + { + _centroids.push_back(new Vec(_tetrahedra[i]->centroid())); + } + log()->info("number of vertices " + std::to_string(numVertices)); log()->info("number of edges " + std::to_string(numEdges)); log()->info("number of tetrahedra " + std::to_string(numTetra)); @@ -536,7 +567,7 @@ TetraMeshSnapshot::Node* TetraMeshSnapshot::buildTree(vector::iterator firs { auto median = length >> 1; std::nth_element(first, first + median, last, [this, depth](int m1, int m2) { - return m1 != m2 && lessthan(*_vertices[m1], *_vertices[m2], depth % 3); + return m1 != m2 && lessthan(*_centroids[m1], *_centroids[m2], depth % 3); }); return new TetraMeshSnapshot::Node(*(first + median), depth, buildTree(first, first + median, depth + 1), buildTree(first + median + 1, last, depth + 1)); @@ -549,7 +580,7 @@ TetraMeshSnapshot::Node* TetraMeshSnapshot::buildTree(vector::iterator firs void TetraMeshSnapshot::buildSearchPerBlock() { // abort if there are no cells - if (!numVertices) return; + if (!numTetra) return; log()->info("Building data structures to accelerate searching the tetrahedralization"); @@ -559,16 +590,23 @@ void TetraMeshSnapshot::buildSearchPerBlock() _nb2 = _nb * _nb; _nb3 = _nb * _nb * _nb; - // initialize a vector of nb x nb x nb lists, each containing the cells overlapping a certain block in the domain + // initialize a vector of nb * nb * nb lists _blocklists.resize(_nb3); - // add the tetrahedron to the lists for all blocks it may overlap - for (int m = 0; m != numVertices; ++m) + // we add the tetrahedra to all blocks they potentially overlap with + // this will slow down the search tree but if no search tree is present + // we can simply loop over all tetrahedra inside the block + int i1, j1, k1, i2, j2, k2; + for (int c = 0; c != numTetra; ++c) { - int i, j, k; - _extent.cellIndices(i, j, k, *_vertices[m], _nb, _nb, _nb); - - _blocklists[i * _nb2 + j * _nb + k].push_back(m); + // _extent.cellIndices(i1, j1, k1, *_centroids[c], _nb, _nb, _nb); + // _blocklists[i1 * _nb2 + j1 * _nb + k1].push_back(c); + + _extent.cellIndices(i1, j1, k1, _tetrahedra[c]->rmin() - Vec(_eps, _eps, _eps), _nb, _nb, _nb); + _extent.cellIndices(i2, j2, k2, _tetrahedra[c]->rmax() + Vec(_eps, _eps, _eps), _nb, _nb, _nb); + for (int i = i1; i <= i2; i++) + for (int j = j1; j <= j2; j++) + for (int k = k1; k <= k2; k++) _blocklists[i * _nb2 + j * _nb + k].push_back(c); } // compile block list statistics @@ -655,8 +693,9 @@ void TetraMeshSnapshot::writeGridPlotFiles(const SimulationItem* probe) const log()->info("Writing plot files for tetrahedralization with " + std::to_string(numTetra) + " tetrahedra"); log()->infoSetElapsed(numTetra); int numDone = 0; - for (const Tetra* tetra : _tetrahedra) + for (int i = 0; i < numTetra; i++) { + const Tetra* tetra = _tetrahedra[i]; vector coords; coords.reserve(12); vector indices; @@ -680,7 +719,8 @@ void TetraMeshSnapshot::writeGridPlotFiles(const SimulationItem* probe) const if (tetra->zmin() <= 0 && tetra->zmax() >= 0) plotxy.writePolyhedron(coords, indices); if (tetra->ymin() <= 0 && tetra->ymax() >= 0) plotxz.writePolyhedron(coords, indices); if (tetra->xmin() <= 0 && tetra->xmax() >= 0) plotyz.writePolyhedron(coords, indices); - if (numTetra <= 1000) plotxyz.writePolyhedron(coords, indices); + if (i <= 1000) + plotxyz.writePolyhedron(coords, indices); // like VoronoiMeshSnapshot, but why even write at all? // log message if the minimum time has elapsed numDone++; @@ -780,25 +820,43 @@ int TetraMeshSnapshot::cellIndex(Position bfr) const _extent.cellIndices(i, j, k, bfr, _nb, _nb, _nb); int b = i * _nb2 + j * _nb + k; - // look for the closest vertex in this block using the search tree + // look for the closest centroid in this block using the search tree Node* tree = _blocktrees[b]; if (tree) { - int m = tree->nearest(bfr, _vertices)->m(); - - for (int t : _vertexTetra[m]) + // use a breadth-first search over the neighboring tetrahedra + int root = tree->nearest(bfr, _centroids)->m(); + std::queue queue; + std::unordered_set explored; + queue.push(root); + + int t; + while (queue.size() > 0) { - if (_tetrahedra[t]->Tetra::inside(bfr)) return t; - } + t = queue.front(); + queue.pop(); + const Tetra* tetra = _tetrahedra[t]; - log()->error("cellIndex failed to find the tetrahedron"); - } + if (tetra->inside(bfr)) return t; + explored.insert(t); - // if no tree or tetrahedron was not found loop over all tetrahedra - for (int i = 0; i < numTetra; i++) + for (int n : tetra->_neighbors) + { + if (n != -1 && explored.find(n) == explored.end()) // if not already explored + queue.push(n); + } + } + log()->error("cellIndex failed to find the tetrahedron"); // change to warning? + } + else { - if (_tetrahedra[i]->Tetra::inside(bfr)) return i; + // if there is no search tree, simply loop over all tetrahedra in the block + for (int t : _blocklists[b]) + { + if (_tetrahedra[t]->inside(bfr)) return t; + } } + return -1; } @@ -835,7 +893,7 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator public: MySegmentGenerator(const TetraMeshSnapshot* grid) : _grid(grid) {} - #define WRITE + // #define WRITE #ifdef WRITE std::ofstream out; diff --git a/SKIRT/core/TetraMeshSnapshot.hpp b/SKIRT/core/TetraMeshSnapshot.hpp index 7081cddc..132730ac 100644 --- a/SKIRT/core/TetraMeshSnapshot.hpp +++ b/SKIRT/core/TetraMeshSnapshot.hpp @@ -11,7 +11,6 @@ #include "Snapshot.hpp" #include "TetraMeshSpatialGrid.hpp" #include "Tetrahedron.hpp" -#include #include "array" class PathSegmentGenerator; class SiteListInterface; @@ -143,7 +142,7 @@ class TetraMeshSnapshot : public Snapshot //========== Specialty constructors ========== public: - TetraMeshSnapshot(const SimulationItem* item, const Box& extent, const TetraMeshSpatialGrid& grid); + TetraMeshSnapshot(const TetraMeshSpatialGrid* grid, const Box& extent); //=========== Private construction ========== @@ -167,7 +166,7 @@ class TetraMeshSnapshot : public Snapshot constructed with these adjusted site positions, which are distributed more uniformly, thereby avoiding overly elongated cells in the Tetra tessellation. Relaxation can be quite time-consuming because the Tetra tessellation must be constructed twice. */ - void buildMesh(const TetraMeshSpatialGrid& grid); + void buildMesh(const TetraMeshSpatialGrid* grid); /** This private function calculates the volumes for all cells without using the Tetra mesh. It assumes that both mass and mass density columns are being imported. */ @@ -411,7 +410,7 @@ class TetraMeshSnapshot : public Snapshot vector _tetrahedra; vector _edges; vector _vertices; - vector> _vertexTetra; + vector _centroids; // data members initialized when processing snapshot input, but only if a density policy has been set Array _rhov; // density for each cell (not normalized) diff --git a/SKIRT/core/TetraMeshSpatialGrid.cpp b/SKIRT/core/TetraMeshSpatialGrid.cpp index d3c0d57b..3f60d4d6 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.cpp +++ b/SKIRT/core/TetraMeshSpatialGrid.cpp @@ -111,7 +111,7 @@ void TetraMeshSpatialGrid::setupSelfBefore() // warn user if none of the criteria were enabled if (!_hasAny) find()->warning("None of the tree subdivision criteria are enabled"); - _mesh = new TetraMeshSnapshot(this, extent(), *this); + _mesh = new TetraMeshSnapshot(this, extent()); } bool TetraMeshSpatialGrid::tetUnsuitable(double* pa, double* pb, double* pc, double* pd, double vol) const diff --git a/SKIRT/core/Tetrahedron.cpp b/SKIRT/core/Tetrahedron.cpp index 526e9952..373cfe0e 100644 --- a/SKIRT/core/Tetrahedron.cpp +++ b/SKIRT/core/Tetrahedron.cpp @@ -185,7 +185,6 @@ double Tetra::generateBarycentric(double& s, double& t, double& u) } else if (s + t + u > 1.0) { - double tmp = u; u = s + t + u - 1.0; s = 1 - t - tmp; From 1ad4f3598471422012689f345ffac6b741927469 Mon Sep 17 00:00:00 2001 From: arlauwer Date: Thu, 25 Jan 2024 17:33:27 +0100 Subject: [PATCH 15/51] updated traversal algo to Martie (2017) --- SKIRT/core/TetraMeshSnapshot.cpp | 321 +++++++++++-------------------- SKIRT/core/TetraMeshSnapshot.hpp | 1 - SKIRT/core/Tetrahedron.cpp | 86 +-------- SKIRT/core/Tetrahedron.hpp | 47 ++--- 4 files changed, 134 insertions(+), 321 deletions(-) diff --git a/SKIRT/core/TetraMeshSnapshot.cpp b/SKIRT/core/TetraMeshSnapshot.cpp index 01ddb0a2..e4b6a1fe 100644 --- a/SKIRT/core/TetraMeshSnapshot.cpp +++ b/SKIRT/core/TetraMeshSnapshot.cpp @@ -280,25 +280,6 @@ TetraMeshSnapshot::TetraMeshSnapshot(const TetraMeshSpatialGrid* grid, const Box setExtent(extent); buildMesh(grid); buildSearchPerBlock(); - - int N = 1e6; - Position pos; - volatile int index = 0; - - auto start = std::chrono::high_resolution_clock::now(); - - for (int i = 0; i < N; i++) - { - pos = random()->position(_extent); - index += cellIndex(pos); - } - // 2994 - // 3135 - - auto end = std::chrono::high_resolution_clock::now(); - auto dur = std::chrono::duration_cast(end - start); - printf("dur: %ld\n", dur.count()); - printf("average checks: %f\n", index / (double)N); } //////////////////////////////////////////////////////////////////// @@ -413,7 +394,7 @@ void TetraMeshSnapshot::buildMesh(const TetraMeshSpatialGrid* grid) behavior.fixedvolume = 1; // -a max volume behavior.neighout = 2; // -nn neighbors and edges? behavior.zeroindex = 1; // -z zero index - behavior.edgesout = 1; // -e edges + // behavior.edgesout = 1; // -e edges // behavior.weighted = 1; // -w weighted // parameters @@ -442,49 +423,32 @@ void TetraMeshSnapshot::buildMesh(const TetraMeshSpatialGrid* grid) _vertices[i] = new Vec(x, y, z); } - _edges.resize(numEdges); - for (int i = 0; i < numEdges; i++) - { - int v1 = out.edgelist[2 * i]; - int v2 = out.edgelist[2 * i + 1]; - _edges[i] = new Edge(v1, v2, _vertices[v1], _vertices[v2]); - } - _tetrahedra.resize(numTetra); for (int i = 0; i < numTetra; i++) { std::array vertices; - std::array indices; - std::array neighbors; - std::array edges; + std::array neighbors; for (int c = 0; c < 4; c++) { - indices[c] = out.tetrahedronlist[4 * i + c]; - vertices[c] = _vertices[indices[c]]; - neighbors[c] = out.neighborlist[4 * i + c]; - } - for (int e = 0; e < 6; e++) - { - int ei = out.tet2edgelist[6 * i + e]; + vertices[c] = _vertices[out.tetrahedronlist[4 * i + c]]; + int ntetra = out.neighborlist[4 * i + c]; - Edge* edge = _edges[ei]; - auto t1 = std::find(indices.begin(), indices.end(), edge->i1) - indices.begin(); - auto t2 = std::find(indices.begin(), indices.end(), edge->i2) - indices.begin(); - - // tetgen edge order: 23 03 01 12 13 02 - // static constexpr int tetgen_order[12] = {2, 3, 0, 3, 0, 1, 1, 2, 1, 3, 0, 2}; - // int t1 = tetgen_order[2 * e]; // 2 0 0 1 1 0 - // int t2 = tetgen_order[2 * e + 1]; // 3 3 1 2 3 2 - - if (t1 > t2) std::swap(t1, t2); - edges[(t1 == 0) ? t2 - 1 : t1 + t2] = _edges[ei]; + // find which face is shared with neighbor + int nface; + for (int cn = 0; cn < 4; cn++) + { + if (out.neighborlist[4 * ntetra + cn] == i) + { + nface = cn; + break; + } + } + neighbors[c] = Face(ntetra, nface); } - _tetrahedra[i] = new Tetra(vertices, indices, neighbors, edges); - } - for (int i = 0; i < numTetra; i++) - { - _centroids.push_back(new Vec(_tetrahedra[i]->centroid())); + _tetrahedra[i] = new Tetra(vertices, neighbors); + + _centroids.push_back(&_tetrahedra[i]->_centroid); } log()->info("number of vertices " + std::to_string(numVertices)); @@ -746,7 +710,7 @@ int TetraMeshSnapshot::numEntities() const Position TetraMeshSnapshot::position(int m) const { - return Position(_tetrahedra[m]->centroid()); + return Position(_tetrahedra[m]->_centroid); } //////////////////////////////////////////////////////////////////// @@ -840,8 +804,9 @@ int TetraMeshSnapshot::cellIndex(Position bfr) const if (tetra->inside(bfr)) return t; explored.insert(t); - for (int n : tetra->_neighbors) + for (const Face& face : tetra->_faces) { + int n = face._ntetra; if (n != -1 && explored.find(n) == explored.end()) // if not already explored queue.push(n); } @@ -886,14 +851,14 @@ void TetraMeshSnapshot::getEntities(EntityCollection& entities, Position bfr) co class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator { const TetraMeshSnapshot* _grid{nullptr}; - int _mr{-1}; - bool wasInside = false; // true if the path is was ever inside the convex hull - int enteringFace = -1; + int _mr = -1; + int _enteringFace = -1; + int _leavingFace = -1; public: MySegmentGenerator(const TetraMeshSnapshot* grid) : _grid(grid) {} - // #define WRITE +#define WRITE #ifdef WRITE std::ofstream out; @@ -908,12 +873,11 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator out.close(); } - void write(const Vec& exit, double ds, int face) + void write(double ds, int face) { out << "photon=" << _mr << "," << face << "\n"; out << "ds=" << ds << "\n"; out << "r=" << r().x() << "," << r().y() << "," << r().z() << "\n"; - out << "exit=" << exit.x() << "," << exit.y() << "," << exit.z() << "\n"; out << "k=" << k().x() << "," << k().y() << "," << k().z() << std::endl; } #endif @@ -922,195 +886,124 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator if (state() == State::Unknown) { // try moving the photon packet inside the grid; if this is impossible, return an empty path + // this also changes the state() if (!moveInside(_grid->extent(), _grid->_eps)) return false; // get the index of the cell containing the current position _mr = _grid->cellIndex(r()); - if (_mr == -1) - setState(State::Outside); - else - wasInside = true; // setState has already been called - // if the photon packet started outside the grid, return the corresponding nonzero-length segment; // otherwise fall through to determine the first actual segment - if (ds() > 0.) return true; + // if (ds() > 0.) return true; // is this really needed? -#ifdef WRITE - startwriting(); - write(r(), 0, _mr); -#endif - } - - // intentionally falls through - if (state() == State::Inside) - { - // loop in case no exit point was found (which should happen only rarely) - while (true) + // find the entering face, this will do a few full plucker products for the first traversal step + if (state() == State::Inside) { - const Plucker ray = Plucker(r(), k()); - const Tetra* tetra = _grid->_tetrahedra[_mr]; - // temp variable to store plucker products and eventually the barycentric coordinates - std::array prods; + const Tetra* tetra = _grid->_tetrahedra[_mr]; + // Plücker coordinates of the ray + const Vec U = k(); + const Vec V = Vec::cross(U, r()); - int leavingFace = -1; - if (enteringFace == -1) // start ray traversal inside + for (int face = 0; face < 4; face++) { - for (int face = 0; face < 4; face++) + bool entering = true; + std::array cv = tetra->clockwiseVertices(face); + + for (int i = 0; i < 3; i++) { - if (tetra->intersects(prods, ray, face, true)) + // edges: 12, 20, 01 + // verts: 0, 1, 2 + int t1 = cv[(i + 1) % 3]; + int t2 = cv[(i + 2) % 3]; + Vec edge_U = tetra->getEdge(t1, t2); + Vec edge_V = Vec::cross(edge_U, *tetra->_vertices[t1]); + + // naive implementation for Plücker product with all 3 edges per face + double prod = Vec::dot(U, edge_V) + Vec::dot(V, edge_U); + + // all products must be non-negative for it to be an entering face + if (prod < 0) { - leavingFace = face; + entering = false; break; } } - } - else - { - std::array t = Tetra::clockwiseVertices(enteringFace); - - // 2 step decision tree - prods[0] = tetra->getProd(ray, t[0], enteringFace); - bool clockwise0 = prods[0] < 0; - // if clockwise move clockwise (t moves c but i moves cc) - // 1 is c and 2 is cc - int i = clockwise0 ? 1 : 2; - prods[i] = tetra->getProd(ray, t[i], enteringFace); - - // if 2 clockwise: face=t0 - // if 2 counter: face=t0 - // if clockwise then counter: face=t2 - // if counter then clockwise: face=t1 - - if (clockwise0 == (prods[i] < 0)) - leavingFace = t[0]; - else if (clockwise0) - leavingFace = t[2]; - else - leavingFace = t[1]; - - // get prods (this calculates prod[i] again but is much cleaner) - tetra->intersects(prods, ray, leavingFace, true); - } - // if no exit point was found, advance the current point by a small distance, - // recalculate the cell index, and return to the start of the loop - if (leavingFace == -1) - { - propagater(_grid->_eps); - _mr = _grid->cellIndex(r()); - enteringFace = -1; - - // if we're outside the domain, terminate the path without returning a path segment - if (_mr < 0) + if (entering) { - setState(State::Outside); - return false; + _enteringFace = face; + break; } } - // otherwise set the current point to the exit point and return the path segment - else - { - Vec exit = tetra->calcExit(prods, leavingFace); - - double ds = (exit - r()).norm(); - int next_mr = tetra->_neighbors[leavingFace]; - // we could assert if r+exit and r+sq*k are the same - - propagater(ds + _grid->_eps); - setSegment(_mr, ds); + } #ifdef WRITE - write(exit, ds, leavingFace); + startwriting(); + write(0, _mr); #endif - // set enteringFace if there is a neighboring cell - if (next_mr != -1) - { - auto& neighbors = _grid->_tetrahedra[next_mr]->_neighbors; - enteringFace = - std::distance(neighbors.begin(), std::find(neighbors.begin(), neighbors.end(), _mr)); - } - else - { - enteringFace = -1; - setState(State::Outside); - } - // set new cell - _mr = next_mr; - - return true; - } - } } - if (state() == State::Outside) + // intentionally falls through + if (state() == State::Inside) { - // everything below here is only useful if we use the convex hull - /* - // outside the convex hull and inside extent - if (wasInside && _grid->extent().contains(r())) + // loop in case no exit point was found (which should happen only rarely) + while (true) { - double t_x, t_y, t_z; - if (kx() < 0) - t_x = (_grid->extent().xmin() - rx()) / kx(); - else - t_x = (_grid->extent().xmax() - rx()) / kx(); - if (ky() < 0) - t_y = (_grid->extent().ymin() - ry()) / ky(); - else - t_y = (_grid->extent().ymax() - ry()) / ky(); - if (kz() < 0) - t_z = (_grid->extent().zmin() - rz()) / kz(); - else - t_z = (_grid->extent().zmax() - rz()) / kz(); + const Tetra* tetra = _grid->_tetrahedra[_mr]; - double ds = min({t_x, t_y, t_z}); - propagater(ds + _grid->_eps); - setSegment(_mr, ds); -#ifdef WRITE - write(r(), ds, -1); - stopwriting(); -#endif - return false; - } + // the translated Plücker moment in the local coordinate system + const Vec moment = Vec::cross(k(), r() - *tetra->_vertices[_enteringFace]); + std::array cv = Tetra::clockwiseVertices(_enteringFace); - // figure out if the path intersects the convex hull - if (!wasInside) - { - const Plucker ray = Plucker(r(), k()); - std::array barycoords; - for (int i = 0; i < _grid->numTetra; i++) - { - const Tetra* tetra = _grid->_tetrahedra[i]; - for (int face = 0; face < 4; face++) - { - // convex hull face - if (tetra->_neighbors[face] == -1) - { - if (tetra->intersects(barycoords, ray, face, false)) - { - _mr = i; - enteringFace = face; + // 2 step decision tree + int clockwise0 = Vec::dot(moment, tetra->getEdge(cv[0], _enteringFace)) <= 0; // problem here is prod = 0 + // if clockwise move clockwise + // (0+1)%3=1 is c and (0-1)%3=2 is cc + int i = clockwise0 ? 1 : 2; + int clockwisei = Vec::dot(moment, tetra->getEdge(cv[i], _enteringFace)) < 0; + // c0 and ci form binary % 3 + // 0 0 -> 0 + // 0 1 -> 1 + // 1 0 -> 2 + // 1 1 -> 0 + + _leavingFace = cv[((clockwise0 << 1) | clockwisei) % 3]; + + // calculate ds from exit face + const Vec& v0 = *tetra->_vertices[0]; + Vec e01 = tetra->getEdge(0, 1); + Vec e02 = tetra->getEdge(0, 2); + Vec n = Vec::cross(e01, e02); - Vec exit = tetra->calcExit(barycoords, enteringFace); + double ds = Vec::dot(n, v0 - r()) / Vec::dot(n, k()); - double ds = (exit - r()).norm(); - propagater(ds + _grid->_eps); -// setSegment(-1, ds); // not sure if this is needed + // we could assert if r+exit and r+sq*k are the same + + propagater(ds + _grid->_eps); + setSegment(_mr, ds); #ifdef WRITE - write(exit, ds, enteringFace); + write(ds, _leavingFace); #endif - wasInside = true; - setState(State::Inside); - return true; - } - } - } + _mr = tetra->_faces[_leavingFace]._ntetra; + + // set enteringFace only if there is a neighboring cell + if (_mr < 0) + { + // _enteringFace = -1; + setState(State::Outside); } + else + { + _enteringFace = tetra->_faces[_leavingFace]._nface; + } + + return true; } - */ } + + if (state() == State::Outside) + {} #ifdef WRITE stopwriting(); #endif diff --git a/SKIRT/core/TetraMeshSnapshot.hpp b/SKIRT/core/TetraMeshSnapshot.hpp index 132730ac..da23c4d5 100644 --- a/SKIRT/core/TetraMeshSnapshot.hpp +++ b/SKIRT/core/TetraMeshSnapshot.hpp @@ -408,7 +408,6 @@ class TetraMeshSnapshot : public Snapshot // data members initialized when processing snapshot input and further completed by BuildMesh() vector _tetrahedra; - vector _edges; vector _vertices; vector _centroids; diff --git a/SKIRT/core/Tetrahedron.cpp b/SKIRT/core/Tetrahedron.cpp index 373cfe0e..36a3764d 100644 --- a/SKIRT/core/Tetrahedron.cpp +++ b/SKIRT/core/Tetrahedron.cpp @@ -1,30 +1,8 @@ #include "Tetrahedron.hpp" #include "FatalError.hpp" -//////////////////////////////////////////////////////////////////// - -Plucker::Plucker() {} - -//////////////////////////////////////////////////////////////////// - -Plucker::Plucker(const Vec& pos, const Vec& dir) : U(dir), V(Vec::cross(dir, pos)) {} - -//////////////////////////////////////////////////////////////////// - -inline double Plucker::dot(const Plucker& a, const Plucker& b) -{ - return Vec::dot(a.U, b.V) + Vec::dot(b.U, a.V); -} - -//////////////////////////////////////////////////////////////////// - -Edge::Edge(int i1, int i2, const Vec* v1, const Vec* v2) : Plucker(*v1, *v2 - *v1), i1(i1), i2(i2) {} - -//////////////////////////////////////////////////////////////////// - -Tetra::Tetra(const std::array& vertices, const std::array& indices, - const std::array& neighbors, const std::array& edges) - : _vertices(vertices), _indices(indices), _edges(edges), _neighbors(neighbors) +Tetra::Tetra(const std::array& vertices, const std::array& neighbors) + : _vertices(vertices), _faces(neighbors) { double xmin = DBL_MAX; double ymin = DBL_MAX; @@ -46,40 +24,16 @@ Tetra::Tetra(const std::array& vertices, const std::array& indi _volume = 1 / 6. * abs(Vec::dot(Vec::cross(*_vertices[1] - *_vertices[0], *_vertices[2] - *_vertices[0]), *_vertices[3] - *_vertices[0])); -} -//////////////////////////////////////////////////////////////////// - -double Tetra::getProd(const Plucker& ray, int t1, int t2) const -{ - int e = (std::min(t1, t2) == 0) ? std::max(t1, t2) - 1 : t1 + t2; - Edge* edge = _edges[e]; - - // not the same order -> *-1 - // beginning of t1 == beginning of edge (= same order) - return (_indices[t1] == edge->i1 ? 1 : -1) * Plucker::dot(ray, *edge); + for (int i = 0; i < 4; i++) _centroid += *_vertices[i]; + _centroid /= 4; } //////////////////////////////////////////////////////////////////// -bool Tetra::intersects(std::array& barycoords, const Plucker& ray, int face, bool leaving) const +Vec Tetra::getEdge(int t1, int t2) const { - std::array t = clockwiseVertices(face); - - double sum = 0; - for (int i = 0; i < 3; i++) - { - // edges: 12, 20, 01 - // verts: 0, 1, 2 - double prod = getProd(ray, t[(i + 1) % 3], t[(i + 2) % 3]); - if (leaving != (prod <= 0)) return false; // change this so for both leavig and entering prod=0 works - barycoords[i] = prod; - sum += prod; - } - if (sum != 0) - for (int i = 0; i < 3; i++) barycoords[i] /= sum; - - return true; + return *_vertices[t2] - *_vertices[t1]; } //////////////////////////////////////////////////////////////////// @@ -98,8 +52,8 @@ bool Tetra::inside(const Position& bfr) const // optimized version (probably not that much better) Vec e0p = bfr - *_vertices[0]; - Vec e02 = *_vertices[2] - *_vertices[0]; - Vec e01 = *_vertices[1] - *_vertices[0]; + Vec e02 = getEdge(0, 2); + Vec e01 = getEdge(0, 1); if (Vec::dot(Vec::cross(e02, e01), e0p) > 0) // 02 x 01 return false; @@ -111,8 +65,8 @@ bool Tetra::inside(const Position& bfr) const return false; Vec e1p = bfr - *_vertices[1]; - Vec e12 = *_vertices[2] - *_vertices[1]; - Vec e13 = *_vertices[3] - *_vertices[1]; + Vec e12 = getEdge(1, 2); + Vec e13 = getEdge(1, 3); return Vec::dot(Vec::cross(e12, e13), e1p) < 0; // 12 x 13 // checks 3 edges too many but very simple @@ -133,26 +87,6 @@ bool Tetra::inside(const Position& bfr) const //////////////////////////////////////////////////////////////////// -Vec Tetra::calcExit(const std::array& barycoords, int face) const -{ - std::array t = Tetra::clockwiseVertices(face); - Vec exit; - for (int i = 0; i < 3; i++) exit += *_vertices[t[i]] * barycoords[i]; - return exit; -} - -//////////////////////////////////////////////////////////////////// - -Vec Tetra::centroid() const -{ - Vec pos; - for (int i = 0; i < 4; i++) pos += *_vertices[i]; - pos /= 4; - return pos; -} - -//////////////////////////////////////////////////////////////////// - double Tetra::volume() const { return _volume; diff --git a/SKIRT/core/Tetrahedron.hpp b/SKIRT/core/Tetrahedron.hpp index ded929f8..21fa89ee 100644 --- a/SKIRT/core/Tetrahedron.hpp +++ b/SKIRT/core/Tetrahedron.hpp @@ -2,25 +2,13 @@ #include "Box.hpp" #include "Position.hpp" -class Plucker +struct Face { -private: - Vec U, V; - -public: - Plucker(); + Face() {} + Face(int ntetra, int nface) : _ntetra(ntetra), _nface(nface) {} - Plucker(const Vec& pos, const Vec& dir); - - // permuted inner product - static inline double dot(const Plucker& a, const Plucker& b); -}; - -class Edge : public Plucker -{ -public: - const int i1, i2; - Edge(int i1, int i2, const Vec* v1, const Vec* v2); + int _ntetra; + int _nface; }; class Tetra : public Box @@ -30,24 +18,16 @@ class Tetra : public Box Array _properties; public: - std::array _vertices; - std::array _indices; - std::array _edges; - std::array _neighbors; + const std::array _vertices; + const std::array _faces; + Vec _centroid; public: - Tetra(const std::array& vertices, const std::array& indices, - const std::array& neighbors, const std::array& edges); - - double getProd(const Plucker& ray, int t1, int t2) const; - - bool intersects(std::array& prods, const Plucker& ray, int face, bool leaving = true) const; + Tetra(const std::array& vertices, const std::array& neighbors); bool inside(const Position& bfr) const; - Vec calcExit(const std::array& barycoords, int face) const; - - Vec centroid() const; + Vec getEdge(int t1, int t2) const; double volume() const; @@ -57,5 +37,12 @@ class Tetra : public Box Position generatePosition(double s, double t, double u) const; + /** + * @brief This gives the clockwise vertices of a given face looking from inside the tetrahedron. + * The Plücker products are thus all negative for a leaving ray and positive for an entering ray from outside + * + * @param face + * @return std::array + */ static std::array clockwiseVertices(int face); }; From a27e45be8059f85be62a97036e7c4518958ea54c Mon Sep 17 00:00:00 2001 From: arlauwer Date: Fri, 26 Jan 2024 23:32:11 +0100 Subject: [PATCH 16/51] temp fix for edge cases (vertex intersection still not working) --- SKIRT/core/TetraMeshSnapshot.cpp | 64 +++++++++++++++++++++++--------- 1 file changed, 46 insertions(+), 18 deletions(-) diff --git a/SKIRT/core/TetraMeshSnapshot.cpp b/SKIRT/core/TetraMeshSnapshot.cpp index e4b6a1fe..43ac4dbd 100644 --- a/SKIRT/core/TetraMeshSnapshot.cpp +++ b/SKIRT/core/TetraMeshSnapshot.cpp @@ -134,6 +134,11 @@ namespace p->vertexlist[2] = vertices[2]; p->vertexlist[3] = vertices[3]; } + + template int sgn(T a) + { + return (a > T(0)) - (a < T(0)); + } } //////////////////////////////////////////////////////////////////// @@ -807,7 +812,7 @@ int TetraMeshSnapshot::cellIndex(Position bfr) const for (const Face& face : tetra->_faces) { int n = face._ntetra; - if (n != -1 && explored.find(n) == explored.end()) // if not already explored + if (n != -1 && (explored.find(n) == explored.end())) // if not already explored queue.push(n); } } @@ -885,6 +890,12 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator { if (state() == State::Unknown) { + _rx = 0; + _ry = 0; + _rz = 0; + _kx = 1 / sqrt(2); + _ky = 1 / sqrt(2); + _kz = 0; // try moving the photon packet inside the grid; if this is impossible, return an empty path // this also changes the state() if (!moveInside(_grid->extent(), _grid->_eps)) return false; @@ -956,26 +967,43 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator std::array cv = Tetra::clockwiseVertices(_enteringFace); // 2 step decision tree - int clockwise0 = Vec::dot(moment, tetra->getEdge(cv[0], _enteringFace)) <= 0; // problem here is prod = 0 - // if clockwise move clockwise - // (0+1)%3=1 is c and (0-1)%3=2 is cc - int i = clockwise0 ? 1 : 2; - int clockwisei = Vec::dot(moment, tetra->getEdge(cv[i], _enteringFace)) < 0; - // c0 and ci form binary % 3 - // 0 0 -> 0 - // 0 1 -> 1 - // 1 0 -> 2 - // 1 1 -> 0 - - _leavingFace = cv[((clockwise0 << 1) | clockwisei) % 3]; + double plucker0 = Vec::dot(moment, tetra->getEdge(cv[0], _enteringFace)); + int orientation0 = -sgn(plucker0); // clockwise=1, cclockwise=-1, intersects=0 + // if clockwise move clockwise else move cclockwise + int i = orientation0 == 1 ? 1 : 2; + double pluckeri = Vec::dot(moment, tetra->getEdge(cv[i], _enteringFace)); + int orientationi = -sgn(pluckeri); // clockwise=1, cclockwise=-1, intersects=0 + // -1 -1 -> 0 + // 1 1 -> 0 + // -1 1 -> 1 + // 1 -1 -> 2 + // 0 0 -> any (0) + // 0 1 -> 1 + // 0 -1 -> 2 + // -1 0 -> 1 + // 1 0 -> 2 + static constexpr int a[3][3] = { + {0, 1, 1}, // -1-1, -1 0, -1 1 + {2, 0, 1}, // 0-1, 0 0, 0 1 + {2, 2, 0} // 1-1, 1 0, 1 1 + }; + + _leavingFace = cv[a[orientation0 + 1][orientationi + 1]]; // calculate ds from exit face - const Vec& v0 = *tetra->_vertices[0]; - Vec e01 = tetra->getEdge(0, 1); - Vec e02 = tetra->getEdge(0, 2); - Vec n = Vec::cross(e01, e02); - double ds = Vec::dot(n, v0 - r()) / Vec::dot(n, k()); + // we need the 3 vertices that are on the leaving face (order doesn't matter) + cv = tetra->clockwiseVertices(_leavingFace); + Vec& v0 = *tetra->_vertices[cv[0]]; + Vec e01 = tetra->getEdge(cv[0], cv[1]); + Vec e02 = tetra->getEdge(cv[0], cv[2]); + Vec n = Vec::cross(e01, e02); + double ndotk = Vec::dot(n, k()); + double ds; + if (ndotk == 0) // the ray is inside the leaving face + ds = 0; + else + ds = Vec::dot(n, v0 - r()) / ndotk; // we could assert if r+exit and r+sq*k are the same From 5fcc4fac1eb385389e2ce7294047020bf3998aa7 Mon Sep 17 00:00:00 2001 From: arlauwer Date: Wed, 31 Jan 2024 20:04:57 +0100 Subject: [PATCH 17/51] almost functional traversal with edge cases only entry vertex intersection isn't working --- SKIRT/core/TetraMeshSnapshot.cpp | 216 ++++++++++++++++--------------- SKIRT/core/TetraMeshSnapshot.hpp | 1 - SKIRT/core/Tetrahedron.cpp | 34 ++++- SKIRT/core/Tetrahedron.hpp | 5 +- 4 files changed, 142 insertions(+), 114 deletions(-) diff --git a/SKIRT/core/TetraMeshSnapshot.cpp b/SKIRT/core/TetraMeshSnapshot.cpp index 43ac4dbd..aa4d1a6d 100644 --- a/SKIRT/core/TetraMeshSnapshot.cpp +++ b/SKIRT/core/TetraMeshSnapshot.cpp @@ -399,6 +399,7 @@ void TetraMeshSnapshot::buildMesh(const TetraMeshSpatialGrid* grid) behavior.fixedvolume = 1; // -a max volume behavior.neighout = 2; // -nn neighbors and edges? behavior.zeroindex = 1; // -z zero index + behavior.facesout = 1; // -f faces // behavior.edgesout = 1; // -e edges // behavior.weighted = 1; // -w weighted @@ -415,7 +416,6 @@ void TetraMeshSnapshot::buildMesh(const TetraMeshSpatialGrid* grid) tetrahedralize(&behavior, &in, &out); numTetra = out.numberoftetrahedra; - numEdges = out.numberofedges; numVertices = out.numberofpoints; _vertices.resize(numVertices); @@ -432,10 +432,13 @@ void TetraMeshSnapshot::buildMesh(const TetraMeshSpatialGrid* grid) for (int i = 0; i < numTetra; i++) { std::array vertices; - std::array neighbors; + std::array faces; + + // calc vertices first + for (int c = 0; c < 4; c++) vertices[c] = _vertices[out.tetrahedronlist[4 * i + c]]; + for (int c = 0; c < 4; c++) { - vertices[c] = _vertices[out.tetrahedronlist[4 * i + c]]; int ntetra = out.neighborlist[4 * i + c]; // find which face is shared with neighbor @@ -448,16 +451,15 @@ void TetraMeshSnapshot::buildMesh(const TetraMeshSpatialGrid* grid) break; } } - neighbors[c] = Face(ntetra, nface); + faces[c] = Face(ntetra, nface); } - _tetrahedra[i] = new Tetra(vertices, neighbors); + _tetrahedra[i] = new Tetra(vertices, faces); _centroids.push_back(&_tetrahedra[i]->_centroid); } log()->info("number of vertices " + std::to_string(numVertices)); - log()->info("number of edges " + std::to_string(numEdges)); log()->info("number of tetrahedra " + std::to_string(numTetra)); } @@ -856,9 +858,12 @@ void TetraMeshSnapshot::getEntities(EntityCollection& entities, Position bfr) co class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator { const TetraMeshSnapshot* _grid{nullptr}; - int _mr = -1; - int _enteringFace = -1; - int _leavingFace = -1; + int _mr; + int _enteringFace; + int _leavingFace; + + int noleaving = 0; + int epsstep = 0; public: MySegmentGenerator(const TetraMeshSnapshot* grid) : _grid(grid) {} @@ -875,6 +880,7 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator void stopwriting() { + printf("noleaving: %d, %d\n", noleaving, epsstep); out.close(); } @@ -890,64 +896,26 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator { if (state() == State::Unknown) { - _rx = 0; - _ry = 0; - _rz = 0; - _kx = 1 / sqrt(2); - _ky = 1 / sqrt(2); - _kz = 0; + // _rx = 0; + // _ry = 0; + // _rz = 0; + // _kx = 0; + // _ky = 0; + // _kz = 1; + // try moving the photon packet inside the grid; if this is impossible, return an empty path // this also changes the state() if (!moveInside(_grid->extent(), _grid->_eps)) return false; // get the index of the cell containing the current position _mr = _grid->cellIndex(r()); + _enteringFace = -1; + _leavingFace = -1; // if the photon packet started outside the grid, return the corresponding nonzero-length segment; // otherwise fall through to determine the first actual segment - // if (ds() > 0.) return true; // is this really needed? - - // find the entering face, this will do a few full plucker products for the first traversal step - if (state() == State::Inside) - { - - const Tetra* tetra = _grid->_tetrahedra[_mr]; - // Plücker coordinates of the ray - const Vec U = k(); - const Vec V = Vec::cross(U, r()); - - for (int face = 0; face < 4; face++) - { - bool entering = true; - std::array cv = tetra->clockwiseVertices(face); - - for (int i = 0; i < 3; i++) - { - // edges: 12, 20, 01 - // verts: 0, 1, 2 - int t1 = cv[(i + 1) % 3]; - int t2 = cv[(i + 2) % 3]; - Vec edge_U = tetra->getEdge(t1, t2); - Vec edge_V = Vec::cross(edge_U, *tetra->_vertices[t1]); - - // naive implementation for Plücker product with all 3 edges per face - double prod = Vec::dot(U, edge_V) + Vec::dot(V, edge_U); - - // all products must be non-negative for it to be an entering face - if (prod < 0) - { - entering = false; - break; - } - } + if (ds() > 0.) return true; - if (entering) - { - _enteringFace = face; - break; - } - } - } #ifdef WRITE startwriting(); write(0, _mr); @@ -961,77 +929,113 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator while (true) { const Tetra* tetra = _grid->_tetrahedra[_mr]; + Position pos = r(); + Direction dir = k(); + + // find entering face using a single Plücker product + if (_enteringFace == -1) + { + constexpr int etable[6][2] = {{3, 2}, {1, 3}, {2, 1}, {3, 0}, {0, 2}, {1, 0}}; + // try all 6 edges because of rare edge cases where ray is inside edge + // having only 1 non-zero Plücker product + int e = 0; + for (int v1 = 0; v1 < 3; v1++) + { + for (int v2 = v1 + 1; v2 < 4; v2++) + { + + Vec moment12 = Vec::cross(dir, pos - *tetra->_vertices[v1]); + double prod12 = Vec::dot(moment12, tetra->getEdge(v1, v2)); + if (prod12 != 0.) + { + _enteringFace = prod12 < 0 ? etable[e][0] : etable[e][1]; + break; + } + e++; + } + if (_enteringFace != -1) break; + } + } // the translated Plücker moment in the local coordinate system - const Vec moment = Vec::cross(k(), r() - *tetra->_vertices[_enteringFace]); + Vec moment = Vec::cross(dir, pos - *tetra->_vertices[_enteringFace]); std::array cv = Tetra::clockwiseVertices(_enteringFace); // 2 step decision tree - double plucker0 = Vec::dot(moment, tetra->getEdge(cv[0], _enteringFace)); - int orientation0 = -sgn(plucker0); // clockwise=1, cclockwise=-1, intersects=0 + int clock0 = Vec::dot(moment, tetra->getEdge(cv[0], _enteringFace)) < 0; // if clockwise move clockwise else move cclockwise - int i = orientation0 == 1 ? 1 : 2; - double pluckeri = Vec::dot(moment, tetra->getEdge(cv[i], _enteringFace)); - int orientationi = -sgn(pluckeri); // clockwise=1, cclockwise=-1, intersects=0 - // -1 -1 -> 0 - // 1 1 -> 0 - // -1 1 -> 1 - // 1 -1 -> 2 - // 0 0 -> any (0) - // 0 1 -> 1 - // 0 -1 -> 2 - // -1 0 -> 1 - // 1 0 -> 2 - static constexpr int a[3][3] = { - {0, 1, 1}, // -1-1, -1 0, -1 1 - {2, 0, 1}, // 0-1, 0 0, 0 1 - {2, 2, 0} // 1-1, 1 0, 1 1 - }; - - _leavingFace = cv[a[orientation0 + 1][orientationi + 1]]; - - // calculate ds from exit face - - // we need the 3 vertices that are on the leaving face (order doesn't matter) - cv = tetra->clockwiseVertices(_leavingFace); - Vec& v0 = *tetra->_vertices[cv[0]]; - Vec e01 = tetra->getEdge(cv[0], cv[1]); - Vec e02 = tetra->getEdge(cv[0], cv[2]); - Vec n = Vec::cross(e01, e02); - double ndotk = Vec::dot(n, k()); + int i = clock0 ? 1 : 2; + int cclocki = Vec::dot(moment, tetra->getEdge(cv[i], _enteringFace)) >= 0; + + // decision table for clock0 and cclocki + // 1 1 -> 2 + // 0 0 -> 1 + // 1 0 -> 0 + // 0 1 -> 0 + constexpr int dtable[2][2] = {{1, 0}, {0, 2}}; + _leavingFace = cv[dtable[clock0][cclocki]]; + const Vec& n = tetra->_faces[_leavingFace]._normal; + const Vec& v = *tetra->_vertices[_enteringFace]; + double ndotk = Vec::dot(n, dir); double ds; - if (ndotk == 0) // the ray is inside the leaving face + // if ndotk == 0. we are inside the leaving face, we should just move to the neighboring tetra and not move (ds=0) + // once it moved to its neighbor, it can't move back to this cell and it will traverse the next step correctly + if (ndotk == 0.) + { ds = 0; + } else - ds = Vec::dot(n, v0 - r()) / ndotk; + { + ds = Vec::dot(n, v - pos) / ndotk; + // if ds is too close to leaving face or negative or ndotk is negative + // we should not use the traversal algorithm + if (ds < _grid->_eps || ndotk < 0) + { + _leavingFace = -1; + epsstep++; + } + } - // we could assert if r+exit and r+sq*k are the same + // if no exit point was found, advance the current point by a small distance, + // recalculate the cell index, and return to the start of the loop + if (_leavingFace == -1) + { + noleaving++; - propagater(ds + _grid->_eps); - setSegment(_mr, ds); + propagater(_grid->_eps); + _mr = _grid->cellIndex(r()); + // move towards centroid? + _enteringFace = -1; + + if (_mr < 0) setState(State::Outside); #ifdef WRITE - write(ds, _leavingFace); + write(ds, -1); #endif - - _mr = tetra->_faces[_leavingFace]._ntetra; - - // set enteringFace only if there is a neighboring cell - if (_mr < 0) - { - // _enteringFace = -1; - setState(State::Outside); } + // otherwise set the current point to the exit point and return the path segment else { - _enteringFace = tetra->_faces[_leavingFace]._nface; - } + propagater(ds); + setSegment(_mr, ds); + _mr = tetra->_faces[_leavingFace]._ntetra; - return true; + if (_mr < 0) + setState(State::Outside); + else + _enteringFace = tetra->_faces[_leavingFace]._nface; + + _leavingFace = -1; +#ifdef WRITE + write(ds, _leavingFace); +#endif + return true; + } } } if (state() == State::Outside) {} + #ifdef WRITE stopwriting(); #endif diff --git a/SKIRT/core/TetraMeshSnapshot.hpp b/SKIRT/core/TetraMeshSnapshot.hpp index da23c4d5..bb7ac414 100644 --- a/SKIRT/core/TetraMeshSnapshot.hpp +++ b/SKIRT/core/TetraMeshSnapshot.hpp @@ -403,7 +403,6 @@ class TetraMeshSnapshot : public Snapshot Box _extent; // the spatial domain of the mesh double _eps{0.}; // small fraction of extent int numTetra; - int numEdges; int numVertices; // data members initialized when processing snapshot input and further completed by BuildMesh() diff --git a/SKIRT/core/Tetrahedron.cpp b/SKIRT/core/Tetrahedron.cpp index 36a3764d..a632434a 100644 --- a/SKIRT/core/Tetrahedron.cpp +++ b/SKIRT/core/Tetrahedron.cpp @@ -1,8 +1,8 @@ #include "Tetrahedron.hpp" #include "FatalError.hpp" +#include -Tetra::Tetra(const std::array& vertices, const std::array& neighbors) - : _vertices(vertices), _faces(neighbors) +Tetra::Tetra(const std::array& vertices, const std::array& faces) : _vertices(vertices), _faces(faces) { double xmin = DBL_MAX; double ymin = DBL_MAX; @@ -27,6 +27,30 @@ Tetra::Tetra(const std::array& vertices, const std::array& nei for (int i = 0; i < 4; i++) _centroid += *_vertices[i]; _centroid /= 4; + + for (int f = 0; f < 4; f++) + { + std::array cv = clockwiseVertices(f); + Vec e12 = *vertices[cv[1]] - *vertices[cv[0]]; + Vec e13 = *vertices[cv[2]] - *vertices[cv[0]]; + Vec normal = Vec::cross(e12, e13); + normal /= normal.norm(); + + Face& face = _faces[f]; + face._normal = normal; + } + + // this convention makes edges go clockwise around leaving rays from inside the tetrahedron + // so their plucker products are all positive if the ray leaves + const Vec e01 = *_vertices[1] - *_vertices[0]; + const Vec e02 = *_vertices[2] - *_vertices[0]; + const Vec e03 = *_vertices[3] - *_vertices[0]; + double orientation = Vec::dot(Vec::cross(e01, e02), e03); + if (orientation < 0) + { + printf("ORIENTATION SWITCHED!!!!!!!!!"); + // swap last 2, this means first 2 indices can be ordered i < j + } } //////////////////////////////////////////////////////////////////// @@ -139,8 +163,8 @@ Position Tetra::generatePosition(double s, double t, double u) const std::array Tetra::clockwiseVertices(int face) { - std::array t = {(face + 3) % 4, (face + 2) % 4, (face + 1) % 4}; + std::array cv = {(face + 3) % 4, (face + 2) % 4, (face + 1) % 4}; // if face is even we should swap two edges - if (face % 2 == 0) std::swap(t[0], t[2]); - return t; + if (face % 2 == 0) std::swap(cv[0], cv[2]); + return cv; } diff --git a/SKIRT/core/Tetrahedron.hpp b/SKIRT/core/Tetrahedron.hpp index 21fa89ee..580369b4 100644 --- a/SKIRT/core/Tetrahedron.hpp +++ b/SKIRT/core/Tetrahedron.hpp @@ -7,6 +7,7 @@ struct Face Face() {} Face(int ntetra, int nface) : _ntetra(ntetra), _nface(nface) {} + Vec _normal; int _ntetra; int _nface; }; @@ -19,11 +20,11 @@ class Tetra : public Box public: const std::array _vertices; - const std::array _faces; + std::array _faces; Vec _centroid; public: - Tetra(const std::array& vertices, const std::array& neighbors); + Tetra(const std::array& vertices, const std::array& faces); bool inside(const Position& bfr) const; From cf0379e42cc59ab2d32b2f2364c26f078e53fe09 Mon Sep 17 00:00:00 2001 From: arlauwer Date: Mon, 5 Feb 2024 19:25:33 +0100 Subject: [PATCH 18/51] traversal funtioncal, uses plane intersection if unsure small fixes to prepare TetraMeshSpatialGrid --- SKIRT/core/TetraMeshSnapshot.cpp | 344 +++++++++++++++++----------- SKIRT/core/TetraMeshSnapshot.hpp | 16 +- SKIRT/core/TetraMeshSpatialGrid.cpp | 12 +- SKIRT/core/TetraMeshSpatialGrid.hpp | 15 -- SKIRT/core/Tetrahedron.cpp | 21 +- SKIRT/core/Tetrahedron.hpp | 4 + 6 files changed, 228 insertions(+), 184 deletions(-) diff --git a/SKIRT/core/TetraMeshSnapshot.cpp b/SKIRT/core/TetraMeshSnapshot.cpp index aa4d1a6d..e480fdaa 100644 --- a/SKIRT/core/TetraMeshSnapshot.cpp +++ b/SKIRT/core/TetraMeshSnapshot.cpp @@ -271,6 +271,29 @@ TetraMeshSnapshot::~TetraMeshSnapshot() //////////////////////////////////////////////////////////////////// +void TetraMeshSnapshot::readAndClose() +{ + // // read the site info into memory + // Array prop; + // while (infile()->readRow(prop)) + // { + // _vertices.push_back(new Vec(prop[0], prop[1], prop[2])); + // } + + // // close the file + // Snapshot::readAndClose(); + + // // if we are allowed to build a Voronoi mesh + // // calculate the Voronoi cells + // buildMesh(?, false); + + // // if a mass density policy has been set, calculate masses and densities and build the search data structure + // if (hasMassDensityPolicy()) calculateDensityAndMass(); + // if (hasMassDensityPolicy() || needGetEntities()) buildSearchPerBlock(); +} + +//////////////////////////////////////////////////////////////////// + void TetraMeshSnapshot::setExtent(const Box& extent) { _extent = extent; @@ -279,11 +302,48 @@ void TetraMeshSnapshot::setExtent(const Box& extent) //////////////////////////////////////////////////////////////////// +// TetraMeshSnapshot::TetraMeshSnapshot(const SimulationItem* item, const Box& extent, string filename, bool relax) +// { +// // read the input file +// TextInFile in(item, filename, "Tetra vertices"); +// in.addColumn("position x", "length", "pc"); +// in.addColumn("position y", "length", "pc"); +// in.addColumn("position z", "length", "pc"); +// Array coords; +// while (in.readRow(coords)) _cells.push_back(new Cell(Vec(coords[0], coords[1], coords[2]))); +// in.close(); + +// // calculate the Voronoi cells +// setContext(item); +// setExtent(extent); +// buildMesh(relax); +// buildSearchPerBlock(); +// } + +//////////////////////////////////////////////////////////////////// + +// TetraMeshSnapshot::TetraMeshSnapshot(const SimulationItem* item, const Box& extent, SiteListInterface* sli, +// bool relax) +// { +// // prepare the data +// int n = sli->numSites(); +// _cells.resize(n); +// for (int m = 0; m != n; ++m) _cells[m] = new Cell(sli->sitePosition(m)); + +// // calculate the Voronoi cells +// setContext(item); +// setExtent(extent); +// buildMesh(relax); +// buildSearchPerBlock(); +// } + +//////////////////////////////////////////////////////////////////// + TetraMeshSnapshot::TetraMeshSnapshot(const TetraMeshSpatialGrid* grid, const Box& extent) { setContext(grid); setExtent(extent); - buildMesh(grid); + buildMesh(grid, true); buildSearchPerBlock(); } @@ -318,103 +378,89 @@ namespace //////////////////////////////////////////////////////////////////// -void TetraMeshSnapshot::buildMesh(const TetraMeshSpatialGrid* grid) +void TetraMeshSnapshot::buildMesh(const TetraMeshSpatialGrid* grid, bool plc) { tetgenio in, out; - // tetgenio::facet* f; - // tetgenio::polygon* p; - + tetgenbehavior behavior; in.firstnumber = 0; - in.numberofpoints = 8; - // in.numberofpoints += 4; - in.pointlist = new REAL[in.numberofpoints * 3]; - // bottom half (zmin) - in.pointlist[0] = _extent.xmin(); - in.pointlist[1] = _extent.ymin(); - in.pointlist[2] = _extent.zmin(); + if (plc) + { + in.numberofpoints = 8; + in.pointlist = new REAL[in.numberofpoints * 3]; - in.pointlist[3] = _extent.xmax(); - in.pointlist[4] = _extent.xmin(); - in.pointlist[5] = _extent.zmin(); + // bottom half (zmin) + in.pointlist[0] = _extent.xmin(); + in.pointlist[1] = _extent.ymin(); + in.pointlist[2] = _extent.zmin(); - in.pointlist[6] = _extent.xmax(); - in.pointlist[7] = _extent.xmax(); - in.pointlist[8] = _extent.zmin(); + in.pointlist[3] = _extent.xmax(); + in.pointlist[4] = _extent.xmin(); + in.pointlist[5] = _extent.zmin(); - in.pointlist[9] = _extent.xmin(); - in.pointlist[10] = _extent.ymax(); - in.pointlist[11] = _extent.zmin(); + in.pointlist[6] = _extent.xmax(); + in.pointlist[7] = _extent.xmax(); + in.pointlist[8] = _extent.zmin(); - // top half (zmax) - for (int i = 0; i < 4; i++) - { - // x, y the same but z = zmax - in.pointlist[12 + i * 3 + 0] = in.pointlist[i * 3 + 0]; - in.pointlist[12 + i * 3 + 1] = in.pointlist[i * 3 + 1]; - in.pointlist[12 + i * 3 + 2] = _extent.zmax(); - } + in.pointlist[9] = _extent.xmin(); + in.pointlist[10] = _extent.ymax(); + in.pointlist[11] = _extent.zmin(); - // for (int i = 0; i < 4; i++) - // { - // in.pointlist[24 + i * 3 + 0] = in.pointlist[i * 3 + 0] + _extent.xwidth() * 0.2; - // in.pointlist[24 + i * 3 + 1] = in.pointlist[i * 3 + 1] + _extent.ywidth() * 0.2; - // in.pointlist[24 + i * 3 + 2] = in.pointlist[i * 3 + 2] + _extent.zwidth() * 0.2; - // } + // top half (zmax) + for (int i = 0; i < 4; i++) + { + // x, y the same but z = zmax + in.pointlist[12 + i * 3 + 0] = in.pointlist[i * 3 + 0]; + in.pointlist[12 + i * 3 + 1] = in.pointlist[i * 3 + 1]; + in.pointlist[12 + i * 3 + 2] = _extent.zmax(); + } - /* psc */ - // in.numberofpointattributes = 1; - // in.pointattributelist = new REAL[in.numberofpoints]; - // for (int i = 0; i < 8; i++) - // { - // in.pointattributelist[i] = 1.; - // } - // for (int i = 8; i < in.numberofpoints; i++) - // { - // Vec pos = random()->position(_extent); - // in.pointlist[i * 3 + 0] = pos.x(); - // in.pointlist[i * 3 + 1] = pos.y(); - // in.pointlist[i * 3 + 2] = pos.z(); - // } - // for (int i = 8; i < in.numberofpoints; i++) - // { - // in.pointattributelist[i] = 1.; - // } - // in.pointattributelist[8] = 50.; - - in.numberoffacets = 6; - // in.numberoffacets += 1; - in.facetlist = new tetgenio::facet[in.numberoffacets]; - addFacet(&in.facetlist[0], {0, 1, 2, 3}); // Facet 1. bottom - addFacet(&in.facetlist[1], {4, 5, 6, 7}); // Facet 2. top - addFacet(&in.facetlist[2], {0, 4, 5, 1}); // Facet 3. front - addFacet(&in.facetlist[3], {1, 5, 6, 2}); // Facet 4. right - addFacet(&in.facetlist[4], {2, 6, 7, 3}); // Facet 5. back - addFacet(&in.facetlist[5], {3, 7, 4, 0}); // Facet 6. left - // addFacet(&in.facetlist[6], {8, 9, 10, 11}); // Facet 6. left + in.numberoffacets = 6; + // in.numberoffacets += 1; + in.facetlist = new tetgenio::facet[in.numberoffacets]; + addFacet(&in.facetlist[0], {0, 1, 2, 3}); // Facet 1. bottom + addFacet(&in.facetlist[1], {4, 5, 6, 7}); // Facet 2. top + addFacet(&in.facetlist[2], {0, 4, 5, 1}); // Facet 3. front + addFacet(&in.facetlist[3], {1, 5, 6, 2}); // Facet 4. right + addFacet(&in.facetlist[4], {2, 6, 7, 3}); // Facet 5. back + addFacet(&in.facetlist[5], {3, 7, 4, 0}); // Facet 6. left + + behavior.plc = 1; // -p PLC + behavior.quality = 1; // -q quality mesh + behavior.fixedvolume = 1; // -a max volume + behavior.neighout = 2; // -nn neighbors and edges? + behavior.zeroindex = 1; // -z zero index + behavior.facesout = 1; // -f faces + // behavior.edgesout = 1; // -e edges + // behavior.weighted = 1; // -w weighted + // behavior.minratio = 5.0; // -q quality + behavior.maxvolume = 0.05 * _extent.volume(); // -a max volume + // behavior.mindihedral = 5.0; // -q/ minimal angle + + in.tetunsuitable = [grid](double* pa, double* pb, double* pc, double* pd, double vol) { + return grid->tetUnsuitable(pa, pb, pc, pd, vol); + }; + } + // psc + else + { + for (int i = 8; i < in.numberofpoints; i++) + { + Vec pos = random()->position(_extent); + in.pointlist[i * 3 + 0] = pos.x(); + in.pointlist[i * 3 + 1] = pos.y(); + in.pointlist[i * 3 + 2] = pos.z(); + } - tetgenbehavior behavior; - behavior.plc = 1; // -p PLC - behavior.quality = 1; // -q quality mesh - behavior.fixedvolume = 1; // -a max volume - behavior.neighout = 2; // -nn neighbors and edges? - behavior.zeroindex = 1; // -z zero index - behavior.facesout = 1; // -f faces - // behavior.edgesout = 1; // -e edges - // behavior.weighted = 1; // -w weighted - - // parameters - // behavior.minratio = 5.0; // -q quality - behavior.maxvolume = 0.05 * _extent.volume(); // -a max volume - // behavior.weighted_param = ; - // behavior.mindihedral = 5.0; // -q/ minimal angle - - in.tetunsuitable = [grid](double* pa, double* pb, double* pc, double* pd, double vol) { - return grid->tetUnsuitable(pa, pb, pc, pd, vol); - }; + behavior.psc = 1; // -s PSC + behavior.neighout = 2; // -nn neighbors and edges? + behavior.zeroindex = 1; // -z zero index + behavior.facesout = 1; // -f faces + } tetrahedralize(&behavior, &in, &out); + // tranfser TetGen data in TetraMeshSnapshot data containers numTetra = out.numberoftetrahedra; numVertices = out.numberofpoints; @@ -451,6 +497,7 @@ void TetraMeshSnapshot::buildMesh(const TetraMeshSpatialGrid* grid) break; } } + faces[c] = Face(ntetra, nface); } @@ -475,8 +522,8 @@ void TetraMeshSnapshot::calculateVolume() void TetraMeshSnapshot::calculateDensityAndMass() { // allocate vectors for mass and density - _rhov.resize(numVertices); - Array Mv(numVertices); + _rhov.resize(numTetra); + Array Mv(numTetra); // get the maximum temperature, or zero of there is none double maxT = useTemperatureCutoff() ? maxTemperature() : 0.; @@ -488,7 +535,7 @@ void TetraMeshSnapshot::calculateDensityAndMass() // loop over all sites/cells int numIgnored = 0; - for (int m = 0; m != numVertices; ++m) + for (int m = 0; m != numTetra; ++m) { const Array& prop = _tetrahedra[m]->properties(); @@ -525,7 +572,7 @@ void TetraMeshSnapshot::calculateDensityAndMass() _mass = totalEffectiveMass; // construct a vector with the normalized cumulative site densities - if (numVertices) NR::cdf(_cumrhov, Mv); + if (numTetra) NR::cdf(_cumrhov, Mv); } //////////////////////////////////////////////////////////////////// @@ -640,7 +687,7 @@ void TetraMeshSnapshot::buildSearchSingle() void TetraMeshSnapshot::writeGridPlotFiles(const SimulationItem* probe) const { - ///////////////// TEMP +#ifdef WRITE std::ofstream outputFile("data/tetrahedra.txt"); for (size_t i = 0; i < _tetrahedra.size(); i++) { @@ -652,7 +699,7 @@ void TetraMeshSnapshot::writeGridPlotFiles(const SimulationItem* probe) const } } outputFile.close(); - ///////////////// +#endif // create the plot files SpatialGridPlotFile plotxy(probe, probe->itemName() + "_grid_xy"); @@ -860,15 +907,11 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator const TetraMeshSnapshot* _grid{nullptr}; int _mr; int _enteringFace; - int _leavingFace; - - int noleaving = 0; - int epsstep = 0; public: MySegmentGenerator(const TetraMeshSnapshot* grid) : _grid(grid) {} -#define WRITE + // #define WRITE #ifdef WRITE std::ofstream out; @@ -880,7 +923,6 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator void stopwriting() { - printf("noleaving: %d, %d\n", noleaving, epsstep); out.close(); } @@ -896,13 +938,6 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator { if (state() == State::Unknown) { - // _rx = 0; - // _ry = 0; - // _rz = 0; - // _kx = 0; - // _ky = 0; - // _kz = 1; - // try moving the photon packet inside the grid; if this is impossible, return an empty path // this also changes the state() if (!moveInside(_grid->extent(), _grid->_eps)) return false; @@ -910,7 +945,6 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator // get the index of the cell containing the current position _mr = _grid->cellIndex(r()); _enteringFace = -1; - _leavingFace = -1; // if the photon packet started outside the grid, return the corresponding nonzero-length segment; // otherwise fall through to determine the first actual segment @@ -925,6 +959,7 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator // intentionally falls through if (state() == State::Inside) { + int leavingFace = -1; // loop in case no exit point was found (which should happen only rarely) while (true) { @@ -935,6 +970,7 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator // find entering face using a single Plücker product if (_enteringFace == -1) { + // clockwise and cclockwise adjacent faces when checking edge v1->v2 constexpr int etable[6][2] = {{3, 2}, {1, 3}, {2, 1}, {3, 0}, {0, 2}, {1, 0}}; // try all 6 edges because of rare edge cases where ray is inside edge // having only 1 non-zero Plücker product @@ -962,52 +998,76 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator std::array cv = Tetra::clockwiseVertices(_enteringFace); // 2 step decision tree - int clock0 = Vec::dot(moment, tetra->getEdge(cv[0], _enteringFace)) < 0; + double prod0 = Vec::dot(moment, tetra->getEdge(cv[0], _enteringFace)); + int clock0 = prod0 < 0; // if clockwise move clockwise else move cclockwise int i = clock0 ? 1 : 2; - int cclocki = Vec::dot(moment, tetra->getEdge(cv[i], _enteringFace)) >= 0; - - // decision table for clock0 and cclocki - // 1 1 -> 2 - // 0 0 -> 1 - // 1 0 -> 0 - // 0 1 -> 0 - constexpr int dtable[2][2] = {{1, 0}, {0, 2}}; - _leavingFace = cv[dtable[clock0][cclocki]]; - const Vec& n = tetra->_faces[_leavingFace]._normal; - const Vec& v = *tetra->_vertices[_enteringFace]; - double ndotk = Vec::dot(n, dir); - double ds; - // if ndotk == 0. we are inside the leaving face, we should just move to the neighboring tetra and not move (ds=0) - // once it moved to its neighbor, it can't move back to this cell and it will traverse the next step correctly - if (ndotk == 0.) + double prodi = Vec::dot(moment, tetra->getEdge(cv[i], _enteringFace)); + int cclocki = prodi >= 0; + + double ds = DBL_MAX; + + // use plane intersection algorithm if Plücker products are ambiguous + if (prod0 == 0. || prodi == 0.) { - ds = 0; + for (int face : cv) + { + const Vec& n = tetra->_faces[face]._normal; + const Vec& v = *tetra->_vertices[_enteringFace]; + double ndotk = Vec::dot(n, dir); + if (ndotk > 0) + { + double dq = Vec::dot(n, v - pos) / ndotk; + if (dq < ds) + { + ds = dq; + leavingFace = face; + } + } + } } + // use Maria (2017) algorithm otherwise else { + // decision table for clock0 and cclocki + // 1 1 -> 2 + // 0 0 -> 1 + // 1 0 -> 0 + // 0 1 -> 0 + constexpr int dtable[2][2] = {{1, 0}, {0, 2}}; + leavingFace = cv[dtable[clock0][cclocki]]; + const Vec& n = tetra->_faces[leavingFace]._normal; + const Vec& v = *tetra->_vertices[_enteringFace]; + double ndotk = Vec::dot(n, dir); + + // if ndotk == 0. we are inside the leaving face, we should just move to the neighboring tetra and not move (ds=0) + // once it moved to its neighbor, it can't move back to this cell and it will traverse the next step correctly + // if (ndotk == 0.) + // ds = 0; + // else + // ds = Vec::dot(n, v - pos) / ndotk; + ds = Vec::dot(n, v - pos) / ndotk; - // if ds is too close to leaving face or negative or ndotk is negative - // we should not use the traversal algorithm - if (ds < _grid->_eps || ndotk < 0) - { - _leavingFace = -1; - epsstep++; - } } + // if ds is too close to leaving face we recalculate cellIndex to avoid traversing when ds ~ 0 + // this might actually slow down the traversal + // if no exit point was found, advance the current point by a small distance, // recalculate the cell index, and return to the start of the loop - if (_leavingFace == -1) + if (leavingFace == -1 || ds < _grid->_eps) { - noleaving++; - + // move towards centroid? propagater(_grid->_eps); _mr = _grid->cellIndex(r()); - // move towards centroid? - _enteringFace = -1; - if (_mr < 0) setState(State::Outside); + if (_mr < 0) + { + setState(State::Outside); + return false; + } + + _enteringFace = -1; #ifdef WRITE write(ds, -1); #endif @@ -1017,17 +1077,20 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator { propagater(ds); setSegment(_mr, ds); - _mr = tetra->_faces[_leavingFace]._ntetra; + _mr = tetra->_faces[leavingFace]._ntetra; if (_mr < 0) + { setState(State::Outside); - else - _enteringFace = tetra->_faces[_leavingFace]._nface; + return false; + } + + _enteringFace = tetra->_faces[leavingFace]._nface; - _leavingFace = -1; #ifdef WRITE - write(ds, _leavingFace); + write(ds, leavingFace); #endif + return true; } } @@ -1039,6 +1102,7 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator #ifdef WRITE stopwriting(); #endif + return false; } }; diff --git a/SKIRT/core/TetraMeshSnapshot.hpp b/SKIRT/core/TetraMeshSnapshot.hpp index bb7ac414..a58c4951 100644 --- a/SKIRT/core/TetraMeshSnapshot.hpp +++ b/SKIRT/core/TetraMeshSnapshot.hpp @@ -121,7 +121,7 @@ class TetraMeshSnapshot : public Snapshot During its operation, the function logs some statistical information about the imported snapshot and the resulting data structures. */ - // void readAndClose() override; + void readAndClose() override; //========== Configuration ========== @@ -131,17 +131,13 @@ class TetraMeshSnapshot : public Snapshot default; failing to set the extent of the domain results in undefined behavior. */ void setExtent(const Box& extent); - /** This function configures the snapshot to skip construction of the actual Tetra - tessellation and instead use a search tree across all sites. It should be called only if - (1) the snapshot has been configured to import both a mass/number density column \em and a - volume-integrated mass/number column, and (2) the snapshot will not be required to generate - random positions or trace paths. Violating these conditions will result in undefined - behavior. */ - // void foregoTetraMesh(); - //========== Specialty constructors ========== public: + // TetraMeshSnapshot(const SimulationItem* item, const Box& extent, string filename); + + // TetraMeshSnapshot(const SimulationItem* item, const Box& extent, SiteListInterface* sli); + TetraMeshSnapshot(const TetraMeshSpatialGrid* grid, const Box& extent); //=========== Private construction ========== @@ -166,7 +162,7 @@ class TetraMeshSnapshot : public Snapshot constructed with these adjusted site positions, which are distributed more uniformly, thereby avoiding overly elongated cells in the Tetra tessellation. Relaxation can be quite time-consuming because the Tetra tessellation must be constructed twice. */ - void buildMesh(const TetraMeshSpatialGrid* grid); + void buildMesh(const TetraMeshSpatialGrid* grid, bool plc); /** This private function calculates the volumes for all cells without using the Tetra mesh. It assumes that both mass and mass density columns are being imported. */ diff --git a/SKIRT/core/TetraMeshSpatialGrid.cpp b/SKIRT/core/TetraMeshSpatialGrid.cpp index 3f60d4d6..42397711 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.cpp +++ b/SKIRT/core/TetraMeshSpatialGrid.cpp @@ -17,10 +17,7 @@ ////////////////////////////////////////////////////////////////////// -TetraMeshSpatialGrid::~TetraMeshSpatialGrid() -{ - if (_policy != Policy::ImportedMesh) delete _mesh; -} +TetraMeshSpatialGrid::~TetraMeshSpatialGrid() {} ////////////////////////////////////////////////////////////////////// @@ -267,9 +264,10 @@ bool TetraMeshSpatialGrid::offersInterface(const std::type_info& interfaceTypeIn { if (interfaceTypeInfo == typeid(DensityInCellInterface)) { - if (_policy != Policy::ImportedMesh) return false; - auto ms = find(false); - return ms && ms->media().size() == 1; + return false; + // if (_policy != Policy::ImportedMesh) return false; + // auto ms = find(false); + // return ms && ms->media().size() == 1; } return SpatialGrid::offersInterface(interfaceTypeInfo); } diff --git a/SKIRT/core/TetraMeshSpatialGrid.hpp b/SKIRT/core/TetraMeshSpatialGrid.hpp index 348d0ca8..18547914 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.hpp +++ b/SKIRT/core/TetraMeshSpatialGrid.hpp @@ -26,24 +26,9 @@ class TetraMeshSnapshot; overly elongated cells. */ class TetraMeshSpatialGrid : public BoxSpatialGrid, public DensityInCellInterface { - /** The enumeration type indicating the policy for determining the positions of the sites. */ - ENUM_DEF(Policy, Uniform, CentralPeak, DustDensity, ElectronDensity, GasDensity, File, ImportedSites, ImportedMesh) - ENUM_VAL(Policy, Uniform, "random from uniform distribution") - ENUM_VAL(Policy, CentralPeak, "random from distribution with a steep central peak") - ENUM_VAL(Policy, DustDensity, "random from dust density distribution") - ENUM_VAL(Policy, ElectronDensity, "random from electron density distribution") - ENUM_VAL(Policy, GasDensity, "random from gas density distribution") - ENUM_VAL(Policy, File, "loaded from text column data file") - ENUM_VAL(Policy, ImportedSites, "positions of particles, sites or cells in imported distribution") - ENUM_VAL(Policy, ImportedMesh, "employ imported Tetra mesh in medium system") - ENUM_END() - ITEM_CONCRETE(TetraMeshSpatialGrid, BoxSpatialGrid, "a Tetra tessellation-based spatial grid") ATTRIBUTE_TYPE_DISPLAYED_IF(TetraMeshSpatialGrid, "Level2") - PROPERTY_ENUM(policy, Policy, "the policy for determining the positions of the sites") - ATTRIBUTE_DEFAULT_VALUE(policy, "DustDensity") - PROPERTY_DOUBLE(maxDustFraction, "the maximum fraction of dust contained in each cell") ATTRIBUTE_MIN_VALUE(maxDustFraction, "[0") ATTRIBUTE_MAX_VALUE(maxDustFraction, "1e-2]") diff --git a/SKIRT/core/Tetrahedron.cpp b/SKIRT/core/Tetrahedron.cpp index a632434a..140048dd 100644 --- a/SKIRT/core/Tetrahedron.cpp +++ b/SKIRT/core/Tetrahedron.cpp @@ -2,6 +2,12 @@ #include "FatalError.hpp" #include +Tetra::Tetra(const std::array& vertices, const std::array& faces, const Array& prop) + : Tetra(vertices, faces) +{ + _properties = prop; +} + Tetra::Tetra(const std::array& vertices, const std::array& faces) : _vertices(vertices), _faces(faces) { double xmin = DBL_MAX; @@ -21,13 +27,16 @@ Tetra::Tetra(const std::array& vertices, const std::array& fac } setExtent(Box(xmin, ymin, zmin, xmax, ymax, zmax)); + // volume _volume = 1 / 6. * abs(Vec::dot(Vec::cross(*_vertices[1] - *_vertices[0], *_vertices[2] - *_vertices[0]), *_vertices[3] - *_vertices[0])); + // barycenter for (int i = 0; i < 4; i++) _centroid += *_vertices[i]; _centroid /= 4; + // calculate normal facing out for (int f = 0; f < 4; f++) { std::array cv = clockwiseVertices(f); @@ -39,18 +48,6 @@ Tetra::Tetra(const std::array& vertices, const std::array& fac Face& face = _faces[f]; face._normal = normal; } - - // this convention makes edges go clockwise around leaving rays from inside the tetrahedron - // so their plucker products are all positive if the ray leaves - const Vec e01 = *_vertices[1] - *_vertices[0]; - const Vec e02 = *_vertices[2] - *_vertices[0]; - const Vec e03 = *_vertices[3] - *_vertices[0]; - double orientation = Vec::dot(Vec::cross(e01, e02), e03); - if (orientation < 0) - { - printf("ORIENTATION SWITCHED!!!!!!!!!"); - // swap last 2, this means first 2 indices can be ordered i < j - } } //////////////////////////////////////////////////////////////////// diff --git a/SKIRT/core/Tetrahedron.hpp b/SKIRT/core/Tetrahedron.hpp index 580369b4..81552a06 100644 --- a/SKIRT/core/Tetrahedron.hpp +++ b/SKIRT/core/Tetrahedron.hpp @@ -5,6 +5,8 @@ struct Face { Face() {} + + // normals are calculated in the constructor of Tetra Face(int ntetra, int nface) : _ntetra(ntetra), _nface(nface) {} Vec _normal; @@ -24,6 +26,8 @@ class Tetra : public Box Vec _centroid; public: + Tetra(const std::array& vertices, const std::array& faces, const Array& prop); + Tetra(const std::array& vertices, const std::array& faces); bool inside(const Position& bfr) const; From 62c93e9b10314a90b06406380d7e1e59e524cf1a Mon Sep 17 00:00:00 2001 From: arlauwer Date: Thu, 8 Feb 2024 19:08:51 +0100 Subject: [PATCH 19/51] sped up Tetra::inside --- SKIRT/core/Tetrahedron.cpp | 57 ++++++++++---------------------------- 1 file changed, 14 insertions(+), 43 deletions(-) diff --git a/SKIRT/core/Tetrahedron.cpp b/SKIRT/core/Tetrahedron.cpp index 140048dd..9e90892c 100644 --- a/SKIRT/core/Tetrahedron.cpp +++ b/SKIRT/core/Tetrahedron.cpp @@ -61,49 +61,20 @@ Vec Tetra::getEdge(int t1, int t2) const bool Tetra::inside(const Position& bfr) const { - if (!Box::contains(bfr)) return false; - - /* - face: normals for which the other vertex has a positive dot product with - 3:*02 x 01*| 10 x 12 | 21 x 20 - 2: 13 x 10 |*01 x 03*| 30 x 31 - 1: 20 x 23 | 32 x 30 |*03 x 02* - 0: 31 x 32 | 23 x 21 |*12 x 13* // last one doesn't matter - */ - - // optimized version (probably not that much better) - Vec e0p = bfr - *_vertices[0]; - Vec e02 = getEdge(0, 2); - Vec e01 = getEdge(0, 1); - if (Vec::dot(Vec::cross(e02, e01), e0p) > 0) // 02 x 01 - return false; - - Vec e03 = *_vertices[3] - *_vertices[0]; - if (Vec::dot(Vec::cross(e01, e03), e0p) > 0) // 01 x 03 - return false; - - if (Vec::dot(Vec::cross(e03, e02), e0p) > 0) // 03 x 02 - return false; - - Vec e1p = bfr - *_vertices[1]; - Vec e12 = getEdge(1, 2); - Vec e13 = getEdge(1, 3); - return Vec::dot(Vec::cross(e12, e13), e1p) < 0; // 12 x 13 - - // checks 3 edges too many but very simple - // for (int face = 0; face < 4; face++) - // { - // std::array t = clockwiseVertices(face); - // Vec& v0 = *_vertices[t[0]]; - // Vec& clock = *_vertices[t[1]]; - // Vec& counter = *_vertices[t[2]]; - // Vec normal = Vec::cross(counter - v0, clock - v0); - // if (Vec::dot(normal, bfr - v0) < 0) // is pos on the same side as v3 - // { - // return false; - // } - // } - // return true; + // since we use the k-d tree this will only slow the CellIndex + // if (!Box::contains(bfr)) return false; + + // could optimize this slightly by using same vertex for 3 faces and do final face seperately + + for (int f = 0; f < 4; f++) + { + const Face& face = _faces[f]; + const Vec* vertex = _vertices[(f + 1) % 4]; // any vertex that is on the face + + // if point->face is opposite direction as the outward pointing normal, the point is outside + if (Vec::dot(*vertex - bfr, face._normal) < 0) return false; + } + return true; } //////////////////////////////////////////////////////////////////// From d268cfda58f28e33144849b28fa0e01084fd5edf Mon Sep 17 00:00:00 2001 From: arlauwer Date: Sun, 11 Feb 2024 20:51:27 +0100 Subject: [PATCH 20/51] used traversal in CellIndex and refactored some code --- SKIRT/core/TetraMeshSnapshot.cpp | 290 +++++++++++++++++++++++----- SKIRT/core/TetraMeshSpatialGrid.cpp | 9 +- SKIRT/core/TetraMeshSpatialGrid.hpp | 8 +- 3 files changed, 259 insertions(+), 48 deletions(-) diff --git a/SKIRT/core/TetraMeshSnapshot.cpp b/SKIRT/core/TetraMeshSnapshot.cpp index e480fdaa..9fca533c 100644 --- a/SKIRT/core/TetraMeshSnapshot.cpp +++ b/SKIRT/core/TetraMeshSnapshot.cpp @@ -139,6 +139,33 @@ namespace { return (a > T(0)) - (a < T(0)); } + + int findEnteringFace(const Tetra* tetra, const Vec& pos, const Direction& dir) + { + int enteringFace = -1; + // clockwise and cclockwise adjacent faces when checking edge v1->v2 + constexpr int etable[6][2] = {{3, 2}, {1, 3}, {2, 1}, {3, 0}, {0, 2}, {1, 0}}; + // try all 6 edges because of rare edge cases where ray is inside edge + // having only 1 non-zero Plücker product + int e = 0; + for (int v1 = 0; v1 < 3; v1++) + { + for (int v2 = v1 + 1; v2 < 4; v2++) + { + + Vec moment12 = Vec::cross(dir, pos - *tetra->_vertices[v1]); + double prod12 = Vec::dot(moment12, tetra->getEdge(v1, v2)); + if (prod12 != 0.) + { + enteringFace = prod12 < 0 ? etable[e][0] : etable[e][1]; + break; + } + e++; + } + if (enteringFace != -1) break; + } + return enteringFace; + } } //////////////////////////////////////////////////////////////////// @@ -444,6 +471,8 @@ void TetraMeshSnapshot::buildMesh(const TetraMeshSpatialGrid* grid, bool plc) // psc else { + in.numberofpoints = 50; + in.pointlist = new REAL[in.numberofpoints * 3]; for (int i = 8; i < in.numberofpoints; i++) { Vec pos = random()->position(_extent); @@ -485,16 +514,20 @@ void TetraMeshSnapshot::buildMesh(const TetraMeshSpatialGrid* grid, bool plc) for (int c = 0; c < 4; c++) { + // -1 if no neighbor int ntetra = out.neighborlist[4 * i + c]; // find which face is shared with neighbor - int nface; - for (int cn = 0; cn < 4; cn++) + int nface = -1; + if (ntetra != -1) { - if (out.neighborlist[4 * ntetra + cn] == i) + for (int cn = 0; cn < 4; cn++) { - nface = cn; - break; + if (out.neighborlist[4 * ntetra + cn] == i) + { + nface = cn; + break; + } } } @@ -617,9 +650,6 @@ void TetraMeshSnapshot::buildSearchPerBlock() int i1, j1, k1, i2, j2, k2; for (int c = 0; c != numTetra; ++c) { - // _extent.cellIndices(i1, j1, k1, *_centroids[c], _nb, _nb, _nb); - // _blocklists[i1 * _nb2 + j1 * _nb + k1].push_back(c); - _extent.cellIndices(i1, j1, k1, _tetrahedra[c]->rmin() - Vec(_eps, _eps, _eps), _nb, _nb, _nb); _extent.cellIndices(i2, j2, k2, _tetrahedra[c]->rmax() + Vec(_eps, _eps, _eps), _nb, _nb, _nb); for (int i = i1; i <= i2; i++) @@ -670,16 +700,16 @@ void TetraMeshSnapshot::buildSearchPerBlock() void TetraMeshSnapshot::buildSearchSingle() { // log the number of sites - log()->info(" Number of sites: " + std::to_string(numVertices)); + log()->info(" Number of tetrahedra: " + std::to_string(numTetra)); // abort if there are no cells - if (!numVertices) return; + if (!numTetra) return; // construct a single search tree on the site locations of all cells - log()->info("Building data structure to accelerate searching " + std::to_string(numVertices) + " Voronoi sites"); + log()->info("Building data structure to accelerate searching " + std::to_string(numTetra) + " tetrahedra"); _blocktrees.resize(1); - vector ids(numVertices); - for (int m = 0; m != numVertices; ++m) ids[m] = m; + vector ids(numTetra); + for (int m = 0; m != numTetra; ++m) ids[m] = m; _blocktrees[0] = buildTree(ids.begin(), ids.end(), 0); } @@ -687,7 +717,6 @@ void TetraMeshSnapshot::buildSearchSingle() void TetraMeshSnapshot::writeGridPlotFiles(const SimulationItem* probe) const { -#ifdef WRITE std::ofstream outputFile("data/tetrahedra.txt"); for (size_t i = 0; i < _tetrahedra.size(); i++) { @@ -699,7 +728,25 @@ void TetraMeshSnapshot::writeGridPlotFiles(const SimulationItem* probe) const } } outputFile.close(); -#endif + + // outputFile.open("data/faces.txt"); + // for (size_t i = 0; i < _tetrahedra.size(); i++) + // { + // const Tetra* tetra = _tetrahedra[i]; + // bool out = false; + // for (int f = 0; f < 4; f++) + // { + // if (tetra->_faces[f]._ntetra < 0) + // { + // for (int v : tetra->clockwiseVertices(f)) + // { + // const Vec* r = tetra->_vertices[v]; + // outputFile << r->x() << ", " << r->y() << ", " << r->z() << "\n"; + // } + // } + // } + // } + // outputFile.close(); // create the plot files SpatialGridPlotFile plotxy(probe, probe->itemName() + "_grid_xy"); @@ -838,16 +885,178 @@ int TetraMeshSnapshot::cellIndex(Position bfr) const _extent.cellIndices(i, j, k, bfr, _nb, _nb, _nb); int b = i * _nb2 + j * _nb + k; + // int test = 0; + // look for the closest centroid in this block using the search tree Node* tree = _blocktrees[b]; if (tree) { + // /* + // full traversal algorithm + int enteringFace = -1; + int leavingFace = -1; + int m = tree->m(); + + const Tetra* tetra = _tetrahedra[m]; + + Vec pos = tetra->_centroid; + Direction dir(bfr - tetra->_centroid); + double dist = dir.norm(); // keep subtracting ds until dist < 0 + dir /= dist; + + while (true) + { + tetra = _tetrahedra[m]; + + // find entering face using a single Plücker product + if (enteringFace == -1) + { + enteringFace = findEnteringFace(tetra, pos, dir); + if (enteringFace == -1) break; + } + + // the translated Plücker moment in the local coordinate system + Vec moment = Vec::cross(dir, pos - *tetra->_vertices[enteringFace]); + std::array cv = Tetra::clockwiseVertices(enteringFace); + + // 2 step decision tree + double prod0 = Vec::dot(moment, tetra->getEdge(cv[0], enteringFace)); + int clock0 = prod0 < 0; + // if clockwise move clockwise else move cclockwise + int i = clock0 ? 1 : 2; + double prodi = Vec::dot(moment, tetra->getEdge(cv[i], enteringFace)); + int cclocki = prodi >= 0; + + double ds = DBL_MAX; + + // use plane intersection algorithm if Plücker products are ambiguous + if (prod0 == 0. || prodi == 0.) + { + for (int face : cv) + { + const Vec& n = tetra->_faces[face]._normal; + double ndotk = Vec::dot(n, dir); + if (ndotk > 0) + { + const Vec& v = *tetra->_vertices[enteringFace]; + double dq = Vec::dot(n, v - pos) / ndotk; + if (dq < ds) + { + ds = dq; + leavingFace = face; + } + } + } + } + // use Maria (2017) algorithm otherwise + else + { + // decision table for clock0 and cclocki + // 1 1 -> 2 + // 0 0 -> 1 + // 1 0 -> 0 + // 0 1 -> 0 + constexpr int dtable[2][2] = {{1, 0}, {0, 2}}; + leavingFace = cv[dtable[clock0][cclocki]]; + const Vec& n = tetra->_faces[leavingFace]._normal; + const Vec& v = *tetra->_vertices[enteringFace]; + double ndotk = Vec::dot(n, dir); + + ds = Vec::dot(n, v - pos) / ndotk; + } + + // if ds is too close to leaving face we recalculate cellIndex to avoid traversing when ds ~ 0 + // this might actually slow down the traversal + + // if no exit point was found, advance the current point by a small distance, + // recalculate the cell index, and return to the start of the loop + if (leavingFace == -1) + { + break; // traversal failed + } + // otherwise set the current point to the exit point and return the path segment + else + { + pos += ds * dir; + dist -= ds; + if (dist <= 0) return m; + + m = tetra->_faces[leavingFace]._ntetra; + enteringFace = tetra->_faces[leavingFace]._nface; + + if (m < 0) break; + } + } + // */ + + /* + // traverse towards input point from nearest centroid + int m = tree->nearest(bfr, _centroids)->m(); + const Tetra* root = _tetrahedra[m]; + + test++; + if (root->inside(bfr)) return test; + + Vec pos = root->_centroid; + Vec dir = bfr - pos; + double dist = dir.norm(); + dir /= dist; + + // start by checking all faces + std::vector faces = {0, 1, 2, 3}; + + while (true) + { + const Tetra* tetra = _tetrahedra[m]; + + double ds = DBL_MAX; + int leavingFace = -1; + + // find exit face + for (int face : faces) + { + const Vec& n = tetra->_faces[face]._normal; + double ndotk = Vec::dot(n, dir); + if (ndotk > 0) + { + const Vec& v = *tetra->_vertices[(face + 1) % 4]; + double dq = Vec::dot(n, v - pos) / ndotk; + if (dq < ds) + { + ds = dq; + leavingFace = face; + } + } + } + + if (leavingFace == -1) break; + + // propagate and check inside if we passed the input point + pos += ds * dir; + dist -= ds; + if (dist <= 0) + { + test++; + if (tetra->inside(bfr)) return test; + } + + m = tetra->_faces[leavingFace]._ntetra; + int enteringFace = tetra->_faces[leavingFace]._nface; + faces = {(enteringFace + 1) % 4, (enteringFace + 2) % 4, (enteringFace + 3) % 4}; + + if (m < 0) break; + } + */ + + /* // use a breadth-first search over the neighboring tetrahedra int root = tree->nearest(bfr, _centroids)->m(); std::queue queue; std::unordered_set explored; queue.push(root); + // Vec dir = bfr - _tetrahedra[root]->_centroid; + int t; while (queue.size() > 0) { @@ -855,27 +1064,36 @@ int TetraMeshSnapshot::cellIndex(Position bfr) const queue.pop(); const Tetra* tetra = _tetrahedra[t]; - if (tetra->inside(bfr)) return t; + test++; + if (tetra->inside(bfr)) return test; explored.insert(t); + Vec dir = bfr - tetra->_centroid; for (const Face& face : tetra->_faces) { int n = face._ntetra; if (n != -1 && (explored.find(n) == explored.end())) // if not already explored - queue.push(n); + { + if (Vec::dot(dir, face._normal) > 0) + { + queue.push(n); + explored.insert(n); + } + } } } - log()->error("cellIndex failed to find the tetrahedron"); // change to warning? + */ + + log()->error("search tree failed to find the tetrahedron"); // change to warning? } - else + + // if there is no search tree or search tree failed, simply loop over all tetrahedra in the block + for (int t : _blocklists[b]) { - // if there is no search tree, simply loop over all tetrahedra in the block - for (int t : _blocklists[b]) - { - if (_tetrahedra[t]->inside(bfr)) return t; - } + if (_tetrahedra[t]->inside(bfr)) return t; } + log()->error("cellIndex failed to find the tetrahedron"); // change to warning? return -1; } @@ -970,27 +1188,7 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator // find entering face using a single Plücker product if (_enteringFace == -1) { - // clockwise and cclockwise adjacent faces when checking edge v1->v2 - constexpr int etable[6][2] = {{3, 2}, {1, 3}, {2, 1}, {3, 0}, {0, 2}, {1, 0}}; - // try all 6 edges because of rare edge cases where ray is inside edge - // having only 1 non-zero Plücker product - int e = 0; - for (int v1 = 0; v1 < 3; v1++) - { - for (int v2 = v1 + 1; v2 < 4; v2++) - { - - Vec moment12 = Vec::cross(dir, pos - *tetra->_vertices[v1]); - double prod12 = Vec::dot(moment12, tetra->getEdge(v1, v2)); - if (prod12 != 0.) - { - _enteringFace = prod12 < 0 ? etable[e][0] : etable[e][1]; - break; - } - e++; - } - if (_enteringFace != -1) break; - } + _enteringFace = findEnteringFace(tetra, pos, dir); } // the translated Plücker moment in the local coordinate system @@ -1013,10 +1211,10 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator for (int face : cv) { const Vec& n = tetra->_faces[face]._normal; - const Vec& v = *tetra->_vertices[_enteringFace]; double ndotk = Vec::dot(n, dir); if (ndotk > 0) { + const Vec& v = *tetra->_vertices[_enteringFace]; double dq = Vec::dot(n, v - pos) / ndotk; if (dq < ds) { diff --git a/SKIRT/core/TetraMeshSpatialGrid.cpp b/SKIRT/core/TetraMeshSpatialGrid.cpp index 42397711..3fbff61a 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.cpp +++ b/SKIRT/core/TetraMeshSpatialGrid.cpp @@ -119,7 +119,8 @@ bool TetraMeshSpatialGrid::tetUnsuitable(double* pa, double* pb, double* pc, dou Vec d(pd[0], pd[1], pd[2]); // results for the sampled mass or number densities, if applicable - double rho = 0.; // dust mass density + double rho = 0.; // dust mass density + // double rhovar = 0.; // dust mass variance double rhomin = DBL_MAX; // smallest sample for dust mass density double rhomax = 0.; // largest sample for dust mass density double ne = 0; // electron number density @@ -129,6 +130,7 @@ bool TetraMeshSpatialGrid::tetUnsuitable(double* pa, double* pb, double* pc, dou if (_hasAny) { double rhosum = 0; + // double rhosum2 = 0; double nesum = 0; double ngsum = 0; for (int i = 0; i != _numSamples; ++i) @@ -143,6 +145,7 @@ bool TetraMeshSpatialGrid::tetUnsuitable(double* pa, double* pb, double* pc, dou double rhoi = 0.; for (auto medium : _dustMedia) rhoi += medium->massDensity(bfr); rhosum += rhoi; + // rhosum2 += rhoi * rhoi; if (rhoi < rhomin) rhomin = rhoi; if (rhoi > rhomax) rhomax = rhoi; } @@ -152,6 +155,7 @@ bool TetraMeshSpatialGrid::tetUnsuitable(double* pa, double* pb, double* pc, dou for (auto medium : _gasMedia) ngsum += medium->numberDensity(bfr); } rho = rhosum / _numSamples; + // rhovar = rhomax > 0 ? (rhosum2 / _numSamples - rho * rho) * (vol / _dustMass) * (vol / _dustMass) / (maxDustFraction() * maxDustFraction()) : 0.; ne = nesum / _numSamples; ng = ngsum / _numSamples; } @@ -161,6 +165,7 @@ bool TetraMeshSpatialGrid::tetUnsuitable(double* pa, double* pb, double* pc, dou { double delta = rho * vol / _dustMass; if (delta > maxDustFraction()) return true; + if (delta < minDustFraction()) return false; } // handle maximum dust optical depth @@ -173,6 +178,8 @@ bool TetraMeshSpatialGrid::tetUnsuitable(double* pa, double* pb, double* pc, dou // handle maximum dust density dispersion if (_hasDustDensityDispersion) { + // if (rhovar > maxDustDensityDispersion()) return true; + double q = rhomax > 0 ? (rhomax - rhomin) / rhomax : 0.; if (q > maxDustDensityDispersion()) return true; } diff --git a/SKIRT/core/TetraMeshSpatialGrid.hpp b/SKIRT/core/TetraMeshSpatialGrid.hpp index 18547914..07b3292a 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.hpp +++ b/SKIRT/core/TetraMeshSpatialGrid.hpp @@ -35,6 +35,12 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid, public DensityInCellInterfac ATTRIBUTE_DEFAULT_VALUE(maxDustFraction, "1e-6") ATTRIBUTE_DISPLAYED_IF(maxDustFraction, "DustMix") + PROPERTY_DOUBLE(minDustFraction, "the minimum fraction of dust contained in each cell") + ATTRIBUTE_MIN_VALUE(minDustFraction, "[0") + ATTRIBUTE_MAX_VALUE(minDustFraction, "1e-2]") + ATTRIBUTE_DEFAULT_VALUE(minDustFraction, "1e-6") + ATTRIBUTE_DISPLAYED_IF(minDustFraction, "DustMix") + PROPERTY_DOUBLE(maxDustOpticalDepth, "the maximum diagonal dust optical depth for each cell") ATTRIBUTE_MIN_VALUE(maxDustOpticalDepth, "[0") ATTRIBUTE_MAX_VALUE(maxDustOpticalDepth, "100]") @@ -50,7 +56,7 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid, public DensityInCellInterfac PROPERTY_DOUBLE(maxDustDensityDispersion, "the maximum dust density dispersion in each cell") ATTRIBUTE_MIN_VALUE(maxDustDensityDispersion, "[0") - ATTRIBUTE_MAX_VALUE(maxDustDensityDispersion, "1]") + ATTRIBUTE_MAX_VALUE(maxDustDensityDispersion, "1000]") ATTRIBUTE_DEFAULT_VALUE(maxDustDensityDispersion, "0") ATTRIBUTE_DISPLAYED_IF(maxDustDensityDispersion, "DustMix&Level2") From bcdad2fe94b1815f3b5f98f1ee383b7827269a01 Mon Sep 17 00:00:00 2001 From: arlauwer Date: Wed, 14 Feb 2024 15:10:32 +0100 Subject: [PATCH 21/51] tetra stats --- SKIRT/core/TetraMeshSnapshot.cpp | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/SKIRT/core/TetraMeshSnapshot.cpp b/SKIRT/core/TetraMeshSnapshot.cpp index 9fca533c..dcd77aef 100644 --- a/SKIRT/core/TetraMeshSnapshot.cpp +++ b/SKIRT/core/TetraMeshSnapshot.cpp @@ -539,8 +539,31 @@ void TetraMeshSnapshot::buildMesh(const TetraMeshSpatialGrid* grid, bool plc) _centroids.push_back(&_tetrahedra[i]->_centroid); } - log()->info("number of vertices " + std::to_string(numVertices)); - log()->info("number of tetrahedra " + std::to_string(numTetra)); + // compile statistics + double minVol = DBL_MAX; + double maxVol = 0.; + double totalVol = 0.; + double totalVol2 = 0.; + for (int m = 0; m < numTetra; m++) + { + double vol = _tetrahedra[m]->volume(); + totalVol += vol; + totalVol2 += vol * vol; + minVol = min(minVol, vol); + maxVol = max(maxVol, vol); + } + double V = _extent.volume(); + double avgVol = totalVol / (numTetra * V); + double varVol = (totalVol2 / numTetra - avgVol * avgVol) / (V * V); + + // log neighbor statistics + log()->info("Done computing tetrahedral tessellation"); + log()->info(" Number of vertices " + std::to_string(numVertices)); + log()->info(" Number of tetrahedra " + std::to_string(numTetra)); + log()->info(" Average volume\% per cell: " + StringUtils::toString(avgVol, 'e')); + log()->info(" Variance of volume\% per cell: " + StringUtils::toString(varVol, 'e')); + log()->info(" Minimum volume\% cell: " + StringUtils::toString(minVol, 'e')); + log()->info(" Maximum volume\% cell: " + StringUtils::toString(maxVol, 'e')); } //////////////////////////////////////////////////////////////////// From da083d6f5eb01f1be3fc08740f340df21147f6bd Mon Sep 17 00:00:00 2001 From: arlauwer Date: Sat, 17 Feb 2024 12:26:31 +0100 Subject: [PATCH 22/51] fixes and var test --- SKIRT/core/MediumSystem.cpp | 26 ++++++++++++++++++++++++++ SKIRT/core/TetraMeshSnapshot.cpp | 10 +++++----- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/SKIRT/core/MediumSystem.cpp b/SKIRT/core/MediumSystem.cpp index 16add6e4..97b14bd4 100644 --- a/SKIRT/core/MediumSystem.cpp +++ b/SKIRT/core/MediumSystem.cpp @@ -393,6 +393,32 @@ void MediumSystem::setupSelfAfter() _state.initCommunicate(); log->info("Done calculating cell densities"); + + Box extent = grid()->boundingBox(); + + Random* rd = find(); + + double total_m = 0.; + double total_m2 = 0.; + + constexpr double N = 1e7; + + for (int i = 0; i < N; i++) + { + Position pos = rd->position(extent); + int m = grid()->cellIndex(pos); + double gm = massDensity(m); + double tm = dustMassDensity(pos); + double md = (gm - tm) / gm; + + total_m += md; + total_m2 += md * md; + } + double avg_m = total_m / N; + double var_m = total_m2 / N - avg_m * avg_m; + + log->info(StringUtils::toString(avg_m)); + log->info(StringUtils::toString(var_m)); } //////////////////////////////////////////////////////////////////// diff --git a/SKIRT/core/TetraMeshSnapshot.cpp b/SKIRT/core/TetraMeshSnapshot.cpp index dcd77aef..f8047086 100644 --- a/SKIRT/core/TetraMeshSnapshot.cpp +++ b/SKIRT/core/TetraMeshSnapshot.cpp @@ -473,7 +473,7 @@ void TetraMeshSnapshot::buildMesh(const TetraMeshSpatialGrid* grid, bool plc) { in.numberofpoints = 50; in.pointlist = new REAL[in.numberofpoints * 3]; - for (int i = 8; i < in.numberofpoints; i++) + for (int i = 0; i < in.numberofpoints; i++) { Vec pos = random()->position(_extent); in.pointlist[i * 3 + 0] = pos.x(); @@ -542,19 +542,19 @@ void TetraMeshSnapshot::buildMesh(const TetraMeshSpatialGrid* grid, bool plc) // compile statistics double minVol = DBL_MAX; double maxVol = 0.; - double totalVol = 0.; double totalVol2 = 0.; for (int m = 0; m < numTetra; m++) { double vol = _tetrahedra[m]->volume(); - totalVol += vol; totalVol2 += vol * vol; minVol = min(minVol, vol); maxVol = max(maxVol, vol); } double V = _extent.volume(); - double avgVol = totalVol / (numTetra * V); - double varVol = (totalVol2 / numTetra - avgVol * avgVol) / (V * V); + minVol /= V; + maxVol /= V; + double avgVol = 1 / (double)numTetra; + double varVol = (totalVol2 / numTetra / (V * V) - avgVol * avgVol); // log neighbor statistics log()->info("Done computing tetrahedral tessellation"); From 49b6ebd4f458a25d5663334212d91e4bede55c3d Mon Sep 17 00:00:00 2001 From: arlauwer Date: Sat, 17 Feb 2024 15:34:36 +0100 Subject: [PATCH 23/51] var fix --- SKIRT/core/MediumSystem.cpp | 45 ++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/SKIRT/core/MediumSystem.cpp b/SKIRT/core/MediumSystem.cpp index 97b14bd4..b00c5691 100644 --- a/SKIRT/core/MediumSystem.cpp +++ b/SKIRT/core/MediumSystem.cpp @@ -398,8 +398,9 @@ void MediumSystem::setupSelfAfter() Random* rd = find(); - double total_m = 0.; - double total_m2 = 0.; + double M = 0., M2 = 0.; + double G = 0., G2 = 0.; + double E = 0., E2 = 0.; constexpr double N = 1e7; @@ -407,18 +408,42 @@ void MediumSystem::setupSelfAfter() { Position pos = rd->position(extent); int m = grid()->cellIndex(pos); - double gm = massDensity(m); - double tm = dustMassDensity(pos); - double md = (gm - tm) / gm; - - total_m += md; - total_m2 += md * md; + if (m != -1) + { + double gm = massDensity(m); + double tm = dustMassDensity(pos); + double dm = tm ? (gm - tm) / tm : 0.; + M += dm; + M2 += dm * dm; + + double gg = gasNumberDensity(m); + double tg = gasNumberDensity(pos); + double dg = tg ? (gg - tg) / tg : 0.; + G += dg; + G2 += dg * dg; + + double ge = electronNumberDensity(m); + double te = electronNumberDensity(pos); + double de = te ? (ge - te) / te : 0.; + E += de; + E2 += de * de; + } } - double avg_m = total_m / N; - double var_m = total_m2 / N - avg_m * avg_m; + double avg_m = M / N; + double var_m = sqrt(M2 / N - avg_m * avg_m); + + double avg_g = G / N; + double var_g = sqrt(G2 / N - avg_g * avg_g); + + double avg_e = E / N; + double var_e = sqrt(E2 / N - avg_e * avg_e); log->info(StringUtils::toString(avg_m)); log->info(StringUtils::toString(var_m)); + log->info(StringUtils::toString(avg_g)); + log->info(StringUtils::toString(var_g)); + log->info(StringUtils::toString(avg_e)); + log->info(StringUtils::toString(var_e)); } //////////////////////////////////////////////////////////////////// From 4dfbef9b12fb1b23c8256f3611cdb8c17c4b8cdb Mon Sep 17 00:00:00 2001 From: arlauwer Date: Wed, 28 Feb 2024 12:16:38 +0100 Subject: [PATCH 24/51] minVolume --- SKIRT/core/TetraMeshSnapshot.cpp | 15 ++++++++------- SKIRT/core/TetraMeshSpatialGrid.hpp | 6 ++++++ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/SKIRT/core/TetraMeshSnapshot.cpp b/SKIRT/core/TetraMeshSnapshot.cpp index f8047086..bf450f73 100644 --- a/SKIRT/core/TetraMeshSnapshot.cpp +++ b/SKIRT/core/TetraMeshSnapshot.cpp @@ -452,16 +452,17 @@ void TetraMeshSnapshot::buildMesh(const TetraMeshSpatialGrid* grid, bool plc) addFacet(&in.facetlist[4], {2, 6, 7, 3}); // Facet 5. back addFacet(&in.facetlist[5], {3, 7, 4, 0}); // Facet 6. left - behavior.plc = 1; // -p PLC - behavior.quality = 1; // -q quality mesh - behavior.fixedvolume = 1; // -a max volume - behavior.neighout = 2; // -nn neighbors and edges? - behavior.zeroindex = 1; // -z zero index - behavior.facesout = 1; // -f faces + behavior.plc = 1; // -p PLC + behavior.quality = 1; // -q quality mesh + behavior.fixedvolume = 1; // -a max volume + behavior.neighout = 2; // -nn neighbors and edges? + behavior.zeroindex = 1; // -z zero index + behavior.facesout = 1; // -f faces + behavior.maxvolume = grid->minVolume() * _extent.volume(); // -a max volume + // behavior.edgesout = 1; // -e edges // behavior.weighted = 1; // -w weighted // behavior.minratio = 5.0; // -q quality - behavior.maxvolume = 0.05 * _extent.volume(); // -a max volume // behavior.mindihedral = 5.0; // -q/ minimal angle in.tetunsuitable = [grid](double* pa, double* pb, double* pc, double* pd, double vol) { diff --git a/SKIRT/core/TetraMeshSpatialGrid.hpp b/SKIRT/core/TetraMeshSpatialGrid.hpp index 07b3292a..87ecb79b 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.hpp +++ b/SKIRT/core/TetraMeshSpatialGrid.hpp @@ -29,6 +29,12 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid, public DensityInCellInterfac ITEM_CONCRETE(TetraMeshSpatialGrid, BoxSpatialGrid, "a Tetra tessellation-based spatial grid") ATTRIBUTE_TYPE_DISPLAYED_IF(TetraMeshSpatialGrid, "Level2") + PROPERTY_DOUBLE(minVolume, "the minimum volume of each cell") + ATTRIBUTE_MIN_VALUE(minVolume, "]0") + ATTRIBUTE_MAX_VALUE(minVolume, "1[") + ATTRIBUTE_DEFAULT_VALUE(minVolume, "1e-4") + ATTRIBUTE_DISPLAYED_IF(minVolume, "DustMix") + PROPERTY_DOUBLE(maxDustFraction, "the maximum fraction of dust contained in each cell") ATTRIBUTE_MIN_VALUE(maxDustFraction, "[0") ATTRIBUTE_MAX_VALUE(maxDustFraction, "1e-2]") From a307ead69b4c30c5ed4b785f279837f6f83c3fde Mon Sep 17 00:00:00 2001 From: arlauwer Date: Mon, 4 Mar 2024 15:15:29 +0100 Subject: [PATCH 25/51] small fix; removed unused code --- SKIRT/core/TetraMeshSnapshot.cpp | 118 ++++--------------------------- 1 file changed, 13 insertions(+), 105 deletions(-) diff --git a/SKIRT/core/TetraMeshSnapshot.cpp b/SKIRT/core/TetraMeshSnapshot.cpp index bf450f73..39cd5ccd 100644 --- a/SKIRT/core/TetraMeshSnapshot.cpp +++ b/SKIRT/core/TetraMeshSnapshot.cpp @@ -452,14 +452,20 @@ void TetraMeshSnapshot::buildMesh(const TetraMeshSpatialGrid* grid, bool plc) addFacet(&in.facetlist[4], {2, 6, 7, 3}); // Facet 5. back addFacet(&in.facetlist[5], {3, 7, 4, 0}); // Facet 6. left - behavior.plc = 1; // -p PLC - behavior.quality = 1; // -q quality mesh - behavior.fixedvolume = 1; // -a max volume - behavior.neighout = 2; // -nn neighbors and edges? - behavior.zeroindex = 1; // -z zero index - behavior.facesout = 1; // -f faces + behavior.plc = 1; // -p PLC + behavior.quality = 1; // -q quality mesh + behavior.neighout = 2; // -nn neighbors and edges? + behavior.zeroindex = 1; // -z zero index + behavior.facesout = 1; // -f faces + behavior.fixedvolume = 1; // -a max volume behavior.maxvolume = grid->minVolume() * _extent.volume(); // -a max volume - + // behavior.maxvolume = _extent.volume(); // -a max volume + // behavior.steinerleft = -1; // -S infinite + // behavior.nofacewritten = 1; + // behavior.nonodewritten = 1; + // behavior.noelewritten = 1; + behavior.verbose = 1; + // behavior.edgesout = 1; // -e edges // behavior.weighted = 1; // -w weighted // behavior.minratio = 5.0; // -q quality @@ -1011,104 +1017,6 @@ int TetraMeshSnapshot::cellIndex(Position bfr) const if (m < 0) break; } } - // */ - - /* - // traverse towards input point from nearest centroid - int m = tree->nearest(bfr, _centroids)->m(); - const Tetra* root = _tetrahedra[m]; - - test++; - if (root->inside(bfr)) return test; - - Vec pos = root->_centroid; - Vec dir = bfr - pos; - double dist = dir.norm(); - dir /= dist; - - // start by checking all faces - std::vector faces = {0, 1, 2, 3}; - - while (true) - { - const Tetra* tetra = _tetrahedra[m]; - - double ds = DBL_MAX; - int leavingFace = -1; - - // find exit face - for (int face : faces) - { - const Vec& n = tetra->_faces[face]._normal; - double ndotk = Vec::dot(n, dir); - if (ndotk > 0) - { - const Vec& v = *tetra->_vertices[(face + 1) % 4]; - double dq = Vec::dot(n, v - pos) / ndotk; - if (dq < ds) - { - ds = dq; - leavingFace = face; - } - } - } - - if (leavingFace == -1) break; - - // propagate and check inside if we passed the input point - pos += ds * dir; - dist -= ds; - if (dist <= 0) - { - test++; - if (tetra->inside(bfr)) return test; - } - - m = tetra->_faces[leavingFace]._ntetra; - int enteringFace = tetra->_faces[leavingFace]._nface; - faces = {(enteringFace + 1) % 4, (enteringFace + 2) % 4, (enteringFace + 3) % 4}; - - if (m < 0) break; - } - */ - - /* - // use a breadth-first search over the neighboring tetrahedra - int root = tree->nearest(bfr, _centroids)->m(); - std::queue queue; - std::unordered_set explored; - queue.push(root); - - // Vec dir = bfr - _tetrahedra[root]->_centroid; - - int t; - while (queue.size() > 0) - { - t = queue.front(); - queue.pop(); - const Tetra* tetra = _tetrahedra[t]; - - test++; - if (tetra->inside(bfr)) return test; - explored.insert(t); - - Vec dir = bfr - tetra->_centroid; - for (const Face& face : tetra->_faces) - { - int n = face._ntetra; - if (n != -1 && (explored.find(n) == explored.end())) // if not already explored - { - if (Vec::dot(dir, face._normal) > 0) - { - queue.push(n); - explored.insert(n); - } - } - } - } - */ - - log()->error("search tree failed to find the tetrahedron"); // change to warning? } // if there is no search tree or search tree failed, simply loop over all tetrahedra in the block From f1fe491390a6e7c5617a4a6ff6feab6cc6433b20 Mon Sep 17 00:00:00 2001 From: arlauwer Date: Mon, 1 Jul 2024 14:24:27 +0200 Subject: [PATCH 26/51] started cleanup --- SKIRT/core/MediumSystem.cpp | 51 -------- SKIRT/core/TetraMeshSnapshot.cpp | 179 +++++++++------------------- SKIRT/core/TetraMeshSnapshot.hpp | 73 +----------- SKIRT/core/TetraMeshSpatialGrid.hpp | 14 +-- SKIRT/core/Tetrahedron.cpp | 8 +- SKIRT/core/Tetrahedron.hpp | 2 +- 6 files changed, 69 insertions(+), 258 deletions(-) diff --git a/SKIRT/core/MediumSystem.cpp b/SKIRT/core/MediumSystem.cpp index b00c5691..16add6e4 100644 --- a/SKIRT/core/MediumSystem.cpp +++ b/SKIRT/core/MediumSystem.cpp @@ -393,57 +393,6 @@ void MediumSystem::setupSelfAfter() _state.initCommunicate(); log->info("Done calculating cell densities"); - - Box extent = grid()->boundingBox(); - - Random* rd = find(); - - double M = 0., M2 = 0.; - double G = 0., G2 = 0.; - double E = 0., E2 = 0.; - - constexpr double N = 1e7; - - for (int i = 0; i < N; i++) - { - Position pos = rd->position(extent); - int m = grid()->cellIndex(pos); - if (m != -1) - { - double gm = massDensity(m); - double tm = dustMassDensity(pos); - double dm = tm ? (gm - tm) / tm : 0.; - M += dm; - M2 += dm * dm; - - double gg = gasNumberDensity(m); - double tg = gasNumberDensity(pos); - double dg = tg ? (gg - tg) / tg : 0.; - G += dg; - G2 += dg * dg; - - double ge = electronNumberDensity(m); - double te = electronNumberDensity(pos); - double de = te ? (ge - te) / te : 0.; - E += de; - E2 += de * de; - } - } - double avg_m = M / N; - double var_m = sqrt(M2 / N - avg_m * avg_m); - - double avg_g = G / N; - double var_g = sqrt(G2 / N - avg_g * avg_g); - - double avg_e = E / N; - double var_e = sqrt(E2 / N - avg_e * avg_e); - - log->info(StringUtils::toString(avg_m)); - log->info(StringUtils::toString(var_m)); - log->info(StringUtils::toString(avg_g)); - log->info(StringUtils::toString(var_g)); - log->info(StringUtils::toString(avg_e)); - log->info(StringUtils::toString(var_e)); } //////////////////////////////////////////////////////////////////// diff --git a/SKIRT/core/TetraMeshSnapshot.cpp b/SKIRT/core/TetraMeshSnapshot.cpp index 39cd5ccd..c8f1bef0 100644 --- a/SKIRT/core/TetraMeshSnapshot.cpp +++ b/SKIRT/core/TetraMeshSnapshot.cpp @@ -135,11 +135,6 @@ namespace p->vertexlist[3] = vertices[3]; } - template int sgn(T a) - { - return (a > T(0)) - (a < T(0)); - } - int findEnteringFace(const Tetra* tetra, const Vec& pos, const Direction& dir) { int enteringFace = -1; @@ -422,11 +417,11 @@ void TetraMeshSnapshot::buildMesh(const TetraMeshSpatialGrid* grid, bool plc) in.pointlist[2] = _extent.zmin(); in.pointlist[3] = _extent.xmax(); - in.pointlist[4] = _extent.xmin(); + in.pointlist[4] = _extent.ymin(); in.pointlist[5] = _extent.zmin(); in.pointlist[6] = _extent.xmax(); - in.pointlist[7] = _extent.xmax(); + in.pointlist[7] = _extent.ymax(); in.pointlist[8] = _extent.zmin(); in.pointlist[9] = _extent.xmin(); @@ -443,7 +438,6 @@ void TetraMeshSnapshot::buildMesh(const TetraMeshSpatialGrid* grid, bool plc) } in.numberoffacets = 6; - // in.numberoffacets += 1; in.facetlist = new tetgenio::facet[in.numberoffacets]; addFacet(&in.facetlist[0], {0, 1, 2, 3}); // Facet 1. bottom addFacet(&in.facetlist[1], {4, 5, 6, 7}); // Facet 2. top @@ -452,51 +446,41 @@ void TetraMeshSnapshot::buildMesh(const TetraMeshSpatialGrid* grid, bool plc) addFacet(&in.facetlist[4], {2, 6, 7, 3}); // Facet 5. back addFacet(&in.facetlist[5], {3, 7, 4, 0}); // Facet 6. left - behavior.plc = 1; // -p PLC - behavior.quality = 1; // -q quality mesh - behavior.neighout = 2; // -nn neighbors and edges? - behavior.zeroindex = 1; // -z zero index - behavior.facesout = 1; // -f faces - behavior.fixedvolume = 1; // -a max volume - behavior.maxvolume = grid->minVolume() * _extent.volume(); // -a max volume - // behavior.maxvolume = _extent.volume(); // -a max volume - // behavior.steinerleft = -1; // -S infinite - // behavior.nofacewritten = 1; - // behavior.nonodewritten = 1; - // behavior.noelewritten = 1; - behavior.verbose = 1; - - // behavior.edgesout = 1; // -e edges - // behavior.weighted = 1; // -w weighted - // behavior.minratio = 5.0; // -q quality - // behavior.mindihedral = 5.0; // -q/ minimal angle - + behavior.plc = 1; // -p PLC + behavior.quality = 1; // -q quality mesh + behavior.neighout = 2; // -nn neighbors and edges + behavior.facesout = 1; // -f faces + behavior.zeroindex = 1; // -z zero index + behavior.fixedvolume = 1; // -a max volume + behavior.maxvolume = grid->maxVolumeFraction() * _extent.volume(); // -a max volume + // refining criterion in.tetunsuitable = [grid](double* pa, double* pb, double* pc, double* pd, double vol) { return grid->tetUnsuitable(pa, pb, pc, pd, vol); }; } - // psc + // Delaunay Triangulation else { - in.numberofpoints = 50; - in.pointlist = new REAL[in.numberofpoints * 3]; - for (int i = 0; i < in.numberofpoints; i++) - { - Vec pos = random()->position(_extent); - in.pointlist[i * 3 + 0] = pos.x(); - in.pointlist[i * 3 + 1] = pos.y(); - in.pointlist[i * 3 + 2] = pos.z(); - } + // WIP import from file!!! + // in.numberofpoints = 50; + // in.pointlist = new REAL[in.numberofpoints * 3]; + // for (int i = 0; i < in.numberofpoints; i++) + // { + // Vec pos = random()->position(_extent); + // in.pointlist[i * 3 + 0] = pos.x(); + // in.pointlist[i * 3 + 1] = pos.y(); + // in.pointlist[i * 3 + 2] = pos.z(); + // } behavior.psc = 1; // -s PSC - behavior.neighout = 2; // -nn neighbors and edges? - behavior.zeroindex = 1; // -z zero index + behavior.neighout = 2; // -nn neighbors and edges behavior.facesout = 1; // -f faces + behavior.zeroindex = 1; // -z zero index } tetrahedralize(&behavior, &in, &out); - // tranfser TetGen data in TetraMeshSnapshot data containers + // tranfser TetGen data to TetraMeshSnapshot data containers numTetra = out.numberoftetrahedra; numVertices = out.numberofpoints; @@ -516,9 +500,13 @@ void TetraMeshSnapshot::buildMesh(const TetraMeshSpatialGrid* grid, bool plc) std::array vertices; std::array faces; - // calc vertices first - for (int c = 0; c < 4; c++) vertices[c] = _vertices[out.tetrahedronlist[4 * i + c]]; + // vertices + for (int c = 0; c < 4; c++) + { + vertices[c] = _vertices[out.tetrahedronlist[4 * i + c]]; + } + // faces for (int c = 0; c < 4; c++) { // -1 if no neighbor @@ -537,7 +525,6 @@ void TetraMeshSnapshot::buildMesh(const TetraMeshSpatialGrid* grid, bool plc) } } } - faces[c] = Face(ntetra, nface); } @@ -564,13 +551,13 @@ void TetraMeshSnapshot::buildMesh(const TetraMeshSpatialGrid* grid, bool plc) double varVol = (totalVol2 / numTetra / (V * V) - avgVol * avgVol); // log neighbor statistics - log()->info("Done computing tetrahedral tessellation"); + log()->info("Done computing tetrahedralisation"); log()->info(" Number of vertices " + std::to_string(numVertices)); log()->info(" Number of tetrahedra " + std::to_string(numTetra)); - log()->info(" Average volume\% per cell: " + StringUtils::toString(avgVol, 'e')); - log()->info(" Variance of volume\% per cell: " + StringUtils::toString(varVol, 'e')); - log()->info(" Minimum volume\% cell: " + StringUtils::toString(minVol, 'e')); - log()->info(" Maximum volume\% cell: " + StringUtils::toString(maxVol, 'e')); + log()->info(" Average volume fraction per cell: " + StringUtils::toString(avgVol, 'e')); + log()->info(" Variance of volume fraction per cell: " + StringUtils::toString(varVol, 'e')); + log()->info(" Minimum volume fraction cell: " + StringUtils::toString(minVol, 'e')); + log()->info(" Maximum volume fraction cell: " + StringUtils::toString(maxVol, 'e')); } //////////////////////////////////////////////////////////////////// @@ -747,17 +734,17 @@ void TetraMeshSnapshot::buildSearchSingle() void TetraMeshSnapshot::writeGridPlotFiles(const SimulationItem* probe) const { - std::ofstream outputFile("data/tetrahedra.txt"); - for (size_t i = 0; i < _tetrahedra.size(); i++) - { - const Tetra* tetra = _tetrahedra[i]; - for (size_t l = 0; l < 4; l++) - { - const Vec* r = tetra->_vertices[l]; - outputFile << r->x() << ", " << r->y() << ", " << r->z() << "\n"; - } - } - outputFile.close(); + // std::ofstream outputFile("data/tetrahedra.txt"); + // for (size_t i = 0; i < _tetrahedra.size(); i++) + // { + // const Tetra* tetra = _tetrahedra[i]; + // for (size_t l = 0; l < 4; l++) + // { + // const Vec* r = tetra->_vertices[l]; + // outputFile << r->x() << ", " << r->y() << ", " << r->z() << "\n"; + // } + // } + // outputFile.close(); // outputFile.open("data/faces.txt"); // for (size_t i = 0; i < _tetrahedra.size(); i++) @@ -915,8 +902,6 @@ int TetraMeshSnapshot::cellIndex(Position bfr) const _extent.cellIndices(i, j, k, bfr, _nb, _nb, _nb); int b = i * _nb2 + j * _nb + k; - // int test = 0; - // look for the closest centroid in this block using the search tree Node* tree = _blocktrees[b]; if (tree) @@ -1061,35 +1046,12 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator public: MySegmentGenerator(const TetraMeshSnapshot* grid) : _grid(grid) {} - // #define WRITE - -#ifdef WRITE - std::ofstream out; - - void startwriting() - { - if (!out.is_open()) out.open("data/photon.txt"); - } - - void stopwriting() - { - out.close(); - } - - void write(double ds, int face) - { - out << "photon=" << _mr << "," << face << "\n"; - out << "ds=" << ds << "\n"; - out << "r=" << r().x() << "," << r().y() << "," << r().z() << "\n"; - out << "k=" << k().x() << "," << k().y() << "," << k().z() << std::endl; - } -#endif bool next() override { if (state() == State::Unknown) { // try moving the photon packet inside the grid; if this is impossible, return an empty path - // this also changes the state() + // this also changes the _state if (!moveInside(_grid->extent(), _grid->_eps)) return false; // get the index of the cell containing the current position @@ -1099,11 +1061,6 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator // if the photon packet started outside the grid, return the corresponding nonzero-length segment; // otherwise fall through to determine the first actual segment if (ds() > 0.) return true; - -#ifdef WRITE - startwriting(); - write(0, _mr); -#endif } // intentionally falls through @@ -1169,25 +1126,12 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator const Vec& n = tetra->_faces[leavingFace]._normal; const Vec& v = *tetra->_vertices[_enteringFace]; double ndotk = Vec::dot(n, dir); - - // if ndotk == 0. we are inside the leaving face, we should just move to the neighboring tetra and not move (ds=0) - // once it moved to its neighbor, it can't move back to this cell and it will traverse the next step correctly - // if (ndotk == 0.) - // ds = 0; - // else - // ds = Vec::dot(n, v - pos) / ndotk; - ds = Vec::dot(n, v - pos) / ndotk; } - - // if ds is too close to leaving face we recalculate cellIndex to avoid traversing when ds ~ 0 - // this might actually slow down the traversal - // if no exit point was found, advance the current point by a small distance, // recalculate the cell index, and return to the start of the loop if (leavingFace == -1 || ds < _grid->_eps) { - // move towards centroid? propagater(_grid->_eps); _mr = _grid->cellIndex(r()); @@ -1196,11 +1140,10 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator setState(State::Outside); return false; } - - _enteringFace = -1; -#ifdef WRITE - write(ds, -1); -#endif + else + { + _enteringFace = -1; + } } // otherwise set the current point to the exit point and return the path segment else @@ -1214,24 +1157,18 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator setState(State::Outside); return false; } - - _enteringFace = tetra->_faces[leavingFace]._nface; - -#ifdef WRITE - write(ds, leavingFace); -#endif - - return true; + else + { + _enteringFace = tetra->_faces[leavingFace]._nface; + return true; + } } } } if (state() == State::Outside) - {} - -#ifdef WRITE - stopwriting(); -#endif + { + } return false; } diff --git a/SKIRT/core/TetraMeshSnapshot.hpp b/SKIRT/core/TetraMeshSnapshot.hpp index a58c4951..cc20d3e3 100644 --- a/SKIRT/core/TetraMeshSnapshot.hpp +++ b/SKIRT/core/TetraMeshSnapshot.hpp @@ -18,78 +18,7 @@ class SpatialGridPath; //////////////////////////////////////////////////////////////////// -/** A TetraMeshSnapshot object represents a Tetra tessellation or \em mesh of a cuboidal - spatial domain (given a list of generating sites) and offers several facilities related to this - mesh. A Tetra mesh partitions the domain in convex polyhedra. Consider a given set of points - in the domain, called \em sites. For each site there will be a corresponding region consisting - of all points in the domain closer to that site than to any other. These regions are called - Tetra \em cells, and together they form the Tetra tessellation of the domain. - - The TetraMeshSnapshot class serves two key purposes: (1) represent snapshot data produced by - a hydrodynamical simulation and imported from a column text file, defining a primary source or - a transfer medium distribution, and (2) implement an unstructured spatial grid that discretizes - the spatial domain as a basis for the radiative transfer simulation itself. - - To support the first use case (imported snapshot data), the TetraMeshSnapshot class is based - on the Snapshot class; it uses the facilities offered there to configure and help read the - snapshot data, and it implements all functions in the general Snapshot public interface. In - addition it offers functionality that is specific to this snapshot type, such as, for example, - the requirement to configure the spatial extent of the domain. In this use case, the client - employs the default constructor and then proceeds to configure the snapshot object as described - in the Snapshot class header. - - To support the second use case (spatial grid for radiative transfer), the class offers - specialty constructors that accept a list of the generating sites from various sources, - including a programmatically generated list or a user-supplied text column file. Note that this - input file includes just the site coordinates, while a snapshot data file would include - additional properties for each site. The specialty constructors automatically complete the - configuration sequence of the object, so that the getters can be used immediately after - construction. - - Once an TetraMeshSnapshot object has been constructed and fully configured, its data members - are no longer modified. Consequently all getters are re-entrant. - - Using the Voro++ library - ------------------------ - - To build the Tetra tessellation, the buildMesh() function in this class uses the code in the - \c voro subfolder of the SKIRT code hierarchy, which is taken from the Voro++ library written - by Chris H. Rycroft (Harvard University/Lawrence Berkeley Laboratory) at - https://github.com/chr1shr/voro (git commit 122531f) with minimal changes to avoid compiler - warnings. - - A distinguishing feature of the Voro++ library is that it carries out cell-based calculations, - computing the Tetra cell for each site individually, rather than computing the Tetra - tessellation as a global network of vertices and edges. It is therefore particularly - well-suited for applications that require cell-based properties such as the cell volume, the - centroid position or the number of faces or vertices. Equally important in the context of - SKIRT, it is easy to distribute the work over parallel execution threads because, after setting - up a common search data structure holding all sites, the calculations for the various cells are - mutually independent. - - The Voro++ approach also has an important drawback. Because cells are handled independently of - each other, floating pointing rounding errors sometimes cause inconsistencies where the results - for one cell do not properly match the corresponding results for a neighboring cell. This most - often happens when the generating sites are very close to each other and/or form certain - hard-to-calculate geometries. These problems manifest themselves either as empty cells or as - asymmetries in the neighbor lists. To handle these situations, the buildMesh() function removes - some sites from the input list as described below. - - Firstly, before actually building the Tetra tessellation, and in addition to removing sites - that lie outside of the spatial domain, the function discards sites that lie closer to any - previously listed site than \f$10^{-12}\f$ times the diagonal of the spatial domain. This - preventative measure essentially removes any "degenerate" sites from the input list. Given the - extremely small distances, it is very unlikely that the physcis of the input model would be - affected by this operation. - - Secondly, after all Tetra cells have been calculated, the function verifies the cell - properties. If any of the cells have a zero volume or if any of the cell neighbors are not - mutual, the involved sites are discarded and the Tetra construction starts anew from scratch - with the reduced list of sites. After a maximum of 5 attempts, the function throws a fatal - error. In practice, this will hopefully never happen. Discarding incorrectly calculated cells - perhaps incurs a slightly higher risk of changing the physcis of the input model. However, in - practice it seems that these issues mostly occur in regions of high site density, so that the - errors should be fairly limited. */ +/** */ class TetraMeshSnapshot : public Snapshot { //================= Construction - Destruction ================= diff --git a/SKIRT/core/TetraMeshSpatialGrid.hpp b/SKIRT/core/TetraMeshSpatialGrid.hpp index 87ecb79b..2520bc5c 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.hpp +++ b/SKIRT/core/TetraMeshSpatialGrid.hpp @@ -29,21 +29,21 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid, public DensityInCellInterfac ITEM_CONCRETE(TetraMeshSpatialGrid, BoxSpatialGrid, "a Tetra tessellation-based spatial grid") ATTRIBUTE_TYPE_DISPLAYED_IF(TetraMeshSpatialGrid, "Level2") - PROPERTY_DOUBLE(minVolume, "the minimum volume of each cell") - ATTRIBUTE_MIN_VALUE(minVolume, "]0") - ATTRIBUTE_MAX_VALUE(minVolume, "1[") - ATTRIBUTE_DEFAULT_VALUE(minVolume, "1e-4") - ATTRIBUTE_DISPLAYED_IF(minVolume, "DustMix") + PROPERTY_DOUBLE(maxVolumeFraction, "the maximum volume fraction of each cell") + ATTRIBUTE_MIN_VALUE(maxVolumeFraction, "]0") + ATTRIBUTE_MAX_VALUE(maxVolumeFraction, "1[") + ATTRIBUTE_DEFAULT_VALUE(maxVolumeFraction, "1e-3") + ATTRIBUTE_DISPLAYED_IF(maxVolumeFraction, "DustMix") PROPERTY_DOUBLE(maxDustFraction, "the maximum fraction of dust contained in each cell") ATTRIBUTE_MIN_VALUE(maxDustFraction, "[0") - ATTRIBUTE_MAX_VALUE(maxDustFraction, "1e-2]") + ATTRIBUTE_MAX_VALUE(maxDustFraction, "1[") ATTRIBUTE_DEFAULT_VALUE(maxDustFraction, "1e-6") ATTRIBUTE_DISPLAYED_IF(maxDustFraction, "DustMix") PROPERTY_DOUBLE(minDustFraction, "the minimum fraction of dust contained in each cell") ATTRIBUTE_MIN_VALUE(minDustFraction, "[0") - ATTRIBUTE_MAX_VALUE(minDustFraction, "1e-2]") + ATTRIBUTE_MAX_VALUE(minDustFraction, "1[") ATTRIBUTE_DEFAULT_VALUE(minDustFraction, "1e-6") ATTRIBUTE_DISPLAYED_IF(minDustFraction, "DustMix") diff --git a/SKIRT/core/Tetrahedron.cpp b/SKIRT/core/Tetrahedron.cpp index 9e90892c..89c2edfa 100644 --- a/SKIRT/core/Tetrahedron.cpp +++ b/SKIRT/core/Tetrahedron.cpp @@ -1,6 +1,4 @@ #include "Tetrahedron.hpp" -#include "FatalError.hpp" -#include Tetra::Tetra(const std::array& vertices, const std::array& faces, const Array& prop) : Tetra(vertices, faces) @@ -98,13 +96,11 @@ double Tetra::generateBarycentric(double& s, double& t, double& u) // https://vcg.isti.cnr.it/activities/OLD/geometryegraphics/pointintetraedro.html if (s + t > 1.0) { // cut'n fold the cube into a prism - s = 1.0 - s; t = 1.0 - t; } if (t + u > 1.0) { // cut'n fold the prism into a tetrahedron - double tmp = u; u = 1.0 - s - t; t = 1.0 - tmp; @@ -122,9 +118,9 @@ double Tetra::generateBarycentric(double& s, double& t, double& u) Position Tetra::generatePosition(double s, double t, double u) const { - double w = Tetra::generateBarycentric(s, t, u); + double r = Tetra::generateBarycentric(s, t, u); - return Position(w * *_vertices[0] + u * *_vertices[1] + t * *_vertices[2] + s * *_vertices[3]); + return Position(r * *_vertices[0] + u * *_vertices[1] + t * *_vertices[2] + s * *_vertices[3]); } //////////////////////////////////////////////////////////////////// diff --git a/SKIRT/core/Tetrahedron.hpp b/SKIRT/core/Tetrahedron.hpp index 81552a06..87b20b18 100644 --- a/SKIRT/core/Tetrahedron.hpp +++ b/SKIRT/core/Tetrahedron.hpp @@ -1,6 +1,6 @@ +#include "Position.hpp" #include "Array.hpp" #include "Box.hpp" -#include "Position.hpp" struct Face { From 00f6a3912c7c00b0cd72e2331d7616f53958dc6f Mon Sep 17 00:00:00 2001 From: arlauwer Date: Fri, 15 Nov 2024 15:27:13 +0100 Subject: [PATCH 27/51] removed adaptive construction added all Tetra classes started work on refining Delaunay --- SKIRT/core/SimulationItemRegistry.cpp | 3 + SKIRT/core/TetraMeshGeometry.cpp | 44 +++++ SKIRT/core/TetraMeshGeometry.hpp | 77 ++++++++ SKIRT/core/TetraMeshMedium.cpp | 62 +++++++ SKIRT/core/TetraMeshMedium.hpp | 120 +++++++++++++ SKIRT/core/TetraMeshSnapshot.cpp | 245 ++++++++++++-------------- SKIRT/core/TetraMeshSnapshot.hpp | 25 +-- SKIRT/core/TetraMeshSource.cpp | 28 +++ SKIRT/core/TetraMeshSource.hpp | 58 ++++++ SKIRT/core/TetraMeshSpatialGrid.cpp | 214 ++++++++-------------- SKIRT/core/TetraMeshSpatialGrid.hpp | 75 +++----- SKIRT/core/Tetrahedron.hpp | 1 + SKIRT/tetgen/CMakeLists.txt | 2 +- SKIRT/tetgen/tetgen.h | 2 +- 14 files changed, 630 insertions(+), 326 deletions(-) create mode 100644 SKIRT/core/TetraMeshGeometry.cpp create mode 100644 SKIRT/core/TetraMeshGeometry.hpp create mode 100644 SKIRT/core/TetraMeshMedium.cpp create mode 100644 SKIRT/core/TetraMeshMedium.hpp create mode 100644 SKIRT/core/TetraMeshSource.cpp create mode 100644 SKIRT/core/TetraMeshSource.hpp diff --git a/SKIRT/core/SimulationItemRegistry.cpp b/SKIRT/core/SimulationItemRegistry.cpp index 70c6b72b..04545724 100755 --- a/SKIRT/core/SimulationItemRegistry.cpp +++ b/SKIRT/core/SimulationItemRegistry.cpp @@ -248,6 +248,9 @@ #include "TTauriDiskGeometry.hpp" #include "TemperatureProbe.hpp" #include "TemperatureWavelengthCellLibrary.hpp" +#include "TetraMeshGeometry.hpp" +#include "TetraMeshMedium.hpp" +#include "TetraMeshSource.hpp" #include "TetraMeshSpatialGrid.hpp" #include "ThemisDustMix.hpp" #include "ToddlersSED.hpp" diff --git a/SKIRT/core/TetraMeshGeometry.cpp b/SKIRT/core/TetraMeshGeometry.cpp new file mode 100644 index 00000000..f29a5834 --- /dev/null +++ b/SKIRT/core/TetraMeshGeometry.cpp @@ -0,0 +1,44 @@ +/*////////////////////////////////////////////////////////////////// +//// The SKIRT project -- advanced radiative transfer //// +//// © Astronomical Observatory, Ghent University //// +///////////////////////////////////////////////////////////////// */ + +#include "TetraMeshGeometry.hpp" +#include "TetraMeshSnapshot.hpp" + +//////////////////////////////////////////////////////////////////// + +Snapshot* TetraMeshGeometry::createAndOpenSnapshot() +{ + // create and open the snapshot + _tetraMeshSnapshot = new TetraMeshSnapshot; + _tetraMeshSnapshot->open(this, filename(), "Tetra sites"); + + // honor custom column reordering + _tetraMeshSnapshot->useColumns(useColumns()); + + // configure the position columns + _tetraMeshSnapshot->importPosition(); + + // configure the mass or density column + switch (massType()) + { + case MassType::MassDensity: _tetraMeshSnapshot->importMassDensity(); break; + case MassType::Mass: _tetraMeshSnapshot->importMass(); break; + case MassType::NumberDensity: _tetraMeshSnapshot->importNumberDensity(); break; + case MassType::Number: _tetraMeshSnapshot->importNumber(); break; + } + + // set the domain extent + _tetraMeshSnapshot->setExtent(domain()); + return _tetraMeshSnapshot; +} + +//////////////////////////////////////////////////////////////////// + +TetraMeshSnapshot* TetraMeshGeometry::tetraMesh() const +{ + return _tetraMeshSnapshot; +} + +//////////////////////////////////////////////////////////////////// diff --git a/SKIRT/core/TetraMeshGeometry.hpp b/SKIRT/core/TetraMeshGeometry.hpp new file mode 100644 index 00000000..d70fda99 --- /dev/null +++ b/SKIRT/core/TetraMeshGeometry.hpp @@ -0,0 +1,77 @@ +/*////////////////////////////////////////////////////////////////// +//// The SKIRT project -- advanced radiative transfer //// +//// © Astronomical Observatory, Ghent University //// +///////////////////////////////////////////////////////////////// */ + +#ifndef TETRAMESHGEOMETRY_HPP +#define TETRAMESHGEOMETRY_HPP + +#include "MeshGeometry.hpp" +#include "TetraMeshInterface.hpp" + +//////////////////////////////////////////////////////////////////// + +/** A TetraMeshGeometry instance represents a 3D geometry with a spatial density distribution + described by a list of sites generating a Tetra tesselation of a cubodail domain. The data is + usually extracted from a cosmological simulation snapshot, and it must be provided in a column + text file formatted as described below. The total mass in the geometry is normalized to unity + after importing the data. + + Refer to the description of the TextInFile class for information on overall formatting and on + how to include header lines specifying the units for each column in the input file. In case the + input file has no unit specifications, the default units mentioned below are used instead. The + input file should contain 4, 5, or 6 columns, depending on the options configured by the user + for this TetraMeshGeometry instance: + + \f[ x\,(\mathrm{pc}) \quad y\,(\mathrm{pc}) \quad z\,(\mathrm{pc}) \quad \{\, + \rho\,(\text{M}_\odot\,\text{pc}^{-3}) \;\;|\;\; M\,(\text{M}_\odot) \;\;|\;\; + n\,(\text{cm}^{-3}) \;\;|\;\; N\,(1) \,\} \quad [Z\,(1)] \quad [T\,(\mathrm{K})] \f] + + The first three columns are the \f$x\f$, \f$y\f$ and \f$z\f$ coordinates of the Tetra site + (i.e. the location defining a particular Tetra cell). + + Depending on the value of the \em massType option, the fourth column lists the average mass + density \f$\rho\f$, the integrated mass \f$M\f$, the average number density \f$n\f$, or the + integrated number density \f$N\f$ for the cell corresponding to the site. The precise units for + this field are irrelevant because the total mass in the geometry will be normalized to unity + after importing the data. However, the import procedure still insists on knowing the units. + + If the \em importMetallicity option is enabled, the next column specifies a "metallicity" + fraction, which in this context is simply multiplied with the mass/density column to obtain the + actual mass/density of the cell. If the \em importTemperature option is enabled, the next + column specifies a temperature. If this temperature is higher than the maximum configured + temperature, the mass and density for the site are set to zero, regardless of the mass or + density specified in the fourth column. If the \em importTemperature option is disabled, or the + maximum temperature value is set to zero, such a cutoff is not applied. */ +class TetraMeshGeometry : public MeshGeometry, public TetraMeshInterface +{ + ITEM_CONCRETE(TetraMeshGeometry, MeshGeometry, "a geometry imported from data represented on a Tetra mesh") + ATTRIBUTE_TYPE_INSERT(TetraMeshGeometry, "TetraMeshInterface") + ITEM_END() + + //============= Construction - Setup - Destruction ============= + +protected: + /** This function constructs a new TetraMeshSnapshot object, calls its open() function, + passes it the domain extent configured by the user, configures it to import a mass or a + density column, and finally returns a pointer to the object. Ownership of the Snapshot + object is transferred to the caller. */ + Snapshot* createAndOpenSnapshot() override; + + //=================== Other functions ================== + +protected: + /** This function implements the TetraMeshInterface interface. It returns a pointer to the + Tetra mesh snapshot maintained by this geometry. */ + TetraMeshSnapshot* tetraMesh() const override; + + //===================== Data members ==================== + +private: + // an extra pointer to our snapshot used to implement TetraMeshInterface (ownership is passed to base class) + TetraMeshSnapshot* _tetraMeshSnapshot{nullptr}; +}; + +//////////////////////////////////////////////////////////////////// + +#endif diff --git a/SKIRT/core/TetraMeshMedium.cpp b/SKIRT/core/TetraMeshMedium.cpp new file mode 100644 index 00000000..716f8baa --- /dev/null +++ b/SKIRT/core/TetraMeshMedium.cpp @@ -0,0 +1,62 @@ +/*////////////////////////////////////////////////////////////////// +//// The SKIRT project -- advanced radiative transfer //// +//// © Astronomical Observatory, Ghent University //// +///////////////////////////////////////////////////////////////// */ + +#include "TetraMeshMedium.hpp" +#include "Configuration.hpp" +#include "TetraMeshSnapshot.hpp" + +//////////////////////////////////////////////////////////////////// + +Snapshot* TetraMeshMedium::createAndOpenSnapshot() +{ + // create and open the snapshot + _tetraMeshSnapshot = new TetraMeshSnapshot; + _tetraMeshSnapshot->open(this, filename(), "Tetra sites"); + + // honor custom column reordering + _tetraMeshSnapshot->useColumns(useColumns()); + + // configure the position columns + _tetraMeshSnapshot->importPosition(); + + // configure the density and/or mass column(s) + bool bothDensityAndMass = false; + switch (massType()) + { + case MassType::MassDensity: _tetraMeshSnapshot->importMassDensity(); break; + case MassType::Mass: _tetraMeshSnapshot->importMass(); break; + case MassType::MassDensityAndMass: + _tetraMeshSnapshot->importMassDensity(); + _tetraMeshSnapshot->importMass(); + bothDensityAndMass = true; + break; + + case MassType::NumberDensity: _tetraMeshSnapshot->importNumberDensity(); break; + case MassType::Number: _tetraMeshSnapshot->importNumber(); break; + case MassType::NumberDensityAndNumber: + _tetraMeshSnapshot->importNumberDensity(); + _tetraMeshSnapshot->importNumber(); + bothDensityAndMass = true; + break; + } + + // determine whether to forego the Tetra mesh + // auto config = find(); + // if (bothDensityAndMass && !config->mediaNeedGeneratePosition() && !config->snapshotsNeedGetEntities()) + // _tetraMeshSnapshot->foregoTetraMesh(); + + // set the domain extent + _tetraMeshSnapshot->setExtent(domain()); + return _tetraMeshSnapshot; +} + +//////////////////////////////////////////////////////////////////// + +TetraMeshSnapshot* TetraMeshMedium::tetraMesh() const +{ + return _tetraMeshSnapshot; +} + +//////////////////////////////////////////////////////////////////// diff --git a/SKIRT/core/TetraMeshMedium.hpp b/SKIRT/core/TetraMeshMedium.hpp new file mode 100644 index 00000000..52cdbc2e --- /dev/null +++ b/SKIRT/core/TetraMeshMedium.hpp @@ -0,0 +1,120 @@ +/*////////////////////////////////////////////////////////////////// +//// The SKIRT project -- advanced radiative transfer //// +//// © Astronomical Observatory, Ghent University //// +///////////////////////////////////////////////////////////////// */ + +#ifndef TETRAMESHMEDIUM_HPP +#define TETRAMESHMEDIUM_HPP + +#include "MeshMedium.hpp" +#include "TetraMeshInterface.hpp" + +//////////////////////////////////////////////////////////////////// + +/** A TetraMeshMedium instance represents a transfer medium with a spatial density distribution + described by a list of sites generating a Tetra tesselation of a cubodail domain. The data is + usually extracted from a cosmological simulation snapshot, and it must be provided in a column + text file formatted as described below. + + Refer to the description of the TextInFile class for information on overall formatting and on + how to include header lines specifying the units for each column in the input file. In case the + input file has no unit specifications, the default units mentioned below are used instead. The + number of columns expected in the input file depends on the options configured by the user for + this TetraMeshMedium instance: + + \f[ x\,(\mathrm{pc}) \quad y\,(\mathrm{pc}) \quad z\,(\mathrm{pc}) \quad \{\, + \rho\,(\text{M}_\odot\,\text{pc}^{-3}) \;\;|\;\; M\,(\text{M}_\odot) \;\;|\;\; + n\,(\text{cm}^{-3}) \;\;|\;\; N\,(1) \,\} \quad [Z\,(1)] \quad [T\,(\mathrm{K})] \quad [ + v_x\,(\mathrm{km/s}) \quad v_y\,(\mathrm{km/s}) \quad v_z\,(\mathrm{km/s}) ] \quad [ + B_x\,(\mu\mathrm{G}) \quad B_y\,(\mu\mathrm{G}) \quad B_z\,(\mu\mathrm{G}) ] \quad [ + \dots\text{mix family params}\dots ] \f] + + The first three columns are the \f$x\f$, \f$y\f$ and \f$z\f$ coordinates of the Tetra site + (i.e. the location defining a particular Tetra cell). + + Depending on the value of the \em massType option, the fourth column lists the average mass + density \f$\rho\f$, the integrated mass \f$M\f$, the average number density \f$n\f$, or the + integrated number density \f$N\f$ for the cell corresponding to the site. This quantity is + multiplied by the value of the \em massFraction option. + + If the \em importMetallicity option is enabled, the next column specifies a "metallicity" + fraction, which is multiplied with the mass/density column to obtain the actual mass/density of + the cell corresponding to the site. If the \em importTemperature option is enabled, the next + column specifies a temperature. If this temperature is higher than the value of the \em + maxTemperature option, the mass and density for the site are set to zero, regardless of the + mass or density specified in the fourth column. If the \em importTemperature option is + disabled, or the maximum temperature value is set to zero, such a cutoff is not applied. + + If both the \em importMetallicity and \em importTemperature options are enabled, this leads to + the following expression for the density of an imported site (or a simular formula for the + other mass quantity types): + + \f[ \rho_\mathrm{imported} = \begin{cases} f_\mathrm{mass}\,Z\,\rho & \mathrm{if}\; + TAvoiding construction of the Tetra tessellation + + The algorithm used by the TetraMeshSnapshot class for constructing Tetra tessellations + sometimes fails (for example, when generating sites are too close to each other). In those + cases, it can be desirable to forego the construction of the Tetra tessellation and instead + use a nearest neighbor search for sampling the density distribution. This is possible as long + as the full tessellation is not needed for other purposes, such as to perform radiative + transfer or to generate random positions drawn from the density distribution. An important use + case is when the medium density distribution defined on the Tetra grid is resampled to an + octree grid to perform radiative transfer. However, the octree subdivision algorithm requires + the total mass of the medium in addition to the density samples. Without the full Tetra + tessellation, it is impossible to calculate the cell volume that would allow conversion between + cell density and mass. + + To enable this use case, the \em massType option can be set to include both mass density and + volume-integrated mass (or both number density and volume-integrated number) in the imported + data file. Thus, if the \em massType option is set to one of these choices, the import file + must include two columns (mass density \f$\rho\f$ plus integrated mass \f$M\f$, or number + density \f$n\f$ plus integrated number density \f$N\f$ ). Furthermore, if the simulation + configuration allows it (i.e. the full Tetra tessellation is not needed for other purposes), + the TetraMeshSnapshot class will use the information in these two columns to calculate the + cell volumes and the total medium mass, and it will forego construction of the Tetra + tessellation. */ +class TetraMeshMedium : public MeshMedium, public TetraMeshInterface +{ + ITEM_CONCRETE(TetraMeshMedium, MeshMedium, "a transfer medium imported from data represented on a Tetra mesh") + ATTRIBUTE_TYPE_INSERT(TetraMeshMedium, "TetraMeshInterface") + ITEM_END() + + //============= Construction - Setup - Destruction ============= + +protected: + /** This function constructs a new TetraMeshSnapshot object, calls its open() function, + passes it the domain extent configured by the user, configures it to import a mass or a + density column, and finally returns a pointer to the object. Ownership of the Snapshot + object is transferred to the caller. */ + Snapshot* createAndOpenSnapshot() override; + + //=================== Other functions ================== + +protected: + /** This function implements the TetraMeshInterface interface. It returns a pointer to the + Tetra mesh snapshot maintained by this geometry. */ + TetraMeshSnapshot* tetraMesh() const override; + + //===================== Data members ==================== + +private: + // an extra pointer to our snapshot used to implement TetraMeshInterface (ownership is passed to base class) + TetraMeshSnapshot* _tetraMeshSnapshot{nullptr}; +}; + +//////////////////////////////////////////////////////////////////// + +#endif diff --git a/SKIRT/core/TetraMeshSnapshot.cpp b/SKIRT/core/TetraMeshSnapshot.cpp index c8f1bef0..05806a5a 100644 --- a/SKIRT/core/TetraMeshSnapshot.cpp +++ b/SKIRT/core/TetraMeshSnapshot.cpp @@ -24,8 +24,10 @@ #include "Units.hpp" #include "tetgen.h" #include +#include #include #include +#include #include #include "container.hh" @@ -33,7 +35,7 @@ namespace { - // classes used for serializing/deserializing Voronoi cell geometry when communicating the results of + // classes used for serializing/deserializing Tetra cell geometry when communicating the results of // grid construction between multiple processes with the ProcessManager::broadcastAllToAll() function // decorates a std::vector with functions to write serialized versions of various data types @@ -120,21 +122,6 @@ namespace } } - void addFacet(tetgenio::facet* f, std::array vertices) - { - f->numberofpolygons = 1; - f->polygonlist = new tetgenio::polygon[f->numberofpolygons]; - f->numberofholes = 0; - f->holelist = NULL; - tetgenio::polygon* p = &f->polygonlist[0]; - p->numberofvertices = 4; - p->vertexlist = new int[p->numberofvertices]; - p->vertexlist[0] = vertices[0]; - p->vertexlist[1] = vertices[1]; - p->vertexlist[2] = vertices[2]; - p->vertexlist[3] = vertices[3]; - } - int findEnteringFace(const Tetra* tetra, const Vec& pos, const Direction& dir) { int enteringFace = -1; @@ -305,8 +292,8 @@ void TetraMeshSnapshot::readAndClose() // // close the file // Snapshot::readAndClose(); - // // if we are allowed to build a Voronoi mesh - // // calculate the Voronoi cells + // // if we are allowed to build a Tetra mesh + // // calculate the Tetra cells // buildMesh(?, false); // // if a mass density policy has been set, calculate masses and densities and build the search data structure @@ -322,50 +309,63 @@ void TetraMeshSnapshot::setExtent(const Box& extent) _eps = 1e-12 * extent.widths().norm(); } +TetraMeshSnapshot::TetraMeshSnapshot(const SimulationItem* item, const Box& extent, string filename, double mindihedral) +{ + // read the input file + TextInFile in(item, filename, "Tetra vertices"); + in.addColumn("position x", "length", "pc"); + in.addColumn("position y", "length", "pc"); + in.addColumn("position z", "length", "pc"); + Array coords; + while (in.readRow(coords)) _sites.push_back(Vec(coords[0], coords[1], coords[2])); + in.close(); + + // calculate the Tetra cells + setContext(item); + setExtent(extent); + buildMesh(mindihedral); + buildSearchPerBlock(); +} + //////////////////////////////////////////////////////////////////// -// TetraMeshSnapshot::TetraMeshSnapshot(const SimulationItem* item, const Box& extent, string filename, bool relax) -// { -// // read the input file -// TextInFile in(item, filename, "Tetra vertices"); -// in.addColumn("position x", "length", "pc"); -// in.addColumn("position y", "length", "pc"); -// in.addColumn("position z", "length", "pc"); -// Array coords; -// while (in.readRow(coords)) _cells.push_back(new Cell(Vec(coords[0], coords[1], coords[2]))); -// in.close(); - -// // calculate the Voronoi cells -// setContext(item); -// setExtent(extent); -// buildMesh(relax); -// buildSearchPerBlock(); -// } +TetraMeshSnapshot::TetraMeshSnapshot(const SimulationItem* item, const Box& extent, SiteListInterface* sli, + double mindihedral) +{ + // prepare the data + int n = sli->numSites(); + _sites.resize(n); + for (int m = 0; m != n; ++m) _sites[m] = sli->sitePosition(m); + + // calculate the Tetra cells + setContext(item); + setExtent(extent); + buildMesh(mindihedral); + buildSearchPerBlock(); +} //////////////////////////////////////////////////////////////////// -// TetraMeshSnapshot::TetraMeshSnapshot(const SimulationItem* item, const Box& extent, SiteListInterface* sli, -// bool relax) -// { -// // prepare the data -// int n = sli->numSites(); -// _cells.resize(n); -// for (int m = 0; m != n; ++m) _cells[m] = new Cell(sli->sitePosition(m)); - -// // calculate the Voronoi cells -// setContext(item); -// setExtent(extent); -// buildMesh(relax); -// buildSearchPerBlock(); -// } +TetraMeshSnapshot::TetraMeshSnapshot(const SimulationItem* item, const Box& extent, const vector& sites, + double mindihedral) +{ + // store input vertices + _sites = sites; + + // calculate the Tetra cells + setContext(item); + setExtent(extent); + buildMesh(mindihedral); + buildSearchPerBlock(); +} //////////////////////////////////////////////////////////////////// -TetraMeshSnapshot::TetraMeshSnapshot(const TetraMeshSpatialGrid* grid, const Box& extent) +TetraMeshSnapshot::TetraMeshSnapshot(const SimulationItem* item, const Box& extent, double mindihedral) { - setContext(grid); + setContext(item); setExtent(extent); - buildMesh(grid, true); + buildMesh(mindihedral); buildSearchPerBlock(); } @@ -400,85 +400,33 @@ namespace //////////////////////////////////////////////////////////////////// -void TetraMeshSnapshot::buildMesh(const TetraMeshSpatialGrid* grid, bool plc) +void TetraMeshSnapshot::buildMesh(double mindihedral) { - tetgenio in, out; - tetgenbehavior behavior; - in.firstnumber = 0; - - if (plc) - { - in.numberofpoints = 8; - in.pointlist = new REAL[in.numberofpoints * 3]; - - // bottom half (zmin) - in.pointlist[0] = _extent.xmin(); - in.pointlist[1] = _extent.ymin(); - in.pointlist[2] = _extent.zmin(); - - in.pointlist[3] = _extent.xmax(); - in.pointlist[4] = _extent.ymin(); - in.pointlist[5] = _extent.zmin(); - - in.pointlist[6] = _extent.xmax(); - in.pointlist[7] = _extent.ymax(); - in.pointlist[8] = _extent.zmin(); - - in.pointlist[9] = _extent.xmin(); - in.pointlist[10] = _extent.ymax(); - in.pointlist[11] = _extent.zmin(); - - // top half (zmax) - for (int i = 0; i < 4; i++) - { - // x, y the same but z = zmax - in.pointlist[12 + i * 3 + 0] = in.pointlist[i * 3 + 0]; - in.pointlist[12 + i * 3 + 1] = in.pointlist[i * 3 + 1]; - in.pointlist[12 + i * 3 + 2] = _extent.zmax(); - } - - in.numberoffacets = 6; - in.facetlist = new tetgenio::facet[in.numberoffacets]; - addFacet(&in.facetlist[0], {0, 1, 2, 3}); // Facet 1. bottom - addFacet(&in.facetlist[1], {4, 5, 6, 7}); // Facet 2. top - addFacet(&in.facetlist[2], {0, 4, 5, 1}); // Facet 3. front - addFacet(&in.facetlist[3], {1, 5, 6, 2}); // Facet 4. right - addFacet(&in.facetlist[4], {2, 6, 7, 3}); // Facet 5. back - addFacet(&in.facetlist[5], {3, 7, 4, 0}); // Facet 6. left - - behavior.plc = 1; // -p PLC - behavior.quality = 1; // -q quality mesh - behavior.neighout = 2; // -nn neighbors and edges - behavior.facesout = 1; // -f faces - behavior.zeroindex = 1; // -z zero index - behavior.fixedvolume = 1; // -a max volume - behavior.maxvolume = grid->maxVolumeFraction() * _extent.volume(); // -a max volume - // refining criterion - in.tetunsuitable = [grid](double* pa, double* pb, double* pc, double* pd, double vol) { - return grid->tetUnsuitable(pa, pb, pc, pd, vol); - }; - } - // Delaunay Triangulation - else + tetgenio in, out_Delaunay, out; + tetgenbehavior behavior_Delaunay, behavior; + + // in.firstnumber = 0; // remove me if no error + in.numberofpoints = _sites.size(); + in.pointlist = new REAL[in.numberofpoints * 3]; + for (int i = 0; i < in.numberofpoints; i++) { - // WIP import from file!!! - // in.numberofpoints = 50; - // in.pointlist = new REAL[in.numberofpoints * 3]; - // for (int i = 0; i < in.numberofpoints; i++) - // { - // Vec pos = random()->position(_extent); - // in.pointlist[i * 3 + 0] = pos.x(); - // in.pointlist[i * 3 + 1] = pos.y(); - // in.pointlist[i * 3 + 2] = pos.z(); - // } - - behavior.psc = 1; // -s PSC - behavior.neighout = 2; // -nn neighbors and edges - behavior.facesout = 1; // -f faces - behavior.zeroindex = 1; // -z zero index + in.pointlist[i * 3 + 0] = _sites[i].x(); + in.pointlist[i * 3 + 1] = _sites[i].y(); + in.pointlist[i * 3 + 2] = _sites[i].z(); } - tetrahedralize(&behavior, &in, &out); + behavior_Delaunay.psc = 1; // -s PSC + behavior_Delaunay.verbose = 1; + tetrahedralize(&behavior_Delaunay, &in, &out_Delaunay); + + behavior.refine = 1; // -r + behavior.quality = 1; // -q + behavior.mindihedral = mindihedral; // -q + behavior.neighout = 2; // -nn + behavior.facesout = 1; // -f + behavior.zeroindex = 1; // -z + behavior.verbose = 1; // -v + tetrahedralize(&behavior, &out_Delaunay, &out); // tranfser TetGen data to TetraMeshSnapshot data containers numTetra = out.numberoftetrahedra; @@ -533,6 +481,44 @@ void TetraMeshSnapshot::buildMesh(const TetraMeshSpatialGrid* grid, bool plc) _centroids.push_back(&_tetrahedra[i]->_centroid); } + string filename = "tet.txt"; + std::ofstream outFile(filename); + + if (!outFile.is_open()) + { + std::cerr << "Failed to open file for writing: " << filename << std::endl; + return; + } + + std::set> uniqueEdges; // To avoid duplicates + + for (int i = 0; i < numTetra; i++) + { + for (int v1 = 0; v1 < 4; v1++) + { + for (int v2 = v1 + 1; v2 < 4; v2++) + { + int idx1 = out.tetrahedronlist[4 * i + v1]; + int idx2 = out.tetrahedronlist[4 * i + v2]; + + // Store edges in sorted order + if (idx1 > idx2) std::swap(idx1, idx2); + + uniqueEdges.emplace(idx1, idx2); + } + } + } + + for (const auto& edge : uniqueEdges) + { + outFile << out.pointlist[3 * edge.first + 0] << " " << out.pointlist[3 * edge.first + 1] << " " + << out.pointlist[3 * edge.first + 2] << " " << out.pointlist[3 * edge.second + 0] << " " + << out.pointlist[3 * edge.second + 1] << " " << out.pointlist[3 * edge.second + 2] << "\n"; + } + + outFile.close(); + std::cout << "Edges saved to " << filename << std::endl; + // compile statistics double minVol = DBL_MAX; double maxVol = 0.; @@ -801,8 +787,7 @@ void TetraMeshSnapshot::writeGridPlotFiles(const SimulationItem* probe) const if (tetra->zmin() <= 0 && tetra->zmax() >= 0) plotxy.writePolyhedron(coords, indices); if (tetra->ymin() <= 0 && tetra->ymax() >= 0) plotxz.writePolyhedron(coords, indices); if (tetra->xmin() <= 0 && tetra->xmax() >= 0) plotyz.writePolyhedron(coords, indices); - if (i <= 1000) - plotxyz.writePolyhedron(coords, indices); // like VoronoiMeshSnapshot, but why even write at all? + if (i <= 1000) plotxyz.writePolyhedron(coords, indices); // like TetraMeshSnapshot, but why even write at all? // log message if the minimum time has elapsed numDone++; @@ -1010,7 +995,7 @@ int TetraMeshSnapshot::cellIndex(Position bfr) const if (_tetrahedra[t]->inside(bfr)) return t; } - log()->error("cellIndex failed to find the tetrahedron"); // change to warning? + // log()->error("cellIndex failed to find the tetrahedron"); // change to warning? return -1; } diff --git a/SKIRT/core/TetraMeshSnapshot.hpp b/SKIRT/core/TetraMeshSnapshot.hpp index cc20d3e3..1bb790f1 100644 --- a/SKIRT/core/TetraMeshSnapshot.hpp +++ b/SKIRT/core/TetraMeshSnapshot.hpp @@ -63,11 +63,13 @@ class TetraMeshSnapshot : public Snapshot //========== Specialty constructors ========== public: - // TetraMeshSnapshot(const SimulationItem* item, const Box& extent, string filename); + TetraMeshSnapshot(const SimulationItem* item, const Box& extent, string filename, double mindihedral); - // TetraMeshSnapshot(const SimulationItem* item, const Box& extent, SiteListInterface* sli); + TetraMeshSnapshot(const SimulationItem* item, const Box& extent, SiteListInterface* sli, double mindihedral); - TetraMeshSnapshot(const TetraMeshSpatialGrid* grid, const Box& extent); + TetraMeshSnapshot(const SimulationItem* item, const Box& extent, const vector& sites, double mindihedral); + + TetraMeshSnapshot(const SimulationItem* item, const Box& extent, double mindihedral); //=========== Private construction ========== @@ -79,8 +81,8 @@ class TetraMeshSnapshot : public Snapshot corresponding cell information, including any properties relevant for supporting the interrogation capabilities offered by this class. All other data (such as Tetra cell vertices, edges and faces) are discarded. In practice, the function adds the sites to a - Voro++ container, computes the Tetra cells one by one, and copies the relevant cell - information (such as the list of neighboring cells) from the Voro++ data structures into + Tetra++ container, computes the Tetra cells one by one, and copies the relevant cell + information (such as the list of neighboring cells) from the Tetra++ data structures into its own. If the \em relax argument is true, the function performs a single relaxation step on the @@ -91,7 +93,7 @@ class TetraMeshSnapshot : public Snapshot constructed with these adjusted site positions, which are distributed more uniformly, thereby avoiding overly elongated cells in the Tetra tessellation. Relaxation can be quite time-consuming because the Tetra tessellation must be constructed twice. */ - void buildMesh(const TetraMeshSpatialGrid* grid, bool plc); + void buildMesh(double mindihedral); /** This private function calculates the volumes for all cells without using the Tetra mesh. It assumes that both mass and mass density columns are being imported. */ @@ -108,13 +110,13 @@ class TetraMeshSnapshot : public Snapshot Node* buildTree(vector::iterator first, vector::iterator last, int depth) const; /** This private function builds data structures that allow accelerating the operation of the - cellIndex() function. It assumes that the Voronoi mesh has already been built. + cellIndex() function. It assumes that the Tetra mesh has already been built. The domain is partitioned using a linear cubodial grid into cells that are called \em - blocks. For each block, the function builds and stores a list of all Voronoi cells that + blocks. For each block, the function builds and stores a list of all Tetra cells that possibly overlap that block. Retrieving the list of cells possibly overlapping a given point in the domain then comes down to locating the block containing the point (which is - trivial since the grid is linear). The current implementation uses a Voronoi cell's + trivial since the grid is linear). The current implementation uses a Tetra cell's enclosing cuboid to test for intersection with a block. Performing a precise intersection test is \em really slow and the shortened block lists don't substantially accelerate the cellIndex() function. @@ -125,7 +127,7 @@ class TetraMeshSnapshot : public Snapshot void buildSearchPerBlock(); /** This private function builds a data structure that allows accelerating the operation of the - cellIndex() function without using the Voronoi mesh. The domain is not partitioned in + cellIndex() function without using the Tetra mesh. The domain is not partitioned in blocks. The function builds a single binary search tree on all cell sites (see for example en.wikipedia.org/wiki/Kd-tree). */ void buildSearchSingle(); @@ -330,6 +332,9 @@ class TetraMeshSnapshot : public Snapshot int numTetra; int numVertices; + // input vertices + vector _sites; + // data members initialized when processing snapshot input and further completed by BuildMesh() vector _tetrahedra; vector _vertices; diff --git a/SKIRT/core/TetraMeshSource.cpp b/SKIRT/core/TetraMeshSource.cpp new file mode 100644 index 00000000..25bd57d5 --- /dev/null +++ b/SKIRT/core/TetraMeshSource.cpp @@ -0,0 +1,28 @@ +/*////////////////////////////////////////////////////////////////// +//// The SKIRT project -- advanced radiative transfer //// +//// © Astronomical Observatory, Ghent University //// +///////////////////////////////////////////////////////////////// */ + +#include "TetraMeshSource.hpp" +#include "TetraMeshSnapshot.hpp" + +//////////////////////////////////////////////////////////////////// + +Snapshot* TetraMeshSource::createAndOpenSnapshot() +{ + // create and open the snapshot + auto snapshot = new TetraMeshSnapshot; + snapshot->open(this, filename(), "Tetra source sites"); + + // honor custom column reordering + snapshot->useColumns(useColumns()); + + // configure the position columns + snapshot->importPosition(); + + // set the domain extent + snapshot->setExtent(domain()); + return snapshot; +} + +//////////////////////////////////////////////////////////////////// diff --git a/SKIRT/core/TetraMeshSource.hpp b/SKIRT/core/TetraMeshSource.hpp new file mode 100644 index 00000000..f599c6b0 --- /dev/null +++ b/SKIRT/core/TetraMeshSource.hpp @@ -0,0 +1,58 @@ +/*////////////////////////////////////////////////////////////////// +//// The SKIRT project -- advanced radiative transfer //// +//// © Astronomical Observatory, Ghent University //// +///////////////////////////////////////////////////////////////// */ + +#ifndef TETRAMESHSOURCE_HPP +#define TETRAMESHSOURCE_HPP + +#include "MeshSource.hpp" + +//////////////////////////////////////////////////////////////////// + +/** A TetraMeshSource instance represents a primary radiation source with a spatial and spectral + luminosity distribution described by a list of sites generating a Tetra tesselation of a + cubodail domain. The data is usually extracted from a cosmological simulation snapshot, and it + must be provided in a column text file formatted as described below. + + Refer to the description of the TextInFile class for information on overall formatting and on + how to include header lines specifying the units for each column in the input file. In case the + input file has no unit specifications, the default units mentioned below are used instead. The + number of columns expected in the input file depends on the options configured by the user for + this TetraMeshSource instance, including the selected %SEDFamily. + + \f[ x\,(\mathrm{pc}) \quad y\,(\mathrm{pc}) \quad z\,(\mathrm{pc}) \quad [ v_x\,(\mathrm{km/s}) + \quad v_y\,(\mathrm{km/s}) \quad v_z\,(\mathrm{km/s}) \quad [ \sigma_v\,(\mathrm{km/s}) ] ] + \quad [M_\mathrm{curr}\,(\mathrm{M}_\odot)] \quad \dots \text{SED family parameters}\dots \f] + + The first three columns are the \f$x\f$, \f$y\f$ and \f$z\f$ coordinates of the Tetra site + (i.e. the location defining a particular Tetra cell). If the \em importVelocity option is + enabled, the next three columns specify the \f$v_x\f$, \f$v_y\f$, \f$v_z\f$ bulk velocity + components of the source population represented by the cell corresponding to the site. If + additionally the \em importVelocityDispersion option is enabled, the next column specifies the + velocity dispersion \f$\sigma_v\f$, adjusting the velocity for each photon packet launch with a + random offset sampled from a spherically symmetric Gaussian distribution. + + The remaining columns specify the parameters required by the configured %SED family to select + and scale the appropriate %SED. For example for the Bruzual-Charlot %SED family, the remaining + columns provide the initial mass, the metallicity, and the age of the stellar population + represented by the cell corresponding to the site. Refer to the documentation of the configured + type of SEDFamily for information about the expected parameters and their default units. */ +class TetraMeshSource : public MeshSource +{ + ITEM_CONCRETE(TetraMeshSource, MeshSource, "a primary source imported from data represented on a Tetra mesh") + ITEM_END() + + //============= Construction - Setup - Destruction ============= + +protected: + /** This function constructs a new TetraMeshSnapshot object, calls its open() function, + passes it the domain extent configured by the user (using properties offered by the + MeshSource base class), and returns a pointer to the object. Ownership of the Snapshot + object is transferred to the caller. */ + Snapshot* createAndOpenSnapshot() override; +}; + +//////////////////////////////////////////////////////////////////// + +#endif diff --git a/SKIRT/core/TetraMeshSpatialGrid.cpp b/SKIRT/core/TetraMeshSpatialGrid.cpp index 3fbff61a..6c5aa5e4 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.cpp +++ b/SKIRT/core/TetraMeshSpatialGrid.cpp @@ -52,154 +52,96 @@ void TetraMeshSpatialGrid::setupSelfBefore() { BoxSpatialGrid::setupSelfBefore(); - _random = find(); - _numSamples = find()->numDensitySamples(); - - const MediumSystem* ms = find(); - if (!ms) throw FATALERROR("MediumSystem not found"); - - for (Medium* medium : ms->media()) - { - medium->setup(); - switch (medium->mix()->materialType()) - { - case MaterialMix::MaterialType::Dust: _dustMedia.push_back(medium); break; - case MaterialMix::MaterialType::Electrons: _electronMedia.push_back(medium); break; - case MaterialMix::MaterialType::Gas: _gasMedia.push_back(medium); break; - } - } - - if (!_dustMedia.empty()) + // determine an appropriate set of sites and construct the Tetra mesh + switch (_policy) { - if (maxDustFraction() > 0) _hasAny = _hasDustAny = _hasDustFraction = true; - if (maxDustOpticalDepth() > 0) _hasAny = _hasDustAny = _hasDustOpticalDepth = true; - if (maxDustDensityDispersion() > 0) _hasAny = _hasDustAny = _hasDustDensityDispersion = true; - if (_hasDustFraction) + case Policy::Uniform: { - for (auto medium : _dustMedia) _dustMass += medium->mass(); + auto random = find(); + vector rv(_numSites); + for (int m = 0; m != _numSites; ++m) rv[m] = random->position(extent()); + _mesh = new TetraMeshSnapshot(this, extent(), rv, _mindihedral); + break; } - if (_hasDustOpticalDepth) + case Policy::CentralPeak: { - double sigma = 0.; - double mu = 0.; - for (auto medium : _dustMedia) + auto random = find(); + const int a = 1000; // steepness of the peak; the central 1/a portion is NOT covered + const double rscale = extent().rmax().norm(); + vector rv(_numSites); + for (int m = 1; m != _numSites;) // skip first particle so that it remains (0,0,0) { - sigma += medium->mix()->sectionExt(wavelength()); - mu += medium->mix()->mass(); + double r = rscale * pow(1. / a, random->uniform()); // random distribution according to 1/x + Direction k = random->direction(); + Position p = Position(r, k); + if (extent().contains(p)) rv[m++] = p; // discard any points outside of the domain } - _dustKappa = sigma / mu; + _mesh = new TetraMeshSnapshot(this, extent(), rv, _mindihedral); + break; } - } - - // precalculate information for electrons - if (!_electronMedia.empty() && maxElectronFraction() > 0) - { - _hasAny = _hasElectronFraction = true; - for (auto medium : _electronMedia) _electronNumber += medium->number(); - } - - // precalculate information for gas - if (!_gasMedia.empty() && maxGasFraction() > 0) - { - _hasAny = _hasGasFraction = true; - for (auto medium : _gasMedia) _gasNumber += medium->number(); - } - - // warn user if none of the criteria were enabled - if (!_hasAny) find()->warning("None of the tree subdivision criteria are enabled"); - - _mesh = new TetraMeshSnapshot(this, extent()); -} - -bool TetraMeshSpatialGrid::tetUnsuitable(double* pa, double* pb, double* pc, double* pd, double vol) const -{ - Vec a(pa[0], pa[1], pa[2]); - Vec b(pb[0], pb[1], pb[2]); - Vec c(pc[0], pc[1], pc[2]); - Vec d(pd[0], pd[1], pd[2]); - - // results for the sampled mass or number densities, if applicable - double rho = 0.; // dust mass density - // double rhovar = 0.; // dust mass variance - double rhomin = DBL_MAX; // smallest sample for dust mass density - double rhomax = 0.; // largest sample for dust mass density - double ne = 0; // electron number density - double ng = 0.; // gas number density - - // sample densities in node - if (_hasAny) - { - double rhosum = 0; - // double rhosum2 = 0; - double nesum = 0; - double ngsum = 0; - for (int i = 0; i != _numSamples; ++i) + case Policy::DustDensity: { - double s = random()->uniform(); - double t = random()->uniform(); - double u = random()->uniform(); - double r = Tetra::generateBarycentric(s, t, u); - Position bfr(r * a + u * b + t * c + s * d); - if (_hasDustAny) - { - double rhoi = 0.; - for (auto medium : _dustMedia) rhoi += medium->massDensity(bfr); - rhosum += rhoi; - // rhosum2 += rhoi * rhoi; - if (rhoi < rhomin) rhomin = rhoi; - if (rhoi > rhomax) rhomax = rhoi; - } - if (_hasElectronFraction) - for (auto medium : _electronMedia) nesum += medium->numberDensity(bfr); - if (_hasGasFraction) - for (auto medium : _gasMedia) ngsum += medium->numberDensity(bfr); + // build a list of media that have this material type with corresponding weights + vector media; + vector weights; + auto ms = find(); + for (auto medium : ms->media()) + if (medium->mix()->isDust()) media.push_back(medium); + for (auto medium : media) weights.push_back(medium->mass()); + _mesh = + new TetraMeshSnapshot(this, extent(), sampleMedia(media, weights, extent(), _numSites), _mindihedral); + break; } - rho = rhosum / _numSamples; - // rhovar = rhomax > 0 ? (rhosum2 / _numSamples - rho * rho) * (vol / _dustMass) * (vol / _dustMass) / (maxDustFraction() * maxDustFraction()) : 0.; - ne = nesum / _numSamples; - ng = ngsum / _numSamples; - } - - // handle maximum dust mass fraction - if (_hasDustFraction) - { - double delta = rho * vol / _dustMass; - if (delta > maxDustFraction()) return true; - if (delta < minDustFraction()) return false; - } - - // handle maximum dust optical depth - // if (_hasDustOpticalDepth) - // { - // double tau = _dustKappa * rho * node->diagonal(); - // if (tau > maxDustOpticalDepth()) return true; - // } - - // handle maximum dust density dispersion - if (_hasDustDensityDispersion) - { - // if (rhovar > maxDustDensityDispersion()) return true; - - double q = rhomax > 0 ? (rhomax - rhomin) / rhomax : 0.; - if (q > maxDustDensityDispersion()) return true; - } - - // handle maximum electron number fraction - if (_hasElectronFraction) - { - double delta = ne * vol / _electronNumber; - if (delta > maxElectronFraction()) return true; - } + case Policy::ElectronDensity: + { + // build a list of media that have this material type with corresponding weights + vector media; + vector weights; + auto ms = find(); + for (auto medium : ms->media()) + if (medium->mix()->isElectrons()) media.push_back(medium); + for (auto medium : media) weights.push_back(medium->number()); + _mesh = + new TetraMeshSnapshot(this, extent(), sampleMedia(media, weights, extent(), _numSites), _mindihedral); + break; + } + case Policy::GasDensity: + { + // build a list of media that have this material type with corresponding weights + vector media; + vector weights; + auto ms = find(); + for (auto medium : ms->media()) + if (medium->mix()->isGas()) media.push_back(medium); + for (auto medium : media) weights.push_back(medium->number()); + _mesh = + new TetraMeshSnapshot(this, extent(), sampleMedia(media, weights, extent(), _numSites), _mindihedral); + break; + } + case Policy::File: + { + _mesh = new TetraMeshSnapshot(this, extent(), _filename, _mindihedral); + break; + } + case Policy::ImportedSites: + { + auto sli = find()->interface(2); + _mesh = new TetraMeshSnapshot(this, extent(), sli, _mindihedral); + break; + } + case Policy::ImportedMesh: + { + auto ms = find(false); + _mesh = ms->interface(2)->tetraMesh(); - // handle maximum gas number fraction - if (_hasGasFraction) - { - double delta = ng * vol / _gasNumber; - if (delta > maxGasFraction()) return true; + // if there is a single medium component, calculate the normalization factor imposed by it; + // we need this to directly compute cell densities for the DensityInCellInterface + if (ms->media().size() == 1) _norm = _mesh->mass() > 0 ? ms->media()[0]->number() / _mesh->mass() : 0.; + break; + } } - // if we get here, none of the criteria were violated - return false; + _mesh = new TetraMeshSnapshot(this, extent(), _mindihedral); } ////////////////////////////////////////////////////////////////////// diff --git a/SKIRT/core/TetraMeshSpatialGrid.hpp b/SKIRT/core/TetraMeshSpatialGrid.hpp index 2520bc5c..d0d945c4 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.hpp +++ b/SKIRT/core/TetraMeshSpatialGrid.hpp @@ -26,57 +26,36 @@ class TetraMeshSnapshot; overly elongated cells. */ class TetraMeshSpatialGrid : public BoxSpatialGrid, public DensityInCellInterface { + /** The enumeration type indicating the policy for determining the positions of the sites. */ + ENUM_DEF(Policy, Uniform, CentralPeak, DustDensity, ElectronDensity, GasDensity, File, ImportedSites, ImportedMesh) + ENUM_VAL(Policy, Uniform, "random from uniform distribution") + ENUM_VAL(Policy, CentralPeak, "random from distribution with a steep central peak") + ENUM_VAL(Policy, DustDensity, "random from dust density distribution") + ENUM_VAL(Policy, ElectronDensity, "random from electron density distribution") + ENUM_VAL(Policy, GasDensity, "random from gas density distribution") + ENUM_VAL(Policy, File, "loaded from text column data file") + ENUM_VAL(Policy, ImportedSites, "positions of particles, sites or cells in imported distribution") + ENUM_VAL(Policy, ImportedMesh, "employ imported Tetra mesh in medium system") + ENUM_END() + ITEM_CONCRETE(TetraMeshSpatialGrid, BoxSpatialGrid, "a Tetra tessellation-based spatial grid") ATTRIBUTE_TYPE_DISPLAYED_IF(TetraMeshSpatialGrid, "Level2") - PROPERTY_DOUBLE(maxVolumeFraction, "the maximum volume fraction of each cell") - ATTRIBUTE_MIN_VALUE(maxVolumeFraction, "]0") - ATTRIBUTE_MAX_VALUE(maxVolumeFraction, "1[") - ATTRIBUTE_DEFAULT_VALUE(maxVolumeFraction, "1e-3") - ATTRIBUTE_DISPLAYED_IF(maxVolumeFraction, "DustMix") - - PROPERTY_DOUBLE(maxDustFraction, "the maximum fraction of dust contained in each cell") - ATTRIBUTE_MIN_VALUE(maxDustFraction, "[0") - ATTRIBUTE_MAX_VALUE(maxDustFraction, "1[") - ATTRIBUTE_DEFAULT_VALUE(maxDustFraction, "1e-6") - ATTRIBUTE_DISPLAYED_IF(maxDustFraction, "DustMix") - - PROPERTY_DOUBLE(minDustFraction, "the minimum fraction of dust contained in each cell") - ATTRIBUTE_MIN_VALUE(minDustFraction, "[0") - ATTRIBUTE_MAX_VALUE(minDustFraction, "1[") - ATTRIBUTE_DEFAULT_VALUE(minDustFraction, "1e-6") - ATTRIBUTE_DISPLAYED_IF(minDustFraction, "DustMix") - - PROPERTY_DOUBLE(maxDustOpticalDepth, "the maximum diagonal dust optical depth for each cell") - ATTRIBUTE_MIN_VALUE(maxDustOpticalDepth, "[0") - ATTRIBUTE_MAX_VALUE(maxDustOpticalDepth, "100]") - ATTRIBUTE_DEFAULT_VALUE(maxDustOpticalDepth, "0") - ATTRIBUTE_DISPLAYED_IF(maxDustOpticalDepth, "DustMix&Level2") - - PROPERTY_DOUBLE(wavelength, "the wavelength at which to evaluate the optical depth") - ATTRIBUTE_QUANTITY(wavelength, "wavelength") - ATTRIBUTE_MIN_VALUE(wavelength, "1 pm") - ATTRIBUTE_MAX_VALUE(wavelength, "1 m") - ATTRIBUTE_DEFAULT_VALUE(wavelength, "0.55 micron") - ATTRIBUTE_RELEVANT_IF(wavelength, "maxDustOpticalDepth") - - PROPERTY_DOUBLE(maxDustDensityDispersion, "the maximum dust density dispersion in each cell") - ATTRIBUTE_MIN_VALUE(maxDustDensityDispersion, "[0") - ATTRIBUTE_MAX_VALUE(maxDustDensityDispersion, "1000]") - ATTRIBUTE_DEFAULT_VALUE(maxDustDensityDispersion, "0") - ATTRIBUTE_DISPLAYED_IF(maxDustDensityDispersion, "DustMix&Level2") - - PROPERTY_DOUBLE(maxElectronFraction, "the maximum fraction of electrons contained in each cell") - ATTRIBUTE_MIN_VALUE(maxElectronFraction, "[0") - ATTRIBUTE_MAX_VALUE(maxElectronFraction, "1e-2]") - ATTRIBUTE_DEFAULT_VALUE(maxElectronFraction, "1e-6") - ATTRIBUTE_DISPLAYED_IF(maxElectronFraction, "ElectronMix") - - PROPERTY_DOUBLE(maxGasFraction, "the maximum fraction of gas contained in each cell") - ATTRIBUTE_MIN_VALUE(maxGasFraction, "[0") - ATTRIBUTE_MAX_VALUE(maxGasFraction, "1e-2]") - ATTRIBUTE_DEFAULT_VALUE(maxGasFraction, "1e-6") - ATTRIBUTE_DISPLAYED_IF(maxGasFraction, "GasMix") + PROPERTY_ENUM(policy, Policy, "the policy for determining the positions of the sites") + ATTRIBUTE_DEFAULT_VALUE(policy, "DustDensity") + + PROPERTY_INT(numSites, "the number of random sites (or cells in the grid)") + ATTRIBUTE_MIN_VALUE(numSites, "5") + ATTRIBUTE_DEFAULT_VALUE(numSites, "500") + ATTRIBUTE_RELEVANT_IF(numSites, "policyUniform|policyCentralPeak|policyDustDensity|" + "policyElectronDensity|policyGasDensity") + + PROPERTY_STRING(filename, "the name of the file containing the site positions") + ATTRIBUTE_RELEVANT_IF(filename, "policyFile") + + PROPERTY_DOUBLE(mindihedral, "the minimum dihedral angle per tetrahedron") + ATTRIBUTE_DEFAULT_VALUE(mindihedral, "3.5") + ATTRIBUTE_RELEVANT_IF(mindihedral, "!policyImportedMesh") ITEM_END() diff --git a/SKIRT/core/Tetrahedron.hpp b/SKIRT/core/Tetrahedron.hpp index 87b20b18..e1f786e6 100644 --- a/SKIRT/core/Tetrahedron.hpp +++ b/SKIRT/core/Tetrahedron.hpp @@ -1,6 +1,7 @@ #include "Position.hpp" #include "Array.hpp" #include "Box.hpp" +#include struct Face { diff --git a/SKIRT/tetgen/CMakeLists.txt b/SKIRT/tetgen/CMakeLists.txt index a4857542..fd8956e3 100644 --- a/SKIRT/tetgen/CMakeLists.txt +++ b/SKIRT/tetgen/CMakeLists.txt @@ -28,7 +28,7 @@ target_compile_definitions(tetgen PRIVATE -DTETLIBRARY) # set_target_properties(tetgen PROPERTIES POSITION_INDEPENDENT_CODE ON) # adjust C++ compiler flags to our needs -# set(NO_EXTRA_WARNINGS true) # to avoid warnings in the Voro++ code +set(NO_EXTRA_WARNINGS true) # to avoid warnings in the tetgen code (warnings still present) include("../../SMILE/build/CompilerFlags.cmake") diff --git a/SKIRT/tetgen/tetgen.h b/SKIRT/tetgen/tetgen.h index e5810eae..5a0a15ba 100644 --- a/SKIRT/tetgen/tetgen.h +++ b/SKIRT/tetgen/tetgen.h @@ -2560,7 +2560,7 @@ void tetrahedralize(char* switches, tetgenio* in, tetgenio* out, tetgenio* addin // // //============================================================================// -inline void terminatetetgen(tetgenmesh* m, int x) +inline void terminatetetgen(tetgenmesh* /*m*/, int x) { #ifdef TETLIBRARY throw x; From 6ff2c1e058ea827b4b424c7710a9a8a2aa63e2aa Mon Sep 17 00:00:00 2001 From: arlauwer Date: Fri, 15 Nov 2024 17:54:14 +0100 Subject: [PATCH 28/51] removed unnecessary code --- SKIRT/core/SimulationItemRegistry.cpp | 5 +- SKIRT/core/TetraMeshSnapshot.cpp | 178 +++++++------------------- SKIRT/core/TetraMeshSnapshot.hpp | 2 +- SKIRT/core/TetraMeshSpatialGrid.cpp | 2 - 4 files changed, 48 insertions(+), 139 deletions(-) diff --git a/SKIRT/core/SimulationItemRegistry.cpp b/SKIRT/core/SimulationItemRegistry.cpp index 04545724..a916051e 100755 --- a/SKIRT/core/SimulationItemRegistry.cpp +++ b/SKIRT/core/SimulationItemRegistry.cpp @@ -320,6 +320,7 @@ SimulationItemRegistry::SimulationItemRegistry(string version, string format) ItemRegistry::add(); ItemRegistry::add(); ItemRegistry::add(); + ItemRegistry::add(); ItemRegistry::add(); ItemRegistry::add(); ItemRegistry::add(); @@ -441,7 +442,8 @@ SimulationItemRegistry::SimulationItemRegistry(string version, string format) ItemRegistry::add(); ItemRegistry::add(); ItemRegistry::add(); - ItemRegistry::add(); + ItemRegistry::add(); + ItemRegistry::add(); // geometry decorators ItemRegistry::add(); @@ -515,6 +517,7 @@ SimulationItemRegistry::SimulationItemRegistry(string version, string format) ItemRegistry::add(); ItemRegistry::add(); ItemRegistry::add(); + ItemRegistry::add(); // medium system options ItemRegistry::add(); diff --git a/SKIRT/core/TetraMeshSnapshot.cpp b/SKIRT/core/TetraMeshSnapshot.cpp index 05806a5a..d022d5a5 100644 --- a/SKIRT/core/TetraMeshSnapshot.cpp +++ b/SKIRT/core/TetraMeshSnapshot.cpp @@ -33,63 +33,6 @@ //////////////////////////////////////////////////////////////////// -namespace -{ - // classes used for serializing/deserializing Tetra cell geometry when communicating the results of - // grid construction between multiple processes with the ProcessManager::broadcastAllToAll() function - - // decorates a std::vector with functions to write serialized versions of various data types - class SerializedWrite - { - private: - vector& _data; - - public: - SerializedWrite(vector& data) : _data(data) { _data.clear(); } - void write(double v) { _data.push_back(v); } - void write(Vec v) { _data.insert(_data.end(), {v.x(), v.y(), v.z()}); } - void write(Box v) { _data.insert(_data.end(), {v.xmin(), v.ymin(), v.zmin(), v.xmax(), v.ymax(), v.zmax()}); } - void write(const vector& v) - { - _data.push_back(v.size()); - _data.insert(_data.end(), v.begin(), v.end()); - } - }; - - // decorates a std::vector with functions to read serialized versions of various data types - class SerializedRead - { - private: - const double* _data; - const double* _end; - - public: - SerializedRead(const vector& data) : _data(data.data()), _end(data.data() + data.size()) {} - bool empty() { return _data == _end; } - int readInt() { return *_data++; } - void read(double& v) { v = *_data++; } - void read(Vec& v) - { - v.set(*_data, *(_data + 1), *(_data + 2)); - _data += 3; - } - void read(Box& v) - { - v = Box(*_data, *(_data + 1), *(_data + 2), *(_data + 3), *(_data + 4), *(_data + 5)); - _data += 6; - } - void read(vector& v) - { - int n = *_data++; - v.clear(); - v.reserve(n); - for (int i = 0; i != n; ++i) v.push_back(*_data++); - } - }; -} - -//////////////////////////////////////////////////////////////////// - namespace { bool lessthan(Vec p1, Vec p2, int axis) @@ -282,23 +225,7 @@ TetraMeshSnapshot::~TetraMeshSnapshot() void TetraMeshSnapshot::readAndClose() { - // // read the site info into memory - // Array prop; - // while (infile()->readRow(prop)) - // { - // _vertices.push_back(new Vec(prop[0], prop[1], prop[2])); - // } - - // // close the file - // Snapshot::readAndClose(); - - // // if we are allowed to build a Tetra mesh - // // calculate the Tetra cells - // buildMesh(?, false); - - // // if a mass density policy has been set, calculate masses and densities and build the search data structure - // if (hasMassDensityPolicy()) calculateDensityAndMass(); - // if (hasMassDensityPolicy() || needGetEntities()) buildSearchPerBlock(); + // WIP } //////////////////////////////////////////////////////////////////// @@ -317,7 +244,7 @@ TetraMeshSnapshot::TetraMeshSnapshot(const SimulationItem* item, const Box& exte in.addColumn("position y", "length", "pc"); in.addColumn("position z", "length", "pc"); Array coords; - while (in.readRow(coords)) _sites.push_back(Vec(coords[0], coords[1], coords[2])); + while (in.readRow(coords)) _sites.push_back(new Vec(coords[0], coords[1], coords[2])); in.close(); // calculate the Tetra cells @@ -335,7 +262,7 @@ TetraMeshSnapshot::TetraMeshSnapshot(const SimulationItem* item, const Box& exte // prepare the data int n = sli->numSites(); _sites.resize(n); - for (int m = 0; m != n; ++m) _sites[m] = sli->sitePosition(m); + for (int m = 0; m != n; ++m) _sites[m] = new Vec(sli->sitePosition(m)); // calculate the Tetra cells setContext(item); @@ -349,8 +276,10 @@ TetraMeshSnapshot::TetraMeshSnapshot(const SimulationItem* item, const Box& exte TetraMeshSnapshot::TetraMeshSnapshot(const SimulationItem* item, const Box& extent, const vector& sites, double mindihedral) { - // store input vertices - _sites = sites; + // prepare the data + int n = sites.size(); + _sites.resize(n); + for (int m = 0; m != n; ++m) _sites[m] = new Vec(sites[m]); // calculate the Tetra cells setContext(item); @@ -373,12 +302,6 @@ TetraMeshSnapshot::TetraMeshSnapshot(const SimulationItem* item, const Box& exte namespace { - // maximum number of Tetra sites processed between two invocations of infoIfElapsed() - const int logProgressChunkSize = 1000; - - // maximum number of Tetra grid construction iterations - const int maxConstructionIterations = 5; - // function to erase null pointers from a vector of pointers in one go; returns the new size template size_t eraseNullPointers(vector& v) { @@ -402,32 +325,56 @@ namespace void TetraMeshSnapshot::buildMesh(double mindihedral) { + // remove sites outside of the domain + int numOutside = 0; + int numSites = _sites.size(); + for (int m = 0; m != numSites; ++m) + { + if (!_extent.contains(*_sites[m])) + { + delete _sites[m]; + _sites[m] = 0; + numOutside++; + } + } + if (numOutside) numSites = eraseNullPointers(_sites); + log()->info("removed " + StringUtils::toString(numOutside, 'd') + " vertices outside of the domain"); + tetgenio in, out_Delaunay, out; tetgenbehavior behavior_Delaunay, behavior; - + // in.firstnumber = 0; // remove me if no error in.numberofpoints = _sites.size(); in.pointlist = new REAL[in.numberofpoints * 3]; for (int i = 0; i < in.numberofpoints; i++) { - in.pointlist[i * 3 + 0] = _sites[i].x(); - in.pointlist[i * 3 + 1] = _sites[i].y(); - in.pointlist[i * 3 + 2] = _sites[i].z(); + in.pointlist[i * 3 + 0] = _sites[i]->x(); + in.pointlist[i * 3 + 1] = _sites[i]->y(); + in.pointlist[i * 3 + 2] = _sites[i]->z(); } - behavior_Delaunay.psc = 1; // -s PSC - behavior_Delaunay.verbose = 1; + log()->info("Building Delaunay triangulation using input vertices..."); + + behavior_Delaunay.psc = 1; // -s build Delaunay tetrahedralisation + // behavior_Delaunay.verbose = 1; // -v tetrahedralize(&behavior_Delaunay, &in, &out_Delaunay); + log()->info("Built Delaunay triangulation"); + log()->info("Refining triangulation..."); + + // tetgen refine options behavior.refine = 1; // -r behavior.quality = 1; // -q behavior.mindihedral = mindihedral; // -q - behavior.neighout = 2; // -nn - behavior.facesout = 1; // -f - behavior.zeroindex = 1; // -z - behavior.verbose = 1; // -v + // correct output options for out + behavior.neighout = 2; // -nn + behavior.facesout = 1; // -f + behavior.zeroindex = 1; // -z + behavior.verbose = 1; // -v tetrahedralize(&behavior, &out_Delaunay, &out); + log()->info("Refined triangulation"); + // tranfser TetGen data to TetraMeshSnapshot data containers numTetra = out.numberoftetrahedra; numVertices = out.numberofpoints; @@ -481,44 +428,6 @@ void TetraMeshSnapshot::buildMesh(double mindihedral) _centroids.push_back(&_tetrahedra[i]->_centroid); } - string filename = "tet.txt"; - std::ofstream outFile(filename); - - if (!outFile.is_open()) - { - std::cerr << "Failed to open file for writing: " << filename << std::endl; - return; - } - - std::set> uniqueEdges; // To avoid duplicates - - for (int i = 0; i < numTetra; i++) - { - for (int v1 = 0; v1 < 4; v1++) - { - for (int v2 = v1 + 1; v2 < 4; v2++) - { - int idx1 = out.tetrahedronlist[4 * i + v1]; - int idx2 = out.tetrahedronlist[4 * i + v2]; - - // Store edges in sorted order - if (idx1 > idx2) std::swap(idx1, idx2); - - uniqueEdges.emplace(idx1, idx2); - } - } - } - - for (const auto& edge : uniqueEdges) - { - outFile << out.pointlist[3 * edge.first + 0] << " " << out.pointlist[3 * edge.first + 1] << " " - << out.pointlist[3 * edge.first + 2] << " " << out.pointlist[3 * edge.second + 0] << " " - << out.pointlist[3 * edge.second + 1] << " " << out.pointlist[3 * edge.second + 2] << "\n"; - } - - outFile.close(); - std::cout << "Edges saved to " << filename << std::endl; - // compile statistics double minVol = DBL_MAX; double maxVol = 0.; @@ -636,11 +545,10 @@ void TetraMeshSnapshot::buildSearchPerBlock() // abort if there are no cells if (!numTetra) return; - log()->info("Building data structures to accelerate searching the tetrahedralization"); + log()->info("Building data structures to accelerate searching the tetrahedralisation"); // ------------- block lists ------------- _nb = max(3, min(250, static_cast(cbrt(numTetra)))); - // _nb = 1; _nb2 = _nb * _nb; _nb3 = _nb * _nb * _nb; @@ -758,7 +666,7 @@ void TetraMeshSnapshot::writeGridPlotFiles(const SimulationItem* probe) const SpatialGridPlotFile plotxyz(probe, probe->itemName() + "_grid_xyz"); // for each site, compute the corresponding cell and output its edges - log()->info("Writing plot files for tetrahedralization with " + std::to_string(numTetra) + " tetrahedra"); + log()->info("Writing plot files for tetrahedralisation with " + std::to_string(numTetra) + " tetrahedra"); log()->infoSetElapsed(numTetra); int numDone = 0; for (int i = 0; i < numTetra; i++) diff --git a/SKIRT/core/TetraMeshSnapshot.hpp b/SKIRT/core/TetraMeshSnapshot.hpp index 1bb790f1..8f5cf27f 100644 --- a/SKIRT/core/TetraMeshSnapshot.hpp +++ b/SKIRT/core/TetraMeshSnapshot.hpp @@ -333,7 +333,7 @@ class TetraMeshSnapshot : public Snapshot int numVertices; // input vertices - vector _sites; + vector _sites; // data members initialized when processing snapshot input and further completed by BuildMesh() vector _tetrahedra; diff --git a/SKIRT/core/TetraMeshSpatialGrid.cpp b/SKIRT/core/TetraMeshSpatialGrid.cpp index 6c5aa5e4..2ca522d5 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.cpp +++ b/SKIRT/core/TetraMeshSpatialGrid.cpp @@ -140,8 +140,6 @@ void TetraMeshSpatialGrid::setupSelfBefore() break; } } - - _mesh = new TetraMeshSnapshot(this, extent(), _mindihedral); } ////////////////////////////////////////////////////////////////////// From fa0743b32aac19cf3f8fcc8ce4069f9d4d4b40fa Mon Sep 17 00:00:00 2001 From: arlauwer Date: Sat, 16 Nov 2024 11:16:52 +0100 Subject: [PATCH 29/51] added refine option to Tetra grid marked potential oversight in Voro `TetraMeshSnapshot::readAndClose` still needs to infer cell properties from vertex properties --- SKIRT/core/TetraMeshSnapshot.cpp | 92 +++++++++++++++++++---------- SKIRT/core/TetraMeshSnapshot.hpp | 21 ++++--- SKIRT/core/TetraMeshSpatialGrid.hpp | 7 ++- SKIRT/core/VoronoiMeshSnapshot.cpp | 2 +- 4 files changed, 80 insertions(+), 42 deletions(-) diff --git a/SKIRT/core/TetraMeshSnapshot.cpp b/SKIRT/core/TetraMeshSnapshot.cpp index d022d5a5..1f2d60f3 100644 --- a/SKIRT/core/TetraMeshSnapshot.cpp +++ b/SKIRT/core/TetraMeshSnapshot.cpp @@ -225,7 +225,23 @@ TetraMeshSnapshot::~TetraMeshSnapshot() void TetraMeshSnapshot::readAndClose() { - // WIP + // read the site info into memory + Array prop; + while (infile()->readRow(prop)) + { + _sites.push_back(new Vec(prop[0], prop[1], prop[2])); // again this might have to be _positionIndex + 0 1 2 + _properties.push_back(new Array(prop)); // properties have to be used to interpret values inside tetrahedra! + } + + // close the file + Snapshot::readAndClose(); + + // build the mesh without refining + buildMesh(false, 0); + + // if a mass density policy has been set, calculate masses and densities and build the search data structure + if (hasMassDensityPolicy()) calculateDensityAndMass(); + if (hasMassDensityPolicy() || needGetEntities()) buildSearchPerBlock(); } //////////////////////////////////////////////////////////////////// @@ -236,7 +252,8 @@ void TetraMeshSnapshot::setExtent(const Box& extent) _eps = 1e-12 * extent.widths().norm(); } -TetraMeshSnapshot::TetraMeshSnapshot(const SimulationItem* item, const Box& extent, string filename, double mindihedral) +TetraMeshSnapshot::TetraMeshSnapshot(const SimulationItem* item, const Box& extent, string filename, bool refine, + double mindihedral) { // read the input file TextInFile in(item, filename, "Tetra vertices"); @@ -250,13 +267,13 @@ TetraMeshSnapshot::TetraMeshSnapshot(const SimulationItem* item, const Box& exte // calculate the Tetra cells setContext(item); setExtent(extent); - buildMesh(mindihedral); + buildMesh(refine, mindihedral); buildSearchPerBlock(); } //////////////////////////////////////////////////////////////////// -TetraMeshSnapshot::TetraMeshSnapshot(const SimulationItem* item, const Box& extent, SiteListInterface* sli, +TetraMeshSnapshot::TetraMeshSnapshot(const SimulationItem* item, const Box& extent, SiteListInterface* sli, bool refine, double mindihedral) { // prepare the data @@ -267,14 +284,14 @@ TetraMeshSnapshot::TetraMeshSnapshot(const SimulationItem* item, const Box& exte // calculate the Tetra cells setContext(item); setExtent(extent); - buildMesh(mindihedral); + buildMesh(refine, mindihedral); buildSearchPerBlock(); } //////////////////////////////////////////////////////////////////// TetraMeshSnapshot::TetraMeshSnapshot(const SimulationItem* item, const Box& extent, const vector& sites, - double mindihedral) + bool refine, double mindihedral) { // prepare the data int n = sites.size(); @@ -284,17 +301,17 @@ TetraMeshSnapshot::TetraMeshSnapshot(const SimulationItem* item, const Box& exte // calculate the Tetra cells setContext(item); setExtent(extent); - buildMesh(mindihedral); + buildMesh(refine, mindihedral); buildSearchPerBlock(); } //////////////////////////////////////////////////////////////////// -TetraMeshSnapshot::TetraMeshSnapshot(const SimulationItem* item, const Box& extent, double mindihedral) +TetraMeshSnapshot::TetraMeshSnapshot(const SimulationItem* item, const Box& extent, bool refine, double mindihedral) { setContext(item); setExtent(extent); - buildMesh(mindihedral); + buildMesh(refine, mindihedral); buildSearchPerBlock(); } @@ -321,9 +338,7 @@ namespace } } -//////////////////////////////////////////////////////////////////// - -void TetraMeshSnapshot::buildMesh(double mindihedral) +void TetraMeshSnapshot::buildDelaunay(tetgenio& out) { // remove sites outside of the domain int numOutside = 0; @@ -340,8 +355,8 @@ void TetraMeshSnapshot::buildMesh(double mindihedral) if (numOutside) numSites = eraseNullPointers(_sites); log()->info("removed " + StringUtils::toString(numOutside, 'd') + " vertices outside of the domain"); - tetgenio in, out_Delaunay, out; - tetgenbehavior behavior_Delaunay, behavior; + tetgenio in; + tetgenbehavior behavior; // in.firstnumber = 0; // remove me if no error in.numberofpoints = _sites.size(); @@ -353,14 +368,17 @@ void TetraMeshSnapshot::buildMesh(double mindihedral) in.pointlist[i * 3 + 2] = _sites[i]->z(); } - log()->info("Building Delaunay triangulation using input vertices..."); - - behavior_Delaunay.psc = 1; // -s build Delaunay tetrahedralisation - // behavior_Delaunay.verbose = 1; // -v - tetrahedralize(&behavior_Delaunay, &in, &out_Delaunay); + behavior.psc = 1; // -s build Delaunay tetrahedralisation + // behavior.verbose = 1; // -v + log()->info("Building Delaunay triangulation using input vertices..."); + tetrahedralize(&behavior, &in, &out); log()->info("Built Delaunay triangulation"); - log()->info("Refining triangulation..."); +} + +void TetraMeshSnapshot::refineDelaunay(tetgenio& in, tetgenio& out, double mindihedral) +{ + tetgenbehavior behavior; // tetgen refine options behavior.refine = 1; // -r @@ -371,10 +389,31 @@ void TetraMeshSnapshot::buildMesh(double mindihedral) behavior.facesout = 1; // -f behavior.zeroindex = 1; // -z behavior.verbose = 1; // -v - tetrahedralize(&behavior, &out_Delaunay, &out); + log()->info("Refining triangulation..."); + tetrahedralize(&behavior, &in, &out); log()->info("Refined triangulation"); +} + +//////////////////////////////////////////////////////////////////// + +void TetraMeshSnapshot::buildMesh(bool refine, double mindihedral) +{ + tetgenio delaunay, refined; + buildDelaunay(delaunay); + if (refine) + { + refineDelaunay(delaunay, refined, mindihedral); + storeTetrahedra(refined); + } + else + { + storeTetrahedra(delaunay); + } +} +void TetraMeshSnapshot::storeTetrahedra(const tetgenio& out) +{ // tranfser TetGen data to TetraMeshSnapshot data containers numTetra = out.numberoftetrahedra; numVertices = out.numberofpoints; @@ -457,13 +496,6 @@ void TetraMeshSnapshot::buildMesh(double mindihedral) //////////////////////////////////////////////////////////////////// -void TetraMeshSnapshot::calculateVolume() -{ - //WIP -} - -//////////////////////////////////////////////////////////////////// - void TetraMeshSnapshot::calculateDensityAndMass() { // allocate vectors for mass and density @@ -909,9 +941,9 @@ int TetraMeshSnapshot::cellIndex(Position bfr) const //////////////////////////////////////////////////////////////////// -const Array& TetraMeshSnapshot::properties(int /*m*/) const +const Array& TetraMeshSnapshot::properties(int m) const { - // return _sites[m]->properties(); + return *_properties[m]; } //////////////////////////////////////////////////////////////////// diff --git a/SKIRT/core/TetraMeshSnapshot.hpp b/SKIRT/core/TetraMeshSnapshot.hpp index 8f5cf27f..f931eb91 100644 --- a/SKIRT/core/TetraMeshSnapshot.hpp +++ b/SKIRT/core/TetraMeshSnapshot.hpp @@ -63,19 +63,25 @@ class TetraMeshSnapshot : public Snapshot //========== Specialty constructors ========== public: - TetraMeshSnapshot(const SimulationItem* item, const Box& extent, string filename, double mindihedral); + TetraMeshSnapshot(const SimulationItem* item, const Box& extent, string filename, bool refine, double mindihedral); - TetraMeshSnapshot(const SimulationItem* item, const Box& extent, SiteListInterface* sli, double mindihedral); + TetraMeshSnapshot(const SimulationItem* item, const Box& extent, SiteListInterface* sli, bool refine, + double mindihedral); - TetraMeshSnapshot(const SimulationItem* item, const Box& extent, const vector& sites, double mindihedral); + TetraMeshSnapshot(const SimulationItem* item, const Box& extent, const vector& sites, bool refine, + double mindihedral); - TetraMeshSnapshot(const SimulationItem* item, const Box& extent, double mindihedral); + TetraMeshSnapshot(const SimulationItem* item, const Box& extent, bool refine, double mindihedral); //=========== Private construction ========== private: class Node; + void buildDelaunay(tetgenio& out); + + void refineDelaunay(tetgenio& in, tetgenio& out, double mindihedral); + /** Given a list of generating sites (represented as partially initialized Cell objects), this private function builds the Tetra tessellation and stores the corresponding cell information, including any properties relevant for supporting the @@ -93,11 +99,9 @@ class TetraMeshSnapshot : public Snapshot constructed with these adjusted site positions, which are distributed more uniformly, thereby avoiding overly elongated cells in the Tetra tessellation. Relaxation can be quite time-consuming because the Tetra tessellation must be constructed twice. */ - void buildMesh(double mindihedral); + void buildMesh(bool refine, double mindihedral); - /** This private function calculates the volumes for all cells without using the Tetra mesh. - It assumes that both mass and mass density columns are being imported. */ - void calculateVolume(); + void storeTetrahedra(const tetgenio& out); /** This private function calculates the densities and (cumulative) masses for all cells, and logs some statistics. The function assumes that the cell volumes have been calculated, @@ -334,6 +338,7 @@ class TetraMeshSnapshot : public Snapshot // input vertices vector _sites; + vector _properties; // data members initialized when processing snapshot input and further completed by BuildMesh() vector _tetrahedra; diff --git a/SKIRT/core/TetraMeshSpatialGrid.hpp b/SKIRT/core/TetraMeshSpatialGrid.hpp index d0d945c4..22f3df03 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.hpp +++ b/SKIRT/core/TetraMeshSpatialGrid.hpp @@ -53,9 +53,12 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid, public DensityInCellInterfac PROPERTY_STRING(filename, "the name of the file containing the site positions") ATTRIBUTE_RELEVANT_IF(filename, "policyFile") + PROPERTY_BOOL(refine, "refine the grid to have higher quality cells by adding more vertices") + ATTRIBUTE_DEFAULT_VALUE(refine, "false"); + PROPERTY_DOUBLE(mindihedral, "the minimum dihedral angle per tetrahedron") ATTRIBUTE_DEFAULT_VALUE(mindihedral, "3.5") - ATTRIBUTE_RELEVANT_IF(mindihedral, "!policyImportedMesh") + ATTRIBUTE_RELEVANT_IF(mindihedral, "refine") ITEM_END() @@ -75,8 +78,6 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid, public DensityInCellInterfac //======================== Other Functions ======================= public: - bool tetUnsuitable(double* pa, double* pb, double* pc, double* pd, double vol) const; - /** This function returns the number of cells in the grid. */ int numCells() const override; diff --git a/SKIRT/core/VoronoiMeshSnapshot.cpp b/SKIRT/core/VoronoiMeshSnapshot.cpp index ffd925d6..cc00846c 100644 --- a/SKIRT/core/VoronoiMeshSnapshot.cpp +++ b/SKIRT/core/VoronoiMeshSnapshot.cpp @@ -98,7 +98,7 @@ class VoronoiMeshSnapshot::Cell : public Box // enclosing box // constructor derives the site position from the first three property values and stores the user properties; // the other data members are set to zero or empty - Cell(const Array& prop) : _r(prop[0], prop[1], prop[2]), _properties{prop} {} + Cell(const Array& prop) : _r(prop[0], prop[1], prop[2]), _properties{prop} {} // shouldn't this use _positionIndex instead of 0 1 2? // adjusts the site position with the specified offset void relax(double cx, double cy, double cz) { _r += Vec(cx, cy, cz); } From 21ec069f9cc9cf30ec9d21a97a2aaecf10fd163d Mon Sep 17 00:00:00 2001 From: arlauwer Date: Mon, 18 Nov 2024 10:19:32 +0100 Subject: [PATCH 30/51] moved class Tetrahedron to class TetraMeshSnapshot removed unused code --- SKIRT/core/TetraMeshMedium.cpp | 8 -- SKIRT/core/TetraMeshSnapshot.cpp | 171 ++++++++++++++++++++++++++-- SKIRT/core/TetraMeshSnapshot.hpp | 5 +- SKIRT/core/TetraMeshSpatialGrid.cpp | 14 +-- SKIRT/core/Tetrahedron.cpp | 134 ---------------------- SKIRT/core/Tetrahedron.hpp | 54 --------- 6 files changed, 170 insertions(+), 216 deletions(-) delete mode 100644 SKIRT/core/Tetrahedron.cpp delete mode 100644 SKIRT/core/Tetrahedron.hpp diff --git a/SKIRT/core/TetraMeshMedium.cpp b/SKIRT/core/TetraMeshMedium.cpp index 716f8baa..52a873c2 100644 --- a/SKIRT/core/TetraMeshMedium.cpp +++ b/SKIRT/core/TetraMeshMedium.cpp @@ -22,7 +22,6 @@ Snapshot* TetraMeshMedium::createAndOpenSnapshot() _tetraMeshSnapshot->importPosition(); // configure the density and/or mass column(s) - bool bothDensityAndMass = false; switch (massType()) { case MassType::MassDensity: _tetraMeshSnapshot->importMassDensity(); break; @@ -30,7 +29,6 @@ Snapshot* TetraMeshMedium::createAndOpenSnapshot() case MassType::MassDensityAndMass: _tetraMeshSnapshot->importMassDensity(); _tetraMeshSnapshot->importMass(); - bothDensityAndMass = true; break; case MassType::NumberDensity: _tetraMeshSnapshot->importNumberDensity(); break; @@ -38,15 +36,9 @@ Snapshot* TetraMeshMedium::createAndOpenSnapshot() case MassType::NumberDensityAndNumber: _tetraMeshSnapshot->importNumberDensity(); _tetraMeshSnapshot->importNumber(); - bothDensityAndMass = true; break; } - // determine whether to forego the Tetra mesh - // auto config = find(); - // if (bothDensityAndMass && !config->mediaNeedGeneratePosition() && !config->snapshotsNeedGetEntities()) - // _tetraMeshSnapshot->foregoTetraMesh(); - // set the domain extent _tetraMeshSnapshot->setExtent(domain()); return _tetraMeshSnapshot; diff --git a/SKIRT/core/TetraMeshSnapshot.cpp b/SKIRT/core/TetraMeshSnapshot.cpp index 1f2d60f3..46e38c40 100644 --- a/SKIRT/core/TetraMeshSnapshot.cpp +++ b/SKIRT/core/TetraMeshSnapshot.cpp @@ -22,7 +22,6 @@ #include "Table.hpp" #include "TextInFile.hpp" #include "Units.hpp" -#include "tetgen.h" #include #include #include @@ -65,7 +64,149 @@ namespace } } - int findEnteringFace(const Tetra* tetra, const Vec& pos, const Direction& dir) + std::array clockwiseVertices(int face) + { + std::array cv = {(face + 3) % 4, (face + 2) % 4, (face + 1) % 4}; + // if face is even we should swap two edges + if (face % 2 == 0) std::swap(cv[0], cv[2]); + return cv; + } +} + +//////////////////////////////////////////////////////////////////// + +struct TetraMeshSnapshot::Face +{ + Face() {} + + // normals are calculated in the constructor of Tetra + Face(int ntetra, int nface) : _ntetra(ntetra), _nface(nface) {} + + Vec _normal; + int _ntetra; + int _nface; +}; + +//////////////////////////////////////////////////////////////////// + +class TetraMeshSnapshot::Tetra : public Box +{ +private: + double _volume; + +public: + const std::array _vertices; + std::array _faces; + Vec _centroid; + +public: + Tetra(const std::array& vertices, const std::array& faces) : _vertices(vertices), _faces(faces) + { + double xmin = DBL_MAX; + double ymin = DBL_MAX; + double zmin = DBL_MAX; + double xmax = -DBL_MAX; + double ymax = -DBL_MAX; + double zmax = -DBL_MAX; + for (const Vec* vertex : _vertices) + { + xmin = min(xmin, vertex->x()); + ymin = min(ymin, vertex->y()); + zmin = min(zmin, vertex->z()); + xmax = max(xmax, vertex->x()); + ymax = max(ymax, vertex->y()); + zmax = max(zmax, vertex->z()); + } + setExtent(Box(xmin, ymin, zmin, xmax, ymax, zmax)); + + // volume + _volume = 1 / 6. + * abs(Vec::dot(Vec::cross(*_vertices[1] - *_vertices[0], *_vertices[2] - *_vertices[0]), + *_vertices[3] - *_vertices[0])); + + // barycenter + for (int i = 0; i < 4; i++) _centroid += *_vertices[i]; + _centroid /= 4; + + // calculate normal facing out + for (int f = 0; f < 4; f++) + { + std::array cv = clockwiseVertices(f); + Vec e12 = *vertices[cv[1]] - *vertices[cv[0]]; + Vec e13 = *vertices[cv[2]] - *vertices[cv[0]]; + Vec normal = Vec::cross(e12, e13); + normal /= normal.norm(); + + Face& face = _faces[f]; + face._normal = normal; + } + } + + //////////////////////////////////////////////////////////////////// + + Vec getEdge(int t1, int t2) const { return *_vertices[t2] - *_vertices[t1]; } + + //////////////////////////////////////////////////////////////////// + + bool inside(const Position& bfr) const + { + // since we use the k-d tree this will only slow the CellIndex + // if (!Box::contains(bfr)) return false; + + // could optimize this slightly by using same vertex for 3 faces and do final face seperately + + for (int f = 0; f < 4; f++) + { + const Face& face = _faces[f]; + const Vec* vertex = _vertices[(f + 1) % 4]; // any vertex that is on the face + + // if point->face is opposite direction as the outward pointing normal, the point is outside + if (Vec::dot(*vertex - bfr, face._normal) < 0) return false; + } + return true; + } + + //////////////////////////////////////////////////////////////////// + + double volume() const { return _volume; } + + //////////////////////////////////////////////////////////////////// + + double generateBarycentric(double& s, double& t, double& u) const + { + // https://vcg.isti.cnr.it/activities/OLD/geometryegraphics/pointintetraedro.html + if (s + t > 1.0) + { // cut'n fold the cube into a prism + s = 1.0 - s; + t = 1.0 - t; + } + if (t + u > 1.0) + { // cut'n fold the prism into a tetrahedron + double tmp = u; + u = 1.0 - s - t; + t = 1.0 - tmp; + } + else if (s + t + u > 1.0) + { + double tmp = u; + u = s + t + u - 1.0; + s = 1 - t - tmp; + } + return 1 - u - t - s; + } + + //////////////////////////////////////////////////////////////////// + + Position generatePosition(double s, double t, double u) const + { + double r = generateBarycentric(s, t, u); + + return Position(r * *_vertices[0] + u * *_vertices[1] + t * *_vertices[2] + s * *_vertices[3]); + } + + //////////////////////////////////////////////////////////////////// + + int findEnteringFace(const Vec& pos, const Direction& dir) const { int enteringFace = -1; // clockwise and cclockwise adjacent faces when checking edge v1->v2 @@ -78,8 +219,8 @@ namespace for (int v2 = v1 + 1; v2 < 4; v2++) { - Vec moment12 = Vec::cross(dir, pos - *tetra->_vertices[v1]); - double prod12 = Vec::dot(moment12, tetra->getEdge(v1, v2)); + Vec moment12 = Vec::cross(dir, pos - *_vertices[v1]); + double prod12 = Vec::dot(moment12, getEdge(v1, v2)); if (prod12 != 0.) { enteringFace = prod12 < 0 ? etable[e][0] : etable[e][1]; @@ -91,7 +232,7 @@ namespace } return enteringFace; } -} +}; //////////////////////////////////////////////////////////////////// @@ -230,7 +371,7 @@ void TetraMeshSnapshot::readAndClose() while (infile()->readRow(prop)) { _sites.push_back(new Vec(prop[0], prop[1], prop[2])); // again this might have to be _positionIndex + 0 1 2 - _properties.push_back(new Array(prop)); // properties have to be used to interpret values inside tetrahedra! + _properties.push_back(new Array(prop)); // properties have to be used to interpret values inside tetrahedra! } // close the file @@ -239,6 +380,12 @@ void TetraMeshSnapshot::readAndClose() // build the mesh without refining buildMesh(false, 0); + // interpret _properties to stored tetrahedra + // for (Tetra* tetra : _tetrahedra) + // { + // tetra->volume(); + // } + // if a mass density policy has been set, calculate masses and densities and build the search data structure if (hasMassDensityPolicy()) calculateDensityAndMass(); if (hasMassDensityPolicy() || needGetEntities()) buildSearchPerBlock(); @@ -514,7 +661,7 @@ void TetraMeshSnapshot::calculateDensityAndMass() int numIgnored = 0; for (int m = 0; m != numTetra; ++m) { - const Array& prop = _tetrahedra[m]->properties(); + const Array& prop = *_properties[m]; // original mass is zero if temperature is above cutoff or if imported mass/density is not positive double originalDensity = 0.; @@ -717,7 +864,7 @@ void TetraMeshSnapshot::writeGridPlotFiles(const SimulationItem* probe) const coords.push_back(vertex->z()); // get vertices of opposite face - std::array faceIndices = tetra->clockwiseVertices(v); + std::array faceIndices = clockwiseVertices(v); indices.push_back(3); // amount of vertices per face indices.push_back(faceIndices[0]); indices.push_back(faceIndices[1]); @@ -851,13 +998,13 @@ int TetraMeshSnapshot::cellIndex(Position bfr) const // find entering face using a single Plücker product if (enteringFace == -1) { - enteringFace = findEnteringFace(tetra, pos, dir); + enteringFace = tetra->findEnteringFace(pos, dir); if (enteringFace == -1) break; } // the translated Plücker moment in the local coordinate system Vec moment = Vec::cross(dir, pos - *tetra->_vertices[enteringFace]); - std::array cv = Tetra::clockwiseVertices(enteringFace); + std::array cv = clockwiseVertices(enteringFace); // 2 step decision tree double prod0 = Vec::dot(moment, tetra->getEdge(cv[0], enteringFace)); @@ -1002,12 +1149,12 @@ class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator // find entering face using a single Plücker product if (_enteringFace == -1) { - _enteringFace = findEnteringFace(tetra, pos, dir); + _enteringFace = tetra->findEnteringFace(pos, dir); } // the translated Plücker moment in the local coordinate system Vec moment = Vec::cross(dir, pos - *tetra->_vertices[_enteringFace]); - std::array cv = Tetra::clockwiseVertices(_enteringFace); + std::array cv = clockwiseVertices(_enteringFace); // 2 step decision tree double prod0 = Vec::dot(moment, tetra->getEdge(cv[0], _enteringFace)); diff --git a/SKIRT/core/TetraMeshSnapshot.hpp b/SKIRT/core/TetraMeshSnapshot.hpp index f931eb91..7f8e9941 100644 --- a/SKIRT/core/TetraMeshSnapshot.hpp +++ b/SKIRT/core/TetraMeshSnapshot.hpp @@ -10,7 +10,7 @@ #include "MediumSystem.hpp" #include "Snapshot.hpp" #include "TetraMeshSpatialGrid.hpp" -#include "Tetrahedron.hpp" +#include "tetgen.h" #include "array" class PathSegmentGenerator; class SiteListInterface; @@ -78,6 +78,9 @@ class TetraMeshSnapshot : public Snapshot private: class Node; + class Face; + class Tetra; + void buildDelaunay(tetgenio& out); void refineDelaunay(tetgenio& in, tetgenio& out, double mindihedral); diff --git a/SKIRT/core/TetraMeshSpatialGrid.cpp b/SKIRT/core/TetraMeshSpatialGrid.cpp index 2ca522d5..e9b9293e 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.cpp +++ b/SKIRT/core/TetraMeshSpatialGrid.cpp @@ -60,7 +60,7 @@ void TetraMeshSpatialGrid::setupSelfBefore() auto random = find(); vector rv(_numSites); for (int m = 0; m != _numSites; ++m) rv[m] = random->position(extent()); - _mesh = new TetraMeshSnapshot(this, extent(), rv, _mindihedral); + _mesh = new TetraMeshSnapshot(this, extent(), rv, _refine, _mindihedral); break; } case Policy::CentralPeak: @@ -76,7 +76,7 @@ void TetraMeshSpatialGrid::setupSelfBefore() Position p = Position(r, k); if (extent().contains(p)) rv[m++] = p; // discard any points outside of the domain } - _mesh = new TetraMeshSnapshot(this, extent(), rv, _mindihedral); + _mesh = new TetraMeshSnapshot(this, extent(), rv, _refine, _mindihedral); break; } case Policy::DustDensity: @@ -89,7 +89,7 @@ void TetraMeshSpatialGrid::setupSelfBefore() if (medium->mix()->isDust()) media.push_back(medium); for (auto medium : media) weights.push_back(medium->mass()); _mesh = - new TetraMeshSnapshot(this, extent(), sampleMedia(media, weights, extent(), _numSites), _mindihedral); + new TetraMeshSnapshot(this, extent(), sampleMedia(media, weights, extent(), _numSites), _refine, _mindihedral); break; } case Policy::ElectronDensity: @@ -102,7 +102,7 @@ void TetraMeshSpatialGrid::setupSelfBefore() if (medium->mix()->isElectrons()) media.push_back(medium); for (auto medium : media) weights.push_back(medium->number()); _mesh = - new TetraMeshSnapshot(this, extent(), sampleMedia(media, weights, extent(), _numSites), _mindihedral); + new TetraMeshSnapshot(this, extent(), sampleMedia(media, weights, extent(), _numSites), _refine, _mindihedral); break; } case Policy::GasDensity: @@ -115,18 +115,18 @@ void TetraMeshSpatialGrid::setupSelfBefore() if (medium->mix()->isGas()) media.push_back(medium); for (auto medium : media) weights.push_back(medium->number()); _mesh = - new TetraMeshSnapshot(this, extent(), sampleMedia(media, weights, extent(), _numSites), _mindihedral); + new TetraMeshSnapshot(this, extent(), sampleMedia(media, weights, extent(), _numSites), _refine, _mindihedral); break; } case Policy::File: { - _mesh = new TetraMeshSnapshot(this, extent(), _filename, _mindihedral); + _mesh = new TetraMeshSnapshot(this, extent(), _filename, _refine, _mindihedral); break; } case Policy::ImportedSites: { auto sli = find()->interface(2); - _mesh = new TetraMeshSnapshot(this, extent(), sli, _mindihedral); + _mesh = new TetraMeshSnapshot(this, extent(), sli, _refine, _mindihedral); break; } case Policy::ImportedMesh: diff --git a/SKIRT/core/Tetrahedron.cpp b/SKIRT/core/Tetrahedron.cpp deleted file mode 100644 index 89c2edfa..00000000 --- a/SKIRT/core/Tetrahedron.cpp +++ /dev/null @@ -1,134 +0,0 @@ -#include "Tetrahedron.hpp" - -Tetra::Tetra(const std::array& vertices, const std::array& faces, const Array& prop) - : Tetra(vertices, faces) -{ - _properties = prop; -} - -Tetra::Tetra(const std::array& vertices, const std::array& faces) : _vertices(vertices), _faces(faces) -{ - double xmin = DBL_MAX; - double ymin = DBL_MAX; - double zmin = DBL_MAX; - double xmax = -DBL_MAX; - double ymax = -DBL_MAX; - double zmax = -DBL_MAX; - for (const Vec* vertex : _vertices) - { - xmin = min(xmin, vertex->x()); - ymin = min(ymin, vertex->y()); - zmin = min(zmin, vertex->z()); - xmax = max(xmax, vertex->x()); - ymax = max(ymax, vertex->y()); - zmax = max(zmax, vertex->z()); - } - setExtent(Box(xmin, ymin, zmin, xmax, ymax, zmax)); - - // volume - _volume = 1 / 6. - * abs(Vec::dot(Vec::cross(*_vertices[1] - *_vertices[0], *_vertices[2] - *_vertices[0]), - *_vertices[3] - *_vertices[0])); - - // barycenter - for (int i = 0; i < 4; i++) _centroid += *_vertices[i]; - _centroid /= 4; - - // calculate normal facing out - for (int f = 0; f < 4; f++) - { - std::array cv = clockwiseVertices(f); - Vec e12 = *vertices[cv[1]] - *vertices[cv[0]]; - Vec e13 = *vertices[cv[2]] - *vertices[cv[0]]; - Vec normal = Vec::cross(e12, e13); - normal /= normal.norm(); - - Face& face = _faces[f]; - face._normal = normal; - } -} - -//////////////////////////////////////////////////////////////////// - -Vec Tetra::getEdge(int t1, int t2) const -{ - return *_vertices[t2] - *_vertices[t1]; -} - -//////////////////////////////////////////////////////////////////// - -bool Tetra::inside(const Position& bfr) const -{ - // since we use the k-d tree this will only slow the CellIndex - // if (!Box::contains(bfr)) return false; - - // could optimize this slightly by using same vertex for 3 faces and do final face seperately - - for (int f = 0; f < 4; f++) - { - const Face& face = _faces[f]; - const Vec* vertex = _vertices[(f + 1) % 4]; // any vertex that is on the face - - // if point->face is opposite direction as the outward pointing normal, the point is outside - if (Vec::dot(*vertex - bfr, face._normal) < 0) return false; - } - return true; -} - -//////////////////////////////////////////////////////////////////// - -double Tetra::volume() const -{ - return _volume; -} - -//////////////////////////////////////////////////////////////////// - -const Array& Tetra::properties() -{ - return _properties; -} - -//////////////////////////////////////////////////////////////////// - -double Tetra::generateBarycentric(double& s, double& t, double& u) -{ - // https://vcg.isti.cnr.it/activities/OLD/geometryegraphics/pointintetraedro.html - if (s + t > 1.0) - { // cut'n fold the cube into a prism - s = 1.0 - s; - t = 1.0 - t; - } - if (t + u > 1.0) - { // cut'n fold the prism into a tetrahedron - double tmp = u; - u = 1.0 - s - t; - t = 1.0 - tmp; - } - else if (s + t + u > 1.0) - { - double tmp = u; - u = s + t + u - 1.0; - s = 1 - t - tmp; - } - return 1 - u - t - s; -} - -//////////////////////////////////////////////////////////////////// - -Position Tetra::generatePosition(double s, double t, double u) const -{ - double r = Tetra::generateBarycentric(s, t, u); - - return Position(r * *_vertices[0] + u * *_vertices[1] + t * *_vertices[2] + s * *_vertices[3]); -} - -//////////////////////////////////////////////////////////////////// - -std::array Tetra::clockwiseVertices(int face) -{ - std::array cv = {(face + 3) % 4, (face + 2) % 4, (face + 1) % 4}; - // if face is even we should swap two edges - if (face % 2 == 0) std::swap(cv[0], cv[2]); - return cv; -} diff --git a/SKIRT/core/Tetrahedron.hpp b/SKIRT/core/Tetrahedron.hpp deleted file mode 100644 index e1f786e6..00000000 --- a/SKIRT/core/Tetrahedron.hpp +++ /dev/null @@ -1,54 +0,0 @@ -#include "Position.hpp" -#include "Array.hpp" -#include "Box.hpp" -#include - -struct Face -{ - Face() {} - - // normals are calculated in the constructor of Tetra - Face(int ntetra, int nface) : _ntetra(ntetra), _nface(nface) {} - - Vec _normal; - int _ntetra; - int _nface; -}; - -class Tetra : public Box -{ -private: - double _volume; - Array _properties; - -public: - const std::array _vertices; - std::array _faces; - Vec _centroid; - -public: - Tetra(const std::array& vertices, const std::array& faces, const Array& prop); - - Tetra(const std::array& vertices, const std::array& faces); - - bool inside(const Position& bfr) const; - - Vec getEdge(int t1, int t2) const; - - double volume() const; - - const Array& properties(); - - static double generateBarycentric(double& s, double& t, double& u); - - Position generatePosition(double s, double t, double u) const; - - /** - * @brief This gives the clockwise vertices of a given face looking from inside the tetrahedron. - * The Plücker products are thus all negative for a leaving ray and positive for an entering ray from outside - * - * @param face - * @return std::array - */ - static std::array clockwiseVertices(int face); -}; From 464575073023a341e892f0d564c86319befac298 Mon Sep 17 00:00:00 2001 From: arlauwer Date: Fri, 29 Nov 2024 18:21:02 +0100 Subject: [PATCH 31/51] started basic tetra implemenation moved all tetra code to TetraMeshSpatialGrid removed unnecessary code/classes and fixed small oversights added tetgen LICENSE --- SKIRT/core/TetraMeshGeometry.cpp | 44 - SKIRT/core/TetraMeshGeometry.hpp | 77 -- SKIRT/core/TetraMeshInterface.hpp | 33 - SKIRT/core/TetraMeshMedium.cpp | 54 -- SKIRT/core/TetraMeshMedium.hpp | 120 --- SKIRT/core/TetraMeshSnapshot.cpp | 1272 --------------------------- SKIRT/core/TetraMeshSnapshot.hpp | 1 - SKIRT/core/TetraMeshSource.cpp | 28 - SKIRT/core/TetraMeshSource.hpp | 58 -- SKIRT/core/TetraMeshSpatialGrid.cpp | 917 ++++++++++++++++++- SKIRT/core/TetraMeshSpatialGrid.hpp | 60 +- SKIRT/tetgen/LICENSE | 666 ++++++++++++++ SKIRT/tetgen/tetgen.h | 17 +- 13 files changed, 1593 insertions(+), 1754 deletions(-) delete mode 100644 SKIRT/core/TetraMeshGeometry.cpp delete mode 100644 SKIRT/core/TetraMeshGeometry.hpp delete mode 100644 SKIRT/core/TetraMeshInterface.hpp delete mode 100644 SKIRT/core/TetraMeshMedium.cpp delete mode 100644 SKIRT/core/TetraMeshMedium.hpp delete mode 100644 SKIRT/core/TetraMeshSnapshot.cpp delete mode 100644 SKIRT/core/TetraMeshSource.cpp delete mode 100644 SKIRT/core/TetraMeshSource.hpp create mode 100644 SKIRT/tetgen/LICENSE diff --git a/SKIRT/core/TetraMeshGeometry.cpp b/SKIRT/core/TetraMeshGeometry.cpp deleted file mode 100644 index f29a5834..00000000 --- a/SKIRT/core/TetraMeshGeometry.cpp +++ /dev/null @@ -1,44 +0,0 @@ -/*////////////////////////////////////////////////////////////////// -//// The SKIRT project -- advanced radiative transfer //// -//// © Astronomical Observatory, Ghent University //// -///////////////////////////////////////////////////////////////// */ - -#include "TetraMeshGeometry.hpp" -#include "TetraMeshSnapshot.hpp" - -//////////////////////////////////////////////////////////////////// - -Snapshot* TetraMeshGeometry::createAndOpenSnapshot() -{ - // create and open the snapshot - _tetraMeshSnapshot = new TetraMeshSnapshot; - _tetraMeshSnapshot->open(this, filename(), "Tetra sites"); - - // honor custom column reordering - _tetraMeshSnapshot->useColumns(useColumns()); - - // configure the position columns - _tetraMeshSnapshot->importPosition(); - - // configure the mass or density column - switch (massType()) - { - case MassType::MassDensity: _tetraMeshSnapshot->importMassDensity(); break; - case MassType::Mass: _tetraMeshSnapshot->importMass(); break; - case MassType::NumberDensity: _tetraMeshSnapshot->importNumberDensity(); break; - case MassType::Number: _tetraMeshSnapshot->importNumber(); break; - } - - // set the domain extent - _tetraMeshSnapshot->setExtent(domain()); - return _tetraMeshSnapshot; -} - -//////////////////////////////////////////////////////////////////// - -TetraMeshSnapshot* TetraMeshGeometry::tetraMesh() const -{ - return _tetraMeshSnapshot; -} - -//////////////////////////////////////////////////////////////////// diff --git a/SKIRT/core/TetraMeshGeometry.hpp b/SKIRT/core/TetraMeshGeometry.hpp deleted file mode 100644 index d70fda99..00000000 --- a/SKIRT/core/TetraMeshGeometry.hpp +++ /dev/null @@ -1,77 +0,0 @@ -/*////////////////////////////////////////////////////////////////// -//// The SKIRT project -- advanced radiative transfer //// -//// © Astronomical Observatory, Ghent University //// -///////////////////////////////////////////////////////////////// */ - -#ifndef TETRAMESHGEOMETRY_HPP -#define TETRAMESHGEOMETRY_HPP - -#include "MeshGeometry.hpp" -#include "TetraMeshInterface.hpp" - -//////////////////////////////////////////////////////////////////// - -/** A TetraMeshGeometry instance represents a 3D geometry with a spatial density distribution - described by a list of sites generating a Tetra tesselation of a cubodail domain. The data is - usually extracted from a cosmological simulation snapshot, and it must be provided in a column - text file formatted as described below. The total mass in the geometry is normalized to unity - after importing the data. - - Refer to the description of the TextInFile class for information on overall formatting and on - how to include header lines specifying the units for each column in the input file. In case the - input file has no unit specifications, the default units mentioned below are used instead. The - input file should contain 4, 5, or 6 columns, depending on the options configured by the user - for this TetraMeshGeometry instance: - - \f[ x\,(\mathrm{pc}) \quad y\,(\mathrm{pc}) \quad z\,(\mathrm{pc}) \quad \{\, - \rho\,(\text{M}_\odot\,\text{pc}^{-3}) \;\;|\;\; M\,(\text{M}_\odot) \;\;|\;\; - n\,(\text{cm}^{-3}) \;\;|\;\; N\,(1) \,\} \quad [Z\,(1)] \quad [T\,(\mathrm{K})] \f] - - The first three columns are the \f$x\f$, \f$y\f$ and \f$z\f$ coordinates of the Tetra site - (i.e. the location defining a particular Tetra cell). - - Depending on the value of the \em massType option, the fourth column lists the average mass - density \f$\rho\f$, the integrated mass \f$M\f$, the average number density \f$n\f$, or the - integrated number density \f$N\f$ for the cell corresponding to the site. The precise units for - this field are irrelevant because the total mass in the geometry will be normalized to unity - after importing the data. However, the import procedure still insists on knowing the units. - - If the \em importMetallicity option is enabled, the next column specifies a "metallicity" - fraction, which in this context is simply multiplied with the mass/density column to obtain the - actual mass/density of the cell. If the \em importTemperature option is enabled, the next - column specifies a temperature. If this temperature is higher than the maximum configured - temperature, the mass and density for the site are set to zero, regardless of the mass or - density specified in the fourth column. If the \em importTemperature option is disabled, or the - maximum temperature value is set to zero, such a cutoff is not applied. */ -class TetraMeshGeometry : public MeshGeometry, public TetraMeshInterface -{ - ITEM_CONCRETE(TetraMeshGeometry, MeshGeometry, "a geometry imported from data represented on a Tetra mesh") - ATTRIBUTE_TYPE_INSERT(TetraMeshGeometry, "TetraMeshInterface") - ITEM_END() - - //============= Construction - Setup - Destruction ============= - -protected: - /** This function constructs a new TetraMeshSnapshot object, calls its open() function, - passes it the domain extent configured by the user, configures it to import a mass or a - density column, and finally returns a pointer to the object. Ownership of the Snapshot - object is transferred to the caller. */ - Snapshot* createAndOpenSnapshot() override; - - //=================== Other functions ================== - -protected: - /** This function implements the TetraMeshInterface interface. It returns a pointer to the - Tetra mesh snapshot maintained by this geometry. */ - TetraMeshSnapshot* tetraMesh() const override; - - //===================== Data members ==================== - -private: - // an extra pointer to our snapshot used to implement TetraMeshInterface (ownership is passed to base class) - TetraMeshSnapshot* _tetraMeshSnapshot{nullptr}; -}; - -//////////////////////////////////////////////////////////////////// - -#endif diff --git a/SKIRT/core/TetraMeshInterface.hpp b/SKIRT/core/TetraMeshInterface.hpp deleted file mode 100644 index 3e32e188..00000000 --- a/SKIRT/core/TetraMeshInterface.hpp +++ /dev/null @@ -1,33 +0,0 @@ -/*////////////////////////////////////////////////////////////////// -//// The SKIRT project -- advanced radiative transfer //// -//// © Astronomical Observatory, Ghent University //// -///////////////////////////////////////////////////////////////// */ - -#ifndef TETRAMESHINTERFACE_HPP -#define TETRAMESHINTERFACE_HPP - -#include "Basics.hpp" -class TetraMeshSnapshot; - -//////////////////////////////////////////////////////////////////// - -/** TetraMeshInterface is a pure interface. It provides access to the Tetra mesh snapshot - maintained by the object that implements the interface. */ -class TetraMeshInterface -{ -protected: - /** The empty constructor for the interface. */ - TetraMeshInterface() {} - -public: - /** The empty destructor for the interface. */ - virtual ~TetraMeshInterface() {} - - /** This function must be implemented in a derived class. It returns a pointer to the Tetra - mesh snapshot maintained by the object that implements the interface. */ - virtual TetraMeshSnapshot* tetraMesh() const = 0; -}; - -///////////////////////////////////////////////////////////////////////////// - -#endif diff --git a/SKIRT/core/TetraMeshMedium.cpp b/SKIRT/core/TetraMeshMedium.cpp deleted file mode 100644 index 52a873c2..00000000 --- a/SKIRT/core/TetraMeshMedium.cpp +++ /dev/null @@ -1,54 +0,0 @@ -/*////////////////////////////////////////////////////////////////// -//// The SKIRT project -- advanced radiative transfer //// -//// © Astronomical Observatory, Ghent University //// -///////////////////////////////////////////////////////////////// */ - -#include "TetraMeshMedium.hpp" -#include "Configuration.hpp" -#include "TetraMeshSnapshot.hpp" - -//////////////////////////////////////////////////////////////////// - -Snapshot* TetraMeshMedium::createAndOpenSnapshot() -{ - // create and open the snapshot - _tetraMeshSnapshot = new TetraMeshSnapshot; - _tetraMeshSnapshot->open(this, filename(), "Tetra sites"); - - // honor custom column reordering - _tetraMeshSnapshot->useColumns(useColumns()); - - // configure the position columns - _tetraMeshSnapshot->importPosition(); - - // configure the density and/or mass column(s) - switch (massType()) - { - case MassType::MassDensity: _tetraMeshSnapshot->importMassDensity(); break; - case MassType::Mass: _tetraMeshSnapshot->importMass(); break; - case MassType::MassDensityAndMass: - _tetraMeshSnapshot->importMassDensity(); - _tetraMeshSnapshot->importMass(); - break; - - case MassType::NumberDensity: _tetraMeshSnapshot->importNumberDensity(); break; - case MassType::Number: _tetraMeshSnapshot->importNumber(); break; - case MassType::NumberDensityAndNumber: - _tetraMeshSnapshot->importNumberDensity(); - _tetraMeshSnapshot->importNumber(); - break; - } - - // set the domain extent - _tetraMeshSnapshot->setExtent(domain()); - return _tetraMeshSnapshot; -} - -//////////////////////////////////////////////////////////////////// - -TetraMeshSnapshot* TetraMeshMedium::tetraMesh() const -{ - return _tetraMeshSnapshot; -} - -//////////////////////////////////////////////////////////////////// diff --git a/SKIRT/core/TetraMeshMedium.hpp b/SKIRT/core/TetraMeshMedium.hpp deleted file mode 100644 index 52cdbc2e..00000000 --- a/SKIRT/core/TetraMeshMedium.hpp +++ /dev/null @@ -1,120 +0,0 @@ -/*////////////////////////////////////////////////////////////////// -//// The SKIRT project -- advanced radiative transfer //// -//// © Astronomical Observatory, Ghent University //// -///////////////////////////////////////////////////////////////// */ - -#ifndef TETRAMESHMEDIUM_HPP -#define TETRAMESHMEDIUM_HPP - -#include "MeshMedium.hpp" -#include "TetraMeshInterface.hpp" - -//////////////////////////////////////////////////////////////////// - -/** A TetraMeshMedium instance represents a transfer medium with a spatial density distribution - described by a list of sites generating a Tetra tesselation of a cubodail domain. The data is - usually extracted from a cosmological simulation snapshot, and it must be provided in a column - text file formatted as described below. - - Refer to the description of the TextInFile class for information on overall formatting and on - how to include header lines specifying the units for each column in the input file. In case the - input file has no unit specifications, the default units mentioned below are used instead. The - number of columns expected in the input file depends on the options configured by the user for - this TetraMeshMedium instance: - - \f[ x\,(\mathrm{pc}) \quad y\,(\mathrm{pc}) \quad z\,(\mathrm{pc}) \quad \{\, - \rho\,(\text{M}_\odot\,\text{pc}^{-3}) \;\;|\;\; M\,(\text{M}_\odot) \;\;|\;\; - n\,(\text{cm}^{-3}) \;\;|\;\; N\,(1) \,\} \quad [Z\,(1)] \quad [T\,(\mathrm{K})] \quad [ - v_x\,(\mathrm{km/s}) \quad v_y\,(\mathrm{km/s}) \quad v_z\,(\mathrm{km/s}) ] \quad [ - B_x\,(\mu\mathrm{G}) \quad B_y\,(\mu\mathrm{G}) \quad B_z\,(\mu\mathrm{G}) ] \quad [ - \dots\text{mix family params}\dots ] \f] - - The first three columns are the \f$x\f$, \f$y\f$ and \f$z\f$ coordinates of the Tetra site - (i.e. the location defining a particular Tetra cell). - - Depending on the value of the \em massType option, the fourth column lists the average mass - density \f$\rho\f$, the integrated mass \f$M\f$, the average number density \f$n\f$, or the - integrated number density \f$N\f$ for the cell corresponding to the site. This quantity is - multiplied by the value of the \em massFraction option. - - If the \em importMetallicity option is enabled, the next column specifies a "metallicity" - fraction, which is multiplied with the mass/density column to obtain the actual mass/density of - the cell corresponding to the site. If the \em importTemperature option is enabled, the next - column specifies a temperature. If this temperature is higher than the value of the \em - maxTemperature option, the mass and density for the site are set to zero, regardless of the - mass or density specified in the fourth column. If the \em importTemperature option is - disabled, or the maximum temperature value is set to zero, such a cutoff is not applied. - - If both the \em importMetallicity and \em importTemperature options are enabled, this leads to - the following expression for the density of an imported site (or a simular formula for the - other mass quantity types): - - \f[ \rho_\mathrm{imported} = \begin{cases} f_\mathrm{mass}\,Z\,\rho & \mathrm{if}\; - TAvoiding construction of the Tetra tessellation - - The algorithm used by the TetraMeshSnapshot class for constructing Tetra tessellations - sometimes fails (for example, when generating sites are too close to each other). In those - cases, it can be desirable to forego the construction of the Tetra tessellation and instead - use a nearest neighbor search for sampling the density distribution. This is possible as long - as the full tessellation is not needed for other purposes, such as to perform radiative - transfer or to generate random positions drawn from the density distribution. An important use - case is when the medium density distribution defined on the Tetra grid is resampled to an - octree grid to perform radiative transfer. However, the octree subdivision algorithm requires - the total mass of the medium in addition to the density samples. Without the full Tetra - tessellation, it is impossible to calculate the cell volume that would allow conversion between - cell density and mass. - - To enable this use case, the \em massType option can be set to include both mass density and - volume-integrated mass (or both number density and volume-integrated number) in the imported - data file. Thus, if the \em massType option is set to one of these choices, the import file - must include two columns (mass density \f$\rho\f$ plus integrated mass \f$M\f$, or number - density \f$n\f$ plus integrated number density \f$N\f$ ). Furthermore, if the simulation - configuration allows it (i.e. the full Tetra tessellation is not needed for other purposes), - the TetraMeshSnapshot class will use the information in these two columns to calculate the - cell volumes and the total medium mass, and it will forego construction of the Tetra - tessellation. */ -class TetraMeshMedium : public MeshMedium, public TetraMeshInterface -{ - ITEM_CONCRETE(TetraMeshMedium, MeshMedium, "a transfer medium imported from data represented on a Tetra mesh") - ATTRIBUTE_TYPE_INSERT(TetraMeshMedium, "TetraMeshInterface") - ITEM_END() - - //============= Construction - Setup - Destruction ============= - -protected: - /** This function constructs a new TetraMeshSnapshot object, calls its open() function, - passes it the domain extent configured by the user, configures it to import a mass or a - density column, and finally returns a pointer to the object. Ownership of the Snapshot - object is transferred to the caller. */ - Snapshot* createAndOpenSnapshot() override; - - //=================== Other functions ================== - -protected: - /** This function implements the TetraMeshInterface interface. It returns a pointer to the - Tetra mesh snapshot maintained by this geometry. */ - TetraMeshSnapshot* tetraMesh() const override; - - //===================== Data members ==================== - -private: - // an extra pointer to our snapshot used to implement TetraMeshInterface (ownership is passed to base class) - TetraMeshSnapshot* _tetraMeshSnapshot{nullptr}; -}; - -//////////////////////////////////////////////////////////////////// - -#endif diff --git a/SKIRT/core/TetraMeshSnapshot.cpp b/SKIRT/core/TetraMeshSnapshot.cpp deleted file mode 100644 index 46e38c40..00000000 --- a/SKIRT/core/TetraMeshSnapshot.cpp +++ /dev/null @@ -1,1272 +0,0 @@ -/*////////////////////////////////////////////////////////////////// -//// The SKIRT project -- advanced radiative transfer //// -//// © Astronomical Observatory, Ghent University //// -///////////////////////////////////////////////////////////////// */ - -#include "TetraMeshSnapshot.hpp" -#include "EntityCollection.hpp" -#include "FatalError.hpp" -#include "Log.hpp" -#include "MediumSystem.hpp" -#include "NR.hpp" -#include "Parallel.hpp" -#include "ParallelFactory.hpp" -#include "PathSegmentGenerator.hpp" -#include "ProcessManager.hpp" -#include "Random.hpp" -#include "SimulationItem.hpp" -#include "SiteListInterface.hpp" -#include "SpatialGridPath.hpp" -#include "SpatialGridPlotFile.hpp" -#include "StringUtils.hpp" -#include "Table.hpp" -#include "TextInFile.hpp" -#include "Units.hpp" -#include -#include -#include -#include -#include -#include -#include "container.hh" - -//////////////////////////////////////////////////////////////////// - -namespace -{ - bool lessthan(Vec p1, Vec p2, int axis) - { - switch (axis) - { - case 0: // split on x - if (p1.x() < p2.x()) return true; - if (p1.x() > p2.x()) return false; - if (p1.y() < p2.y()) return true; - if (p1.y() > p2.y()) return false; - if (p1.z() < p2.z()) return true; - return false; - case 1: // split on y - if (p1.y() < p2.y()) return true; - if (p1.y() > p2.y()) return false; - if (p1.z() < p2.z()) return true; - if (p1.z() > p2.z()) return false; - if (p1.x() < p2.x()) return true; - return false; - case 2: // split on z - if (p1.z() < p2.z()) return true; - if (p1.z() > p2.z()) return false; - if (p1.x() < p2.x()) return true; - if (p1.x() > p2.x()) return false; - if (p1.y() < p2.y()) return true; - return false; - default: // this should never happen - return false; - } - } - - std::array clockwiseVertices(int face) - { - std::array cv = {(face + 3) % 4, (face + 2) % 4, (face + 1) % 4}; - // if face is even we should swap two edges - if (face % 2 == 0) std::swap(cv[0], cv[2]); - return cv; - } -} - -//////////////////////////////////////////////////////////////////// - -struct TetraMeshSnapshot::Face -{ - Face() {} - - // normals are calculated in the constructor of Tetra - Face(int ntetra, int nface) : _ntetra(ntetra), _nface(nface) {} - - Vec _normal; - int _ntetra; - int _nface; -}; - -//////////////////////////////////////////////////////////////////// - -class TetraMeshSnapshot::Tetra : public Box -{ -private: - double _volume; - -public: - const std::array _vertices; - std::array _faces; - Vec _centroid; - -public: - Tetra(const std::array& vertices, const std::array& faces) : _vertices(vertices), _faces(faces) - { - double xmin = DBL_MAX; - double ymin = DBL_MAX; - double zmin = DBL_MAX; - double xmax = -DBL_MAX; - double ymax = -DBL_MAX; - double zmax = -DBL_MAX; - for (const Vec* vertex : _vertices) - { - xmin = min(xmin, vertex->x()); - ymin = min(ymin, vertex->y()); - zmin = min(zmin, vertex->z()); - xmax = max(xmax, vertex->x()); - ymax = max(ymax, vertex->y()); - zmax = max(zmax, vertex->z()); - } - setExtent(Box(xmin, ymin, zmin, xmax, ymax, zmax)); - - // volume - _volume = 1 / 6. - * abs(Vec::dot(Vec::cross(*_vertices[1] - *_vertices[0], *_vertices[2] - *_vertices[0]), - *_vertices[3] - *_vertices[0])); - - // barycenter - for (int i = 0; i < 4; i++) _centroid += *_vertices[i]; - _centroid /= 4; - - // calculate normal facing out - for (int f = 0; f < 4; f++) - { - std::array cv = clockwiseVertices(f); - Vec e12 = *vertices[cv[1]] - *vertices[cv[0]]; - Vec e13 = *vertices[cv[2]] - *vertices[cv[0]]; - Vec normal = Vec::cross(e12, e13); - normal /= normal.norm(); - - Face& face = _faces[f]; - face._normal = normal; - } - } - - //////////////////////////////////////////////////////////////////// - - Vec getEdge(int t1, int t2) const { return *_vertices[t2] - *_vertices[t1]; } - - //////////////////////////////////////////////////////////////////// - - bool inside(const Position& bfr) const - { - // since we use the k-d tree this will only slow the CellIndex - // if (!Box::contains(bfr)) return false; - - // could optimize this slightly by using same vertex for 3 faces and do final face seperately - - for (int f = 0; f < 4; f++) - { - const Face& face = _faces[f]; - const Vec* vertex = _vertices[(f + 1) % 4]; // any vertex that is on the face - - // if point->face is opposite direction as the outward pointing normal, the point is outside - if (Vec::dot(*vertex - bfr, face._normal) < 0) return false; - } - return true; - } - - //////////////////////////////////////////////////////////////////// - - double volume() const { return _volume; } - - //////////////////////////////////////////////////////////////////// - - double generateBarycentric(double& s, double& t, double& u) const - { - // https://vcg.isti.cnr.it/activities/OLD/geometryegraphics/pointintetraedro.html - if (s + t > 1.0) - { // cut'n fold the cube into a prism - s = 1.0 - s; - t = 1.0 - t; - } - if (t + u > 1.0) - { // cut'n fold the prism into a tetrahedron - double tmp = u; - u = 1.0 - s - t; - t = 1.0 - tmp; - } - else if (s + t + u > 1.0) - { - double tmp = u; - u = s + t + u - 1.0; - s = 1 - t - tmp; - } - return 1 - u - t - s; - } - - //////////////////////////////////////////////////////////////////// - - Position generatePosition(double s, double t, double u) const - { - double r = generateBarycentric(s, t, u); - - return Position(r * *_vertices[0] + u * *_vertices[1] + t * *_vertices[2] + s * *_vertices[3]); - } - - //////////////////////////////////////////////////////////////////// - - int findEnteringFace(const Vec& pos, const Direction& dir) const - { - int enteringFace = -1; - // clockwise and cclockwise adjacent faces when checking edge v1->v2 - constexpr int etable[6][2] = {{3, 2}, {1, 3}, {2, 1}, {3, 0}, {0, 2}, {1, 0}}; - // try all 6 edges because of rare edge cases where ray is inside edge - // having only 1 non-zero Plücker product - int e = 0; - for (int v1 = 0; v1 < 3; v1++) - { - for (int v2 = v1 + 1; v2 < 4; v2++) - { - - Vec moment12 = Vec::cross(dir, pos - *_vertices[v1]); - double prod12 = Vec::dot(moment12, getEdge(v1, v2)); - if (prod12 != 0.) - { - enteringFace = prod12 < 0 ? etable[e][0] : etable[e][1]; - break; - } - e++; - } - if (enteringFace != -1) break; - } - return enteringFace; - } -}; - -//////////////////////////////////////////////////////////////////// - -class TetraMeshSnapshot::Node -{ -private: - int _m; // index in _cells to the site defining the split at this node - int _axis; // split axis for this node (0,1,2) - Node* _up; // ptr to the parent node - Node* _left; // ptr to the left child node - Node* _right; // ptr to the right child node - - // returns the square of its argument - static double sqr(double x) { return x * x; } - -public: - // constructor stores the specified site index and child pointers (which may be null) - Node(int m, int depth, Node* left, Node* right) : _m(m), _axis(depth % 3), _up(0), _left(left), _right(right) - { - if (_left) _left->setParent(this); - if (_right) _right->setParent(this); - } - - // destructor destroys the children - ~Node() - { - delete _left; - delete _right; - } - - // sets parent pointer (called from parent's constructor) - void setParent(Node* up) { _up = up; } - - // returns the corresponding data member - int m() const { return _m; } - Node* up() const { return _up; } - Node* left() const { return _left; } - Node* right() const { return _right; } - - // returns the apropriate child for the specified query point - Node* child(Vec bfr, const vector& points) const - { - return lessthan(bfr, *points[_m], _axis) ? _left : _right; - } - - // returns the other child than the one that would be apropriate for the specified query point - Node* otherChild(Vec bfr, const vector& points) const - { - return lessthan(bfr, *points[_m], _axis) ? _right : _left; - } - - // returns the squared distance from the query point to the split plane - double squaredDistanceToSplitPlane(Vec bfr, const vector& points) const - { - switch (_axis) - { - case 0: // split on x - return sqr(points[_m]->x() - bfr.x()); - case 1: // split on y - return sqr(points[_m]->y() - bfr.y()); - case 2: // split on z - return sqr(points[_m]->z() - bfr.z()); - default: // this should never happen - return 0; - } - } - - // returns the node in this subtree that represents the site nearest to the query point - Node* nearest(Vec bfr, const vector& points) - { - // recursively descend the tree until a leaf node is reached, going left or right depending on - // whether the specified point is less than or greater than the current node in the split dimension - Node* current = this; - while (Node* child = current->child(bfr, points)) current = child; - - // unwind the recursion, looking for the nearest node while climbing up - Node* best = current; - double bestSD = (*points[best->m()] - bfr).norm2(); - while (true) - { - // if the current node is closer than the current best, then it becomes the current best - double currentSD = (*points[current->m()] - bfr).norm2(); - if (currentSD < bestSD) - { - best = current; - bestSD = currentSD; - } - - // if there could be points on the other side of the splitting plane for the current node - // that are closer to the search point than the current best, then ... - double splitSD = current->squaredDistanceToSplitPlane(bfr, points); - if (splitSD < bestSD) - { - // move down the other branch of the tree from the current node looking for closer points, - // following the same recursive process as the entire search - Node* other = current->otherChild(bfr, points); - if (other) - { - Node* otherBest = other->nearest(bfr, points); - double otherBestSD = (*points[otherBest->m()] - bfr).norm2(); - if (otherBestSD < bestSD) - { - best = otherBest; - bestSD = otherBestSD; - } - } - } - - // move up to the parent until we meet the top node - if (current == this) break; - current = current->up(); - } - return best; - } -}; - -//////////////////////////////////////////////////////////////////// - -TetraMeshSnapshot::TetraMeshSnapshot() {} - -//////////////////////////////////////////////////////////////////// - -TetraMeshSnapshot::~TetraMeshSnapshot() -{ - for (auto cell : _vertices) delete cell; - for (auto tetra : _tetrahedra) delete tetra; // WIP FIX THIS - for (auto tree : _blocktrees) delete tree; -} - -//////////////////////////////////////////////////////////////////// - -void TetraMeshSnapshot::readAndClose() -{ - // read the site info into memory - Array prop; - while (infile()->readRow(prop)) - { - _sites.push_back(new Vec(prop[0], prop[1], prop[2])); // again this might have to be _positionIndex + 0 1 2 - _properties.push_back(new Array(prop)); // properties have to be used to interpret values inside tetrahedra! - } - - // close the file - Snapshot::readAndClose(); - - // build the mesh without refining - buildMesh(false, 0); - - // interpret _properties to stored tetrahedra - // for (Tetra* tetra : _tetrahedra) - // { - // tetra->volume(); - // } - - // if a mass density policy has been set, calculate masses and densities and build the search data structure - if (hasMassDensityPolicy()) calculateDensityAndMass(); - if (hasMassDensityPolicy() || needGetEntities()) buildSearchPerBlock(); -} - -//////////////////////////////////////////////////////////////////// - -void TetraMeshSnapshot::setExtent(const Box& extent) -{ - _extent = extent; - _eps = 1e-12 * extent.widths().norm(); -} - -TetraMeshSnapshot::TetraMeshSnapshot(const SimulationItem* item, const Box& extent, string filename, bool refine, - double mindihedral) -{ - // read the input file - TextInFile in(item, filename, "Tetra vertices"); - in.addColumn("position x", "length", "pc"); - in.addColumn("position y", "length", "pc"); - in.addColumn("position z", "length", "pc"); - Array coords; - while (in.readRow(coords)) _sites.push_back(new Vec(coords[0], coords[1], coords[2])); - in.close(); - - // calculate the Tetra cells - setContext(item); - setExtent(extent); - buildMesh(refine, mindihedral); - buildSearchPerBlock(); -} - -//////////////////////////////////////////////////////////////////// - -TetraMeshSnapshot::TetraMeshSnapshot(const SimulationItem* item, const Box& extent, SiteListInterface* sli, bool refine, - double mindihedral) -{ - // prepare the data - int n = sli->numSites(); - _sites.resize(n); - for (int m = 0; m != n; ++m) _sites[m] = new Vec(sli->sitePosition(m)); - - // calculate the Tetra cells - setContext(item); - setExtent(extent); - buildMesh(refine, mindihedral); - buildSearchPerBlock(); -} - -//////////////////////////////////////////////////////////////////// - -TetraMeshSnapshot::TetraMeshSnapshot(const SimulationItem* item, const Box& extent, const vector& sites, - bool refine, double mindihedral) -{ - // prepare the data - int n = sites.size(); - _sites.resize(n); - for (int m = 0; m != n; ++m) _sites[m] = new Vec(sites[m]); - - // calculate the Tetra cells - setContext(item); - setExtent(extent); - buildMesh(refine, mindihedral); - buildSearchPerBlock(); -} - -//////////////////////////////////////////////////////////////////// - -TetraMeshSnapshot::TetraMeshSnapshot(const SimulationItem* item, const Box& extent, bool refine, double mindihedral) -{ - setContext(item); - setExtent(extent); - buildMesh(refine, mindihedral); - buildSearchPerBlock(); -} - -//////////////////////////////////////////////////////////////////// - -namespace -{ - // function to erase null pointers from a vector of pointers in one go; returns the new size - template size_t eraseNullPointers(vector& v) - { - // scan to the first null (or to the end) - auto from = v.begin(); - while (from != v.end() && *from) ++from; - - // copy the tail, overwriting any nulls - auto to = from; - while (from != v.end()) - { - if (*from) *to++ = *from; - ++from; - } - v.erase(to, v.end()); - return v.size(); - } -} - -void TetraMeshSnapshot::buildDelaunay(tetgenio& out) -{ - // remove sites outside of the domain - int numOutside = 0; - int numSites = _sites.size(); - for (int m = 0; m != numSites; ++m) - { - if (!_extent.contains(*_sites[m])) - { - delete _sites[m]; - _sites[m] = 0; - numOutside++; - } - } - if (numOutside) numSites = eraseNullPointers(_sites); - log()->info("removed " + StringUtils::toString(numOutside, 'd') + " vertices outside of the domain"); - - tetgenio in; - tetgenbehavior behavior; - - // in.firstnumber = 0; // remove me if no error - in.numberofpoints = _sites.size(); - in.pointlist = new REAL[in.numberofpoints * 3]; - for (int i = 0; i < in.numberofpoints; i++) - { - in.pointlist[i * 3 + 0] = _sites[i]->x(); - in.pointlist[i * 3 + 1] = _sites[i]->y(); - in.pointlist[i * 3 + 2] = _sites[i]->z(); - } - - behavior.psc = 1; // -s build Delaunay tetrahedralisation - // behavior.verbose = 1; // -v - - log()->info("Building Delaunay triangulation using input vertices..."); - tetrahedralize(&behavior, &in, &out); - log()->info("Built Delaunay triangulation"); -} - -void TetraMeshSnapshot::refineDelaunay(tetgenio& in, tetgenio& out, double mindihedral) -{ - tetgenbehavior behavior; - - // tetgen refine options - behavior.refine = 1; // -r - behavior.quality = 1; // -q - behavior.mindihedral = mindihedral; // -q - // correct output options for out - behavior.neighout = 2; // -nn - behavior.facesout = 1; // -f - behavior.zeroindex = 1; // -z - behavior.verbose = 1; // -v - - log()->info("Refining triangulation..."); - tetrahedralize(&behavior, &in, &out); - log()->info("Refined triangulation"); -} - -//////////////////////////////////////////////////////////////////// - -void TetraMeshSnapshot::buildMesh(bool refine, double mindihedral) -{ - tetgenio delaunay, refined; - buildDelaunay(delaunay); - if (refine) - { - refineDelaunay(delaunay, refined, mindihedral); - storeTetrahedra(refined); - } - else - { - storeTetrahedra(delaunay); - } -} - -void TetraMeshSnapshot::storeTetrahedra(const tetgenio& out) -{ - // tranfser TetGen data to TetraMeshSnapshot data containers - numTetra = out.numberoftetrahedra; - numVertices = out.numberofpoints; - - _vertices.resize(numVertices); - for (int i = 0; i < numVertices; i++) - { - double x = out.pointlist[3 * i + 0]; - double y = out.pointlist[3 * i + 1]; - double z = out.pointlist[3 * i + 2]; - - _vertices[i] = new Vec(x, y, z); - } - - _tetrahedra.resize(numTetra); - for (int i = 0; i < numTetra; i++) - { - std::array vertices; - std::array faces; - - // vertices - for (int c = 0; c < 4; c++) - { - vertices[c] = _vertices[out.tetrahedronlist[4 * i + c]]; - } - - // faces - for (int c = 0; c < 4; c++) - { - // -1 if no neighbor - int ntetra = out.neighborlist[4 * i + c]; - - // find which face is shared with neighbor - int nface = -1; - if (ntetra != -1) - { - for (int cn = 0; cn < 4; cn++) - { - if (out.neighborlist[4 * ntetra + cn] == i) - { - nface = cn; - break; - } - } - } - faces[c] = Face(ntetra, nface); - } - - _tetrahedra[i] = new Tetra(vertices, faces); - - _centroids.push_back(&_tetrahedra[i]->_centroid); - } - - // compile statistics - double minVol = DBL_MAX; - double maxVol = 0.; - double totalVol2 = 0.; - for (int m = 0; m < numTetra; m++) - { - double vol = _tetrahedra[m]->volume(); - totalVol2 += vol * vol; - minVol = min(minVol, vol); - maxVol = max(maxVol, vol); - } - double V = _extent.volume(); - minVol /= V; - maxVol /= V; - double avgVol = 1 / (double)numTetra; - double varVol = (totalVol2 / numTetra / (V * V) - avgVol * avgVol); - - // log neighbor statistics - log()->info("Done computing tetrahedralisation"); - log()->info(" Number of vertices " + std::to_string(numVertices)); - log()->info(" Number of tetrahedra " + std::to_string(numTetra)); - log()->info(" Average volume fraction per cell: " + StringUtils::toString(avgVol, 'e')); - log()->info(" Variance of volume fraction per cell: " + StringUtils::toString(varVol, 'e')); - log()->info(" Minimum volume fraction cell: " + StringUtils::toString(minVol, 'e')); - log()->info(" Maximum volume fraction cell: " + StringUtils::toString(maxVol, 'e')); -} - -//////////////////////////////////////////////////////////////////// - -void TetraMeshSnapshot::calculateDensityAndMass() -{ - // allocate vectors for mass and density - _rhov.resize(numTetra); - Array Mv(numTetra); - - // get the maximum temperature, or zero of there is none - double maxT = useTemperatureCutoff() ? maxTemperature() : 0.; - - // initialize statistics - double totalOriginalMass = 0; - double totalMetallicMass = 0; - double totalEffectiveMass = 0; - - // loop over all sites/cells - int numIgnored = 0; - for (int m = 0; m != numTetra; ++m) - { - const Array& prop = *_properties[m]; - - // original mass is zero if temperature is above cutoff or if imported mass/density is not positive - double originalDensity = 0.; - double originalMass = 0.; - if (maxT && prop[temperatureIndex()] > maxT) - { - numIgnored++; - } - else - { - double volume = _tetrahedra[m]->volume(); - originalDensity = max(0., densityIndex() >= 0 ? prop[densityIndex()] : prop[massIndex()] / volume); - originalMass = max(0., massIndex() >= 0 ? prop[massIndex()] : prop[densityIndex()] * volume); - } - - double effectiveDensity = originalDensity * (useMetallicity() ? prop[metallicityIndex()] : 1.) * multiplier(); - double metallicMass = originalMass * (useMetallicity() ? prop[metallicityIndex()] : 1.); - double effectiveMass = metallicMass * multiplier(); - - _rhov[m] = effectiveDensity; - Mv[m] = effectiveMass; - - totalOriginalMass += originalMass; - totalMetallicMass += metallicMass; - totalEffectiveMass += effectiveMass; - } - - // log mass statistics - logMassStatistics(numIgnored, totalOriginalMass, totalMetallicMass, totalEffectiveMass); - - // remember the effective mass - _mass = totalEffectiveMass; - - // construct a vector with the normalized cumulative site densities - if (numTetra) NR::cdf(_cumrhov, Mv); -} - -//////////////////////////////////////////////////////////////////// - -TetraMeshSnapshot::Node* TetraMeshSnapshot::buildTree(vector::iterator first, vector::iterator last, - int depth) const -{ - auto length = last - first; - if (length > 0) - { - auto median = length >> 1; - std::nth_element(first, first + median, last, [this, depth](int m1, int m2) { - return m1 != m2 && lessthan(*_centroids[m1], *_centroids[m2], depth % 3); - }); - return new TetraMeshSnapshot::Node(*(first + median), depth, buildTree(first, first + median, depth + 1), - buildTree(first + median + 1, last, depth + 1)); - } - return nullptr; -} - -//////////////////////////////////////////////////////////////////// - -void TetraMeshSnapshot::buildSearchPerBlock() -{ - // abort if there are no cells - if (!numTetra) return; - - log()->info("Building data structures to accelerate searching the tetrahedralisation"); - - // ------------- block lists ------------- - _nb = max(3, min(250, static_cast(cbrt(numTetra)))); - _nb2 = _nb * _nb; - _nb3 = _nb * _nb * _nb; - - // initialize a vector of nb * nb * nb lists - _blocklists.resize(_nb3); - - // we add the tetrahedra to all blocks they potentially overlap with - // this will slow down the search tree but if no search tree is present - // we can simply loop over all tetrahedra inside the block - int i1, j1, k1, i2, j2, k2; - for (int c = 0; c != numTetra; ++c) - { - _extent.cellIndices(i1, j1, k1, _tetrahedra[c]->rmin() - Vec(_eps, _eps, _eps), _nb, _nb, _nb); - _extent.cellIndices(i2, j2, k2, _tetrahedra[c]->rmax() + Vec(_eps, _eps, _eps), _nb, _nb, _nb); - for (int i = i1; i <= i2; i++) - for (int j = j1; j <= j2; j++) - for (int k = k1; k <= k2; k++) _blocklists[i * _nb2 + j * _nb + k].push_back(c); - } - - // compile block list statistics - int minRefsPerBlock = INT_MAX; - int maxRefsPerBlock = 0; - int64_t totalBlockRefs = 0; - for (int b = 0; b < _nb3; b++) - { - int refs = _blocklists[b].size(); - totalBlockRefs += refs; - minRefsPerBlock = min(minRefsPerBlock, refs); - maxRefsPerBlock = max(maxRefsPerBlock, refs); - } - double avgRefsPerBlock = double(totalBlockRefs) / _nb3; - - // log block list statistics - log()->info(" Number of blocks in search grid: " + std::to_string(_nb3) + " (" + std::to_string(_nb) + "^3)"); - log()->info(" Average number of cells per block: " + StringUtils::toString(avgRefsPerBlock, 'f', 1)); - log()->info(" Minimum number of cells per block: " + std::to_string(minRefsPerBlock)); - log()->info(" Maximum number of cells per block: " + std::to_string(maxRefsPerBlock)); - - // ------------- search trees ------------- - - // for each block that contains more than a predefined number of cells, - // construct a search tree on the site locations of the cells - _blocktrees.resize(_nb3); - for (int b = 0; b < _nb3; b++) - { - vector& ids = _blocklists[b]; - if (ids.size() > 9) _blocktrees[b] = buildTree(ids.begin(), ids.end(), 0); - } - - // compile and log search tree statistics - int numTrees = 0; - for (int b = 0; b < _nb3; b++) - if (_blocktrees[b]) numTrees++; - log()->info(" Number of search trees: " + std::to_string(numTrees) + " (" - + StringUtils::toString(100. * numTrees / _nb3, 'f', 1) + "% of blocks)"); -} - -//////////////////////////////////////////////////////////////////// - -void TetraMeshSnapshot::buildSearchSingle() -{ - // log the number of sites - log()->info(" Number of tetrahedra: " + std::to_string(numTetra)); - - // abort if there are no cells - if (!numTetra) return; - - // construct a single search tree on the site locations of all cells - log()->info("Building data structure to accelerate searching " + std::to_string(numTetra) + " tetrahedra"); - _blocktrees.resize(1); - vector ids(numTetra); - for (int m = 0; m != numTetra; ++m) ids[m] = m; - _blocktrees[0] = buildTree(ids.begin(), ids.end(), 0); -} - -//////////////////////////////////////////////////////////////////// - -void TetraMeshSnapshot::writeGridPlotFiles(const SimulationItem* probe) const -{ - // std::ofstream outputFile("data/tetrahedra.txt"); - // for (size_t i = 0; i < _tetrahedra.size(); i++) - // { - // const Tetra* tetra = _tetrahedra[i]; - // for (size_t l = 0; l < 4; l++) - // { - // const Vec* r = tetra->_vertices[l]; - // outputFile << r->x() << ", " << r->y() << ", " << r->z() << "\n"; - // } - // } - // outputFile.close(); - - // outputFile.open("data/faces.txt"); - // for (size_t i = 0; i < _tetrahedra.size(); i++) - // { - // const Tetra* tetra = _tetrahedra[i]; - // bool out = false; - // for (int f = 0; f < 4; f++) - // { - // if (tetra->_faces[f]._ntetra < 0) - // { - // for (int v : tetra->clockwiseVertices(f)) - // { - // const Vec* r = tetra->_vertices[v]; - // outputFile << r->x() << ", " << r->y() << ", " << r->z() << "\n"; - // } - // } - // } - // } - // outputFile.close(); - - // create the plot files - SpatialGridPlotFile plotxy(probe, probe->itemName() + "_grid_xy"); - SpatialGridPlotFile plotxz(probe, probe->itemName() + "_grid_xz"); - SpatialGridPlotFile plotyz(probe, probe->itemName() + "_grid_yz"); - SpatialGridPlotFile plotxyz(probe, probe->itemName() + "_grid_xyz"); - - // for each site, compute the corresponding cell and output its edges - log()->info("Writing plot files for tetrahedralisation with " + std::to_string(numTetra) + " tetrahedra"); - log()->infoSetElapsed(numTetra); - int numDone = 0; - for (int i = 0; i < numTetra; i++) - { - const Tetra* tetra = _tetrahedra[i]; - vector coords; - coords.reserve(12); - vector indices; - indices.reserve(16); - - for (int v = 0; v < 4; v++) - { - const Vec* vertex = tetra->_vertices[v]; - coords.push_back(vertex->x()); - coords.push_back(vertex->y()); - coords.push_back(vertex->z()); - - // get vertices of opposite face - std::array faceIndices = clockwiseVertices(v); - indices.push_back(3); // amount of vertices per face - indices.push_back(faceIndices[0]); - indices.push_back(faceIndices[1]); - indices.push_back(faceIndices[2]); - } - - if (tetra->zmin() <= 0 && tetra->zmax() >= 0) plotxy.writePolyhedron(coords, indices); - if (tetra->ymin() <= 0 && tetra->ymax() >= 0) plotxz.writePolyhedron(coords, indices); - if (tetra->xmin() <= 0 && tetra->xmax() >= 0) plotyz.writePolyhedron(coords, indices); - if (i <= 1000) plotxyz.writePolyhedron(coords, indices); // like TetraMeshSnapshot, but why even write at all? - - // log message if the minimum time has elapsed - numDone++; - if (numDone % 2000 == 0) log()->infoIfElapsed("Computed tetrehedra: ", 2000); - } -} - -//////////////////////////////////////////////////////////////////// - -Box TetraMeshSnapshot::extent() const -{ - return _extent; -} - -//////////////////////////////////////////////////////////////////// - -int TetraMeshSnapshot::numEntities() const -{ - return _tetrahedra.size(); -} - -//////////////////////////////////////////////////////////////////// - -Position TetraMeshSnapshot::position(int m) const -{ - return Position(_tetrahedra[m]->_centroid); -} - -//////////////////////////////////////////////////////////////////// - -double TetraMeshSnapshot::volume(int m) const -{ - return _tetrahedra[m]->volume(); -} - -//////////////////////////////////////////////////////////////////// - -Box TetraMeshSnapshot::extent(int m) const -{ - return _tetrahedra[m]->extent(); -} - -//////////////////////////////////////////////////////////////////// - -double TetraMeshSnapshot::density(int m) const -{ - return _rhov[m]; -} - -//////////////////////////////////////////////////////////////////// - -double TetraMeshSnapshot::density(Position bfr) const -{ - int m = cellIndex(bfr); - return m >= 0 ? _rhov[m] : 0; -} - -//////////////////////////////////////////////////////////////////// - -double TetraMeshSnapshot::mass() const -{ - return _mass; -} - -//////////////////////////////////////////////////////////////////// - -Position TetraMeshSnapshot::generatePosition(int m) const -{ - double s = random()->uniform(); - double t = random()->uniform(); - double u = random()->uniform(); - return _tetrahedra[m]->generatePosition(s, t, u); -} - -//////////////////////////////////////////////////////////////////// - -Position TetraMeshSnapshot::generatePosition() const -{ - // if there are no sites, return the origin - if (_tetrahedra.empty()) return Position(); - - // select a site according to its mass contribution - int m = NR::locateClip(_cumrhov, random()->uniform()); - - return generatePosition(m); -} - -//////////////////////////////////////////////////////////////////// - -int TetraMeshSnapshot::cellIndex(Position bfr) const -{ - // make sure the position is inside the domain - if (!_extent.contains(bfr)) return -1; - - // determine the block in which the point falls - int i, j, k; - _extent.cellIndices(i, j, k, bfr, _nb, _nb, _nb); - int b = i * _nb2 + j * _nb + k; - - // look for the closest centroid in this block using the search tree - Node* tree = _blocktrees[b]; - if (tree) - { - // /* - // full traversal algorithm - int enteringFace = -1; - int leavingFace = -1; - int m = tree->m(); - - const Tetra* tetra = _tetrahedra[m]; - - Vec pos = tetra->_centroid; - Direction dir(bfr - tetra->_centroid); - double dist = dir.norm(); // keep subtracting ds until dist < 0 - dir /= dist; - - while (true) - { - tetra = _tetrahedra[m]; - - // find entering face using a single Plücker product - if (enteringFace == -1) - { - enteringFace = tetra->findEnteringFace(pos, dir); - if (enteringFace == -1) break; - } - - // the translated Plücker moment in the local coordinate system - Vec moment = Vec::cross(dir, pos - *tetra->_vertices[enteringFace]); - std::array cv = clockwiseVertices(enteringFace); - - // 2 step decision tree - double prod0 = Vec::dot(moment, tetra->getEdge(cv[0], enteringFace)); - int clock0 = prod0 < 0; - // if clockwise move clockwise else move cclockwise - int i = clock0 ? 1 : 2; - double prodi = Vec::dot(moment, tetra->getEdge(cv[i], enteringFace)); - int cclocki = prodi >= 0; - - double ds = DBL_MAX; - - // use plane intersection algorithm if Plücker products are ambiguous - if (prod0 == 0. || prodi == 0.) - { - for (int face : cv) - { - const Vec& n = tetra->_faces[face]._normal; - double ndotk = Vec::dot(n, dir); - if (ndotk > 0) - { - const Vec& v = *tetra->_vertices[enteringFace]; - double dq = Vec::dot(n, v - pos) / ndotk; - if (dq < ds) - { - ds = dq; - leavingFace = face; - } - } - } - } - // use Maria (2017) algorithm otherwise - else - { - // decision table for clock0 and cclocki - // 1 1 -> 2 - // 0 0 -> 1 - // 1 0 -> 0 - // 0 1 -> 0 - constexpr int dtable[2][2] = {{1, 0}, {0, 2}}; - leavingFace = cv[dtable[clock0][cclocki]]; - const Vec& n = tetra->_faces[leavingFace]._normal; - const Vec& v = *tetra->_vertices[enteringFace]; - double ndotk = Vec::dot(n, dir); - - ds = Vec::dot(n, v - pos) / ndotk; - } - - // if ds is too close to leaving face we recalculate cellIndex to avoid traversing when ds ~ 0 - // this might actually slow down the traversal - - // if no exit point was found, advance the current point by a small distance, - // recalculate the cell index, and return to the start of the loop - if (leavingFace == -1) - { - break; // traversal failed - } - // otherwise set the current point to the exit point and return the path segment - else - { - pos += ds * dir; - dist -= ds; - if (dist <= 0) return m; - - m = tetra->_faces[leavingFace]._ntetra; - enteringFace = tetra->_faces[leavingFace]._nface; - - if (m < 0) break; - } - } - } - - // if there is no search tree or search tree failed, simply loop over all tetrahedra in the block - for (int t : _blocklists[b]) - { - if (_tetrahedra[t]->inside(bfr)) return t; - } - - // log()->error("cellIndex failed to find the tetrahedron"); // change to warning? - return -1; -} - -//////////////////////////////////////////////////////////////////// - -const Array& TetraMeshSnapshot::properties(int m) const -{ - return *_properties[m]; -} - -//////////////////////////////////////////////////////////////////// - -int TetraMeshSnapshot::nearestEntity(Position bfr) const -{ - return cellIndex(bfr); -} - -//////////////////////////////////////////////////////////////////// - -void TetraMeshSnapshot::getEntities(EntityCollection& entities, Position bfr) const -{ - entities.addSingle(cellIndex(bfr)); -} - -//////////////////////////////////////////////////////////////////// - -class TetraMeshSnapshot::MySegmentGenerator : public PathSegmentGenerator -{ - const TetraMeshSnapshot* _grid{nullptr}; - int _mr; - int _enteringFace; - -public: - MySegmentGenerator(const TetraMeshSnapshot* grid) : _grid(grid) {} - - bool next() override - { - if (state() == State::Unknown) - { - // try moving the photon packet inside the grid; if this is impossible, return an empty path - // this also changes the _state - if (!moveInside(_grid->extent(), _grid->_eps)) return false; - - // get the index of the cell containing the current position - _mr = _grid->cellIndex(r()); - _enteringFace = -1; - - // if the photon packet started outside the grid, return the corresponding nonzero-length segment; - // otherwise fall through to determine the first actual segment - if (ds() > 0.) return true; - } - - // intentionally falls through - if (state() == State::Inside) - { - int leavingFace = -1; - // loop in case no exit point was found (which should happen only rarely) - while (true) - { - const Tetra* tetra = _grid->_tetrahedra[_mr]; - Position pos = r(); - Direction dir = k(); - - // find entering face using a single Plücker product - if (_enteringFace == -1) - { - _enteringFace = tetra->findEnteringFace(pos, dir); - } - - // the translated Plücker moment in the local coordinate system - Vec moment = Vec::cross(dir, pos - *tetra->_vertices[_enteringFace]); - std::array cv = clockwiseVertices(_enteringFace); - - // 2 step decision tree - double prod0 = Vec::dot(moment, tetra->getEdge(cv[0], _enteringFace)); - int clock0 = prod0 < 0; - // if clockwise move clockwise else move cclockwise - int i = clock0 ? 1 : 2; - double prodi = Vec::dot(moment, tetra->getEdge(cv[i], _enteringFace)); - int cclocki = prodi >= 0; - - double ds = DBL_MAX; - - // use plane intersection algorithm if Plücker products are ambiguous - if (prod0 == 0. || prodi == 0.) - { - for (int face : cv) - { - const Vec& n = tetra->_faces[face]._normal; - double ndotk = Vec::dot(n, dir); - if (ndotk > 0) - { - const Vec& v = *tetra->_vertices[_enteringFace]; - double dq = Vec::dot(n, v - pos) / ndotk; - if (dq < ds) - { - ds = dq; - leavingFace = face; - } - } - } - } - // use Maria (2017) algorithm otherwise - else - { - // decision table for clock0 and cclocki - // 1 1 -> 2 - // 0 0 -> 1 - // 1 0 -> 0 - // 0 1 -> 0 - constexpr int dtable[2][2] = {{1, 0}, {0, 2}}; - leavingFace = cv[dtable[clock0][cclocki]]; - const Vec& n = tetra->_faces[leavingFace]._normal; - const Vec& v = *tetra->_vertices[_enteringFace]; - double ndotk = Vec::dot(n, dir); - ds = Vec::dot(n, v - pos) / ndotk; - } - // if no exit point was found, advance the current point by a small distance, - // recalculate the cell index, and return to the start of the loop - if (leavingFace == -1 || ds < _grid->_eps) - { - propagater(_grid->_eps); - _mr = _grid->cellIndex(r()); - - if (_mr < 0) - { - setState(State::Outside); - return false; - } - else - { - _enteringFace = -1; - } - } - // otherwise set the current point to the exit point and return the path segment - else - { - propagater(ds); - setSegment(_mr, ds); - _mr = tetra->_faces[leavingFace]._ntetra; - - if (_mr < 0) - { - setState(State::Outside); - return false; - } - else - { - _enteringFace = tetra->_faces[leavingFace]._nface; - return true; - } - } - } - } - - if (state() == State::Outside) - { - } - - return false; - } -}; - -//////////////////////////////////////////////////////////////////// - -void TetraMeshSnapshot::getEntities(EntityCollection& entities, Position bfr, Direction bfk) const -{ - // initialize a path segment generator - MySegmentGenerator generator(this); - generator.start(bfr, bfk); - - // determine and store the path segments in the entity collection - entities.clear(); - while (generator.next()) - { - entities.add(generator.m(), generator.ds()); - } -} - -//////////////////////////////////////////////////////////////////// - -std::unique_ptr TetraMeshSnapshot::createPathSegmentGenerator() const -{ - return std::make_unique(this); -} - -//////////////////////////////////////////////////////////////////// diff --git a/SKIRT/core/TetraMeshSnapshot.hpp b/SKIRT/core/TetraMeshSnapshot.hpp index 7f8e9941..47dde464 100644 --- a/SKIRT/core/TetraMeshSnapshot.hpp +++ b/SKIRT/core/TetraMeshSnapshot.hpp @@ -77,7 +77,6 @@ class TetraMeshSnapshot : public Snapshot private: class Node; - class Face; class Tetra; diff --git a/SKIRT/core/TetraMeshSource.cpp b/SKIRT/core/TetraMeshSource.cpp deleted file mode 100644 index 25bd57d5..00000000 --- a/SKIRT/core/TetraMeshSource.cpp +++ /dev/null @@ -1,28 +0,0 @@ -/*////////////////////////////////////////////////////////////////// -//// The SKIRT project -- advanced radiative transfer //// -//// © Astronomical Observatory, Ghent University //// -///////////////////////////////////////////////////////////////// */ - -#include "TetraMeshSource.hpp" -#include "TetraMeshSnapshot.hpp" - -//////////////////////////////////////////////////////////////////// - -Snapshot* TetraMeshSource::createAndOpenSnapshot() -{ - // create and open the snapshot - auto snapshot = new TetraMeshSnapshot; - snapshot->open(this, filename(), "Tetra source sites"); - - // honor custom column reordering - snapshot->useColumns(useColumns()); - - // configure the position columns - snapshot->importPosition(); - - // set the domain extent - snapshot->setExtent(domain()); - return snapshot; -} - -//////////////////////////////////////////////////////////////////// diff --git a/SKIRT/core/TetraMeshSource.hpp b/SKIRT/core/TetraMeshSource.hpp deleted file mode 100644 index f599c6b0..00000000 --- a/SKIRT/core/TetraMeshSource.hpp +++ /dev/null @@ -1,58 +0,0 @@ -/*////////////////////////////////////////////////////////////////// -//// The SKIRT project -- advanced radiative transfer //// -//// © Astronomical Observatory, Ghent University //// -///////////////////////////////////////////////////////////////// */ - -#ifndef TETRAMESHSOURCE_HPP -#define TETRAMESHSOURCE_HPP - -#include "MeshSource.hpp" - -//////////////////////////////////////////////////////////////////// - -/** A TetraMeshSource instance represents a primary radiation source with a spatial and spectral - luminosity distribution described by a list of sites generating a Tetra tesselation of a - cubodail domain. The data is usually extracted from a cosmological simulation snapshot, and it - must be provided in a column text file formatted as described below. - - Refer to the description of the TextInFile class for information on overall formatting and on - how to include header lines specifying the units for each column in the input file. In case the - input file has no unit specifications, the default units mentioned below are used instead. The - number of columns expected in the input file depends on the options configured by the user for - this TetraMeshSource instance, including the selected %SEDFamily. - - \f[ x\,(\mathrm{pc}) \quad y\,(\mathrm{pc}) \quad z\,(\mathrm{pc}) \quad [ v_x\,(\mathrm{km/s}) - \quad v_y\,(\mathrm{km/s}) \quad v_z\,(\mathrm{km/s}) \quad [ \sigma_v\,(\mathrm{km/s}) ] ] - \quad [M_\mathrm{curr}\,(\mathrm{M}_\odot)] \quad \dots \text{SED family parameters}\dots \f] - - The first three columns are the \f$x\f$, \f$y\f$ and \f$z\f$ coordinates of the Tetra site - (i.e. the location defining a particular Tetra cell). If the \em importVelocity option is - enabled, the next three columns specify the \f$v_x\f$, \f$v_y\f$, \f$v_z\f$ bulk velocity - components of the source population represented by the cell corresponding to the site. If - additionally the \em importVelocityDispersion option is enabled, the next column specifies the - velocity dispersion \f$\sigma_v\f$, adjusting the velocity for each photon packet launch with a - random offset sampled from a spherically symmetric Gaussian distribution. - - The remaining columns specify the parameters required by the configured %SED family to select - and scale the appropriate %SED. For example for the Bruzual-Charlot %SED family, the remaining - columns provide the initial mass, the metallicity, and the age of the stellar population - represented by the cell corresponding to the site. Refer to the documentation of the configured - type of SEDFamily for information about the expected parameters and their default units. */ -class TetraMeshSource : public MeshSource -{ - ITEM_CONCRETE(TetraMeshSource, MeshSource, "a primary source imported from data represented on a Tetra mesh") - ITEM_END() - - //============= Construction - Setup - Destruction ============= - -protected: - /** This function constructs a new TetraMeshSnapshot object, calls its open() function, - passes it the domain extent configured by the user (using properties offered by the - MeshSource base class), and returns a pointer to the object. Ownership of the Snapshot - object is transferred to the caller. */ - Snapshot* createAndOpenSnapshot() override; -}; - -//////////////////////////////////////////////////////////////////// - -#endif diff --git a/SKIRT/core/TetraMeshSpatialGrid.cpp b/SKIRT/core/TetraMeshSpatialGrid.cpp index e9b9293e..5efec924 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.cpp +++ b/SKIRT/core/TetraMeshSpatialGrid.cpp @@ -12,8 +12,10 @@ #include "PathSegmentGenerator.hpp" #include "Random.hpp" #include "SiteListInterface.hpp" +#include "SpatialGridPlotFile.hpp" +#include "StringUtils.hpp" #include "TetraMeshInterface.hpp" -#include "TetraMeshSnapshot.hpp" +#include "tetgen.h" ////////////////////////////////////////////////////////////////////// @@ -23,6 +25,24 @@ TetraMeshSpatialGrid::~TetraMeshSpatialGrid() {} namespace { + // function to erase null pointers from a vector of pointers in one go; returns the new size + template size_t eraseNullPointers(vector& v) + { + // scan to the first null (or to the end) + auto from = v.begin(); + while (from != v.end() && *from) ++from; + + // copy the tail, overwriting any nulls + auto to = from; + while (from != v.end()) + { + if (*from) *to++ = *from; + ++from; + } + v.erase(to, v.end()); + return v.size(); + } + // sample random positions from the given media components with the given relative weigths vector sampleMedia(const vector& media, const vector& weights, const Box& extent, int numSites) @@ -44,23 +64,481 @@ namespace } return rv; } + + bool lessthan(Vec p1, Vec p2, int axis) + { + switch (axis) + { + case 0: // split on x + if (p1.x() < p2.x()) return true; + if (p1.x() > p2.x()) return false; + if (p1.y() < p2.y()) return true; + if (p1.y() > p2.y()) return false; + if (p1.z() < p2.z()) return true; + return false; + case 1: // split on y + if (p1.y() < p2.y()) return true; + if (p1.y() > p2.y()) return false; + if (p1.z() < p2.z()) return true; + if (p1.z() > p2.z()) return false; + if (p1.x() < p2.x()) return true; + return false; + case 2: // split on z + if (p1.z() < p2.z()) return true; + if (p1.z() > p2.z()) return false; + if (p1.x() < p2.x()) return true; + if (p1.x() > p2.x()) return false; + if (p1.y() < p2.y()) return true; + return false; + default: // this should never happen + return false; + } + } + + std::array clockwiseVertices(int face) + { + std::array cv = {(face + 3) % 4, (face + 2) % 4, (face + 1) % 4}; + // if face is even we should swap two edges + if (face % 2 == 0) std::swap(cv[0], cv[2]); + return cv; + } } -////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////// + +struct TetraMeshSpatialGrid::Face +{ + Face() {} + + // normals are calculated in the constructor of Tetra + Face(int ntetra, int nface) : _ntetra(ntetra), _nface(nface) {} + + // setters & getters required? + + Vec _normal; + int _ntetra; // index of neighbouring tetrahedron + int _nface; // neighbouring face index +}; + +//////////////////////////////////////////////////////////////////// + +class TetraMeshSpatialGrid::Tetra : public Box +{ +private: + double _volume; + +public: + const std::array _vertices; + std::array _faces; + Vec _centroid; + +public: + Tetra(const std::array& vertices, const std::array& faces) : _vertices(vertices), _faces(faces) + { + double xmin = DBL_MAX; + double ymin = DBL_MAX; + double zmin = DBL_MAX; + double xmax = -DBL_MAX; + double ymax = -DBL_MAX; + double zmax = -DBL_MAX; + for (const Vec* vertex : _vertices) + { + xmin = min(xmin, vertex->x()); + ymin = min(ymin, vertex->y()); + zmin = min(zmin, vertex->z()); + xmax = max(xmax, vertex->x()); + ymax = max(ymax, vertex->y()); + zmax = max(zmax, vertex->z()); + } + setExtent(Box(xmin, ymin, zmin, xmax, ymax, zmax)); + + // volume + _volume = 1 / 6. * abs(Vec::dot(Vec::cross(getEdge(0, 1), getEdge(0, 2)), getEdge(0, 3))); + + // barycenter + for (int i = 0; i < 4; i++) _centroid += *_vertices[i]; + _centroid /= 4; + + // calculate normal facing out + for (int f = 0; f < 4; f++) + { + std::array cv = clockwiseVertices(f); + Vec e12 = getEdge(cv[0], cv[1]); + Vec e13 = getEdge(cv[0], cv[2]); + Vec normal = Vec::cross(e12, e13); + normal /= normal.norm(); + + Face& face = _faces[f]; + face._normal = normal; + } + } + + std::pair traverse(Vec pos, Direction dir, int& enteringFace) const + { + int leavingFace = -1; + double ds = DBL_MAX; + // loop in case no exit point was found (which should happen only rarely) + + // find entering face using a single Plücker product + if (enteringFace == -1) enteringFace = findEnteringFace(pos, dir); + + // the translated Plücker moment in the local coordinate system + Vec moment = Vec::cross(dir, pos - *_vertices[enteringFace]); + std::array cv = clockwiseVertices(enteringFace); + + // 2 step decision tree + double prod0 = Vec::dot(moment, getEdge(cv[0], enteringFace)); + int clock0 = prod0 < 0; + // if clockwise move clockwise else move cclockwise + int i = clock0 ? 1 : 2; + double prodi = Vec::dot(moment, getEdge(cv[i], enteringFace)); + int cclocki = prodi >= 0; + + // use plane intersection algorithm if Plücker products are ambiguous + if (prod0 == 0. || prodi == 0.) + { + for (int face : cv) + { + const Vec& n = _faces[face]._normal; + double ndotk = Vec::dot(n, dir); + if (ndotk > 0) + { + const Vec& v = *_vertices[enteringFace]; + double dq = Vec::dot(n, v - pos) / ndotk; + if (dq < ds) + { + ds = dq; + leavingFace = face; + } + } + } + } + // use Maria (2017) algorithm otherwise + else + { + // decision table for clock0 and cclocki + // 1 1 -> 2 + // 0 0 -> 1 + // 1 0 -> 0 + // 0 1 -> 0 + constexpr int dtable[2][2] = {{1, 0}, {0, 2}}; + leavingFace = cv[dtable[clock0][cclocki]]; + const Vec& n = _faces[leavingFace]._normal; + const Vec& v = *_vertices[enteringFace]; + double ndotk = Vec::dot(n, dir); + ds = Vec::dot(n, v - pos) / ndotk; + } + + return std::make_pair(ds, leavingFace); + } + + //////////////////////////////////////////////////////////////////// + + Vec getEdge(int t1, int t2) const { return *_vertices[t2] - *_vertices[t1]; } + + //////////////////////////////////////////////////////////////////// + + bool inside(const Position& bfr) const + { + // since we use the k-d tree this will only slow the CellIndex + // if (!Box::contains(bfr)) return false; + + // could optimize this slightly by using same vertex for 3 faces and do final face seperately + + for (int f = 0; f < 4; f++) + { + const Face& face = _faces[f]; + const Vec* vertex = _vertices[(f + 1) % 4]; // any vertex that is on the face + + // if point->face is opposite direction as the outward pointing normal, the point is outside + if (Vec::dot(*vertex - bfr, face._normal) < 0) return false; + } + return true; + } + + //////////////////////////////////////////////////////////////////// + + double volume() const { return _volume; } + + //////////////////////////////////////////////////////////////////// + + double generateBarycentric(double& s, double& t, double& u) const + { + // https://vcg.isti.cnr.it/activities/OLD/geometryegraphics/pointintetraedro.html + if (s + t > 1.0) + { // cut'n fold the cube into a prism + s = 1.0 - s; + t = 1.0 - t; + } + if (t + u > 1.0) + { // cut'n fold the prism into a tetrahedron + double tmp = u; + u = 1.0 - s - t; + t = 1.0 - tmp; + } + else if (s + t + u > 1.0) + { + double tmp = u; + u = s + t + u - 1.0; + s = 1 - t - tmp; + } + return 1 - u - t - s; + } + + //////////////////////////////////////////////////////////////////// + + Position generatePosition(double s, double t, double u) const + { + double r = generateBarycentric(s, t, u); + + return Position(r * *_vertices[0] + u * *_vertices[1] + t * *_vertices[2] + s * *_vertices[3]); + } + + //////////////////////////////////////////////////////////////////// + + int findEnteringFace(const Vec& pos, const Direction& dir) const + { + int enteringFace = -1; + // clockwise and cclockwise adjacent faces when checking edge v1->v2 + constexpr int etable[6][2] = {{3, 2}, {1, 3}, {2, 1}, {3, 0}, {0, 2}, {1, 0}}; + // try all 6 edges because of rare edge cases where ray is inside edge + // having only 1 non-zero Plücker product + int e = 0; + for (int v1 = 0; v1 < 3; v1++) + { + for (int v2 = v1 + 1; v2 < 4; v2++) + { + + Vec moment12 = Vec::cross(dir, pos - *_vertices[v1]); + double prod12 = Vec::dot(moment12, getEdge(v1, v2)); + if (prod12 != 0.) + { + enteringFace = prod12 < 0 ? etable[e][0] : etable[e][1]; + break; + } + e++; + } + if (enteringFace != -1) break; + } + return enteringFace; + } +}; + +//////////////////////////////////////////////////////////////////// + +class TetraMeshSpatialGrid::Node +{ +private: + int _m; // index in _cells to the site defining the split at this node + int _axis; // split axis for this node (0,1,2) + Node* _up; // ptr to the parent node + Node* _left; // ptr to the left child node + Node* _right; // ptr to the right child node + + // returns the square of its argument + static double sqr(double x) { return x * x; } + +public: + // constructor stores the specified site index and child pointers (which may be null) + Node(int m, int depth, Node* left, Node* right) : _m(m), _axis(depth % 3), _up(0), _left(left), _right(right) + { + if (_left) _left->setParent(this); + if (_right) _right->setParent(this); + } + + // destructor destroys the children + ~Node() + { + delete _left; + delete _right; + } + + // sets parent pointer (called from parent's constructor) + void setParent(Node* up) { _up = up; } + + // returns the corresponding data member + int m() const { return _m; } + Node* up() const { return _up; } + Node* left() const { return _left; } + Node* right() const { return _right; } + + // returns the apropriate child for the specified query point + Node* child(Vec bfr, const vector& points) const + { + return lessthan(bfr, *points[_m], _axis) ? _left : _right; + } + + // returns the other child than the one that would be apropriate for the specified query point + Node* otherChild(Vec bfr, const vector& points) const + { + return lessthan(bfr, *points[_m], _axis) ? _right : _left; + } + + // returns the squared distance from the query point to the split plane + double squaredDistanceToSplitPlane(Vec bfr, const vector& points) const + { + switch (_axis) + { + case 0: // split on x + return sqr(points[_m]->x() - bfr.x()); + case 1: // split on y + return sqr(points[_m]->y() - bfr.y()); + case 2: // split on z + return sqr(points[_m]->z() - bfr.z()); + default: // this should never happen + return 0; + } + } + + // returns the node in this subtree that represents the site nearest to the query point + Node* nearest(Vec bfr, const vector& points) + { + // recursively descend the tree until a leaf node is reached, going left or right depending on + // whether the specified point is less than or greater than the current node in the split dimension + Node* current = this; + while (Node* child = current->child(bfr, points)) current = child; + + // unwind the recursion, looking for the nearest node while climbing up + Node* best = current; + double bestSD = (*points[best->m()] - bfr).norm2(); + while (true) + { + // if the current node is closer than the current best, then it becomes the current best + double currentSD = (*points[current->m()] - bfr).norm2(); + if (currentSD < bestSD) + { + best = current; + bestSD = currentSD; + } + + // if there could be points on the other side of the splitting plane for the current node + // that are closer to the search point than the current best, then ... + double splitSD = current->squaredDistanceToSplitPlane(bfr, points); + if (splitSD < bestSD) + { + // move down the other branch of the tree from the current node looking for closer points, + // following the same recursive process as the entire search + Node* other = current->otherChild(bfr, points); + if (other) + { + Node* otherBest = other->nearest(bfr, points); + double otherBestSD = (*points[otherBest->m()] - bfr).norm2(); + if (otherBestSD < bestSD) + { + best = otherBest; + bestSD = otherBestSD; + } + } + } + + // move up to the parent until we meet the top node + if (current == this) break; + current = current->up(); + } + return best; + } +}; + +TetraMeshSpatialGrid::Node* TetraMeshSpatialGrid::buildTree(vector::iterator first, vector::iterator last, + int depth) const +{ + auto length = last - first; + if (length > 0) + { + auto median = length >> 1; + std::nth_element(first, first + median, last, [this, depth](int m1, int m2) { + return m1 != m2 && lessthan(*_centroids[m1], *_centroids[m2], depth % 3); + }); + return new TetraMeshSpatialGrid::Node(*(first + median), depth, buildTree(first, first + median, depth + 1), + buildTree(first + median + 1, last, depth + 1)); + } + return nullptr; +} + +//////////////////////////////////////////////////////////////////// + +void TetraMeshSpatialGrid::buildSearchPerBlock() +{ + // abort if there are no cells + if (!_numTetra) return; + + _log->info("Building data structures to accelerate searching the tetrahedralisation"); + + // ------------- block lists ------------- + _nb = max(3, min(250, static_cast(cbrt(_numTetra)))); + _nb2 = _nb * _nb; + _nb3 = _nb * _nb * _nb; + + // initialize a vector of nb * nb * nb lists + _blocklists.resize(_nb3); + + // we add the tetrahedra to all blocks they potentially overlap with + // this will slow down the search tree but if no search tree is present + // we can simply loop over all tetrahedra inside the block + int i1, j1, k1, i2, j2, k2; + for (int c = 0; c != _numTetra; ++c) + { + cellIndices(i1, j1, k1, _tetrahedra[c]->rmin() - Vec(_eps, _eps, _eps), _nb, _nb, _nb); + cellIndices(i2, j2, k2, _tetrahedra[c]->rmax() + Vec(_eps, _eps, _eps), _nb, _nb, _nb); + for (int i = i1; i <= i2; i++) + for (int j = j1; j <= j2; j++) + for (int k = k1; k <= k2; k++) _blocklists[i * _nb2 + j * _nb + k].push_back(c); + } + + // compile block list statistics + int minRefsPerBlock = INT_MAX; + int maxRefsPerBlock = 0; + int64_t totalBlockRefs = 0; + for (int b = 0; b < _nb3; b++) + { + int refs = _blocklists[b].size(); + totalBlockRefs += refs; + minRefsPerBlock = min(minRefsPerBlock, refs); + maxRefsPerBlock = max(maxRefsPerBlock, refs); + } + double avgRefsPerBlock = double(totalBlockRefs) / _nb3; + + // log block list statistics + _log->info(" Number of blocks in search grid: " + std::to_string(_nb3) + " (" + std::to_string(_nb) + "^3)"); + _log->info(" Average number of cells per block: " + StringUtils::toString(avgRefsPerBlock, 'f', 1)); + _log->info(" Minimum number of cells per block: " + std::to_string(minRefsPerBlock)); + _log->info(" Maximum number of cells per block: " + std::to_string(maxRefsPerBlock)); + + // ------------- search trees ------------- + + // for each block that contains more than a predefined number of cells, + // construct a search tree on the site locations of the cells + _blocktrees.resize(_nb3); + for (int b = 0; b < _nb3; b++) + { + vector& ids = _blocklists[b]; + if (ids.size() > 9) _blocktrees[b] = buildTree(ids.begin(), ids.end(), 0); + } + + // compile and log search tree statistics + int numTrees = 0; + for (int b = 0; b < _nb3; b++) + if (_blocktrees[b]) numTrees++; + _log->info(" Number of search trees: " + std::to_string(numTrees) + " (" + + StringUtils::toString(100. * numTrees / _nb3, 'f', 1) + "% of blocks)"); +} + +//////////////////////////////////////////////////////////////////// void TetraMeshSpatialGrid::setupSelfBefore() { BoxSpatialGrid::setupSelfBefore(); + _log = find(); + // determine an appropriate set of sites and construct the Tetra mesh switch (_policy) { case Policy::Uniform: { auto random = find(); - vector rv(_numSites); - for (int m = 0; m != _numSites; ++m) rv[m] = random->position(extent()); - _mesh = new TetraMeshSnapshot(this, extent(), rv, _refine, _mindihedral); + _vertices.resize(_numSites); + for (int m = 0; m != _numSites; ++m) _vertices[m] = new Vec((random->position(extent()))); break; } case Policy::CentralPeak: @@ -68,15 +546,15 @@ void TetraMeshSpatialGrid::setupSelfBefore() auto random = find(); const int a = 1000; // steepness of the peak; the central 1/a portion is NOT covered const double rscale = extent().rmax().norm(); - vector rv(_numSites); + _vertices.resize(_numSites); + _vertices[0] = new Vec(0, 0, 0); for (int m = 1; m != _numSites;) // skip first particle so that it remains (0,0,0) { double r = rscale * pow(1. / a, random->uniform()); // random distribution according to 1/x Direction k = random->direction(); - Position p = Position(r, k); - if (extent().contains(p)) rv[m++] = p; // discard any points outside of the domain + Vec p = Position(r, k); + if (extent().contains(p)) _vertices[m++] = new Vec(p); // discard any points outside of the domain } - _mesh = new TetraMeshSnapshot(this, extent(), rv, _refine, _mindihedral); break; } case Policy::DustDensity: @@ -88,8 +566,10 @@ void TetraMeshSpatialGrid::setupSelfBefore() for (auto medium : ms->media()) if (medium->mix()->isDust()) media.push_back(medium); for (auto medium : media) weights.push_back(medium->mass()); - _mesh = - new TetraMeshSnapshot(this, extent(), sampleMedia(media, weights, extent(), _numSites), _refine, _mindihedral); + vector sites = sampleMedia(media, weights, extent(), _numSites); + int n = sites.size(); + _vertices.resize(n); + for (int m = 0; m != n; ++m) _vertices[m] = new Vec(sites[m]); break; } case Policy::ElectronDensity: @@ -101,8 +581,10 @@ void TetraMeshSpatialGrid::setupSelfBefore() for (auto medium : ms->media()) if (medium->mix()->isElectrons()) media.push_back(medium); for (auto medium : media) weights.push_back(medium->number()); - _mesh = - new TetraMeshSnapshot(this, extent(), sampleMedia(media, weights, extent(), _numSites), _refine, _mindihedral); + vector sites = sampleMedia(media, weights, extent(), _numSites); + int n = sites.size(); + _vertices.resize(n); + for (int m = 0; m != n; ++m) _vertices[m] = new Vec(sites[m]); break; } case Policy::GasDensity: @@ -114,95 +596,297 @@ void TetraMeshSpatialGrid::setupSelfBefore() for (auto medium : ms->media()) if (medium->mix()->isGas()) media.push_back(medium); for (auto medium : media) weights.push_back(medium->number()); - _mesh = - new TetraMeshSnapshot(this, extent(), sampleMedia(media, weights, extent(), _numSites), _refine, _mindihedral); - break; - } - case Policy::File: - { - _mesh = new TetraMeshSnapshot(this, extent(), _filename, _refine, _mindihedral); + vector sites = sampleMedia(media, weights, extent(), _numSites); + int n = sites.size(); + _vertices.resize(n); + for (int m = 0; m != n; ++m) _vertices[m] = new Vec(sites[m]); break; } case Policy::ImportedSites: { auto sli = find()->interface(2); - _mesh = new TetraMeshSnapshot(this, extent(), sli, _refine, _mindihedral); + // prepare the data + int n = sli->numSites(); + _vertices.resize(n); + for (int m = 0; m != n; ++m) _vertices[m] = new Vec(sli->sitePosition(m)); break; } - case Policy::ImportedMesh: - { - auto ms = find(false); - _mesh = ms->interface(2)->tetraMesh(); + } - // if there is a single medium component, calculate the normalization factor imposed by it; - // we need this to directly compute cell densities for the DensityInCellInterface - if (ms->media().size() == 1) _norm = _mesh->mass() > 0 ? ms->media()[0]->number() / _mesh->mass() : 0.; - break; + if (_vertices.empty()) + { + throw FATALERROR("No vertices available for mesh generation"); + } + + buildMesh(); + buildSearchPerBlock(); +} + +void TetraMeshSpatialGrid::buildMesh() +{ + tetgenio delaunay, refined; + buildDelaunay(delaunay); + if (refine()) + { + refineDelaunay(delaunay, refined); + storeTetrahedra(refined, true); + } + else + { + storeTetrahedra(delaunay, false); + } +} + +void TetraMeshSpatialGrid::buildDelaunay(tetgenio& out) +{ + // remove sites outside of the domain + int numOutside = 0; + _numVertices = _vertices.size(); + for (int m = 0; m != _numVertices; ++m) + { + if (!contains(*_vertices[m])) + { + delete _vertices[m]; + _vertices[m] = 0; + numOutside++; } } + if (numOutside) _numVertices = eraseNullPointers(_vertices); + _log->info("removed " + StringUtils::toString(numOutside, 'd') + " vertices outside of the domain"); + + tetgenio in; + tetgenbehavior behavior; + + // in.firstnumber = 0; // remove me if no error + in.numberofpoints = _vertices.size(); + in.pointlist = new REAL[in.numberofpoints * 3]; + for (int i = 0; i < in.numberofpoints; i++) + { + in.pointlist[i * 3 + 0] = _vertices[i]->x(); + in.pointlist[i * 3 + 1] = _vertices[i]->y(); + in.pointlist[i * 3 + 2] = _vertices[i]->z(); + } + + behavior.psc = 1; // -s build Delaunay tetrahedralisation + + _log->info("Building Delaunay triangulation using input vertices..."); + tetrahedralize(&behavior, &in, &out); + _log->info("Built Delaunay triangulation"); } -////////////////////////////////////////////////////////////////////// +void TetraMeshSpatialGrid::refineDelaunay(tetgenio& in, tetgenio& out) +{ + tetgenbehavior behavior; -int TetraMeshSpatialGrid::numCells() const + // tetgen refine options + behavior.refine = 1; // -r + behavior.quality = 1; // -q + // use default tetgen options for quality + // correct output options for out + behavior.neighout = 2; // -nn + behavior.facesout = 1; // -f + behavior.zeroindex = 1; // -z + + _log->info("Refining triangulation..."); + tetrahedralize(&behavior, &in, &out); + _log->info("Refined triangulation"); +} + +void TetraMeshSpatialGrid::storeTetrahedra(const tetgenio& out, bool restoreVertices) { - return _mesh->numEntities(); + // tranfser TetGen data to TetraMeshSpatialGrid data containers + _numTetra = out.numberoftetrahedra; + + if (restoreVertices) + { + // delete old vertices + for (int i = 0; i < _numVertices; i++) delete _vertices[i]; + + _numVertices = out.numberofpoints; + + _vertices.resize(_numVertices); + for (int i = 0; i < _numVertices; i++) + { + double x = out.pointlist[3 * i + 0]; + double y = out.pointlist[3 * i + 1]; + double z = out.pointlist[3 * i + 2]; + + _vertices[i] = new Vec(x, y, z); + } + } + + _tetrahedra.resize(_numTetra); + for (int i = 0; i < _numTetra; i++) + { + std::array vertices; + std::array faces; + + // vertices + for (int c = 0; c < 4; c++) + { + vertices[c] = _vertices[out.tetrahedronlist[4 * i + c]]; + } + + // faces + for (int c = 0; c < 4; c++) + { + // -1 if no neighbor + int ntetra = out.neighborlist[4 * i + c]; + + // find which face is shared with neighbor + int nface = -1; + if (ntetra != -1) + { + for (int cn = 0; cn < 4; cn++) + { + if (out.neighborlist[4 * ntetra + cn] == i) + { + nface = cn; + break; + } + } + } + faces[c] = Face(ntetra, nface); + } + + _tetrahedra[i] = new Tetra(vertices, faces); + + _centroids.push_back(&_tetrahedra[i]->_centroid); + } + + // compile statistics + double minVol = DBL_MAX; + double maxVol = 0.; + double totalVol2 = 0.; + for (int m = 0; m < _numTetra; m++) + { + double vol = _tetrahedra[m]->volume(); + totalVol2 += vol * vol; + minVol = min(minVol, vol); + maxVol = max(maxVol, vol); + } + double V = Box::volume(); + minVol /= V; + maxVol /= V; + double avgVol = 1 / (double)_numTetra; + double varVol = (totalVol2 / _numTetra / (V * V) - avgVol * avgVol); + + // log neighbor statistics + _log->info("Done computing tetrahedralisation"); + _log->info(" Number of vertices " + std::to_string(_numVertices)); + _log->info(" Number of tetrahedra " + std::to_string(_numTetra)); + _log->info(" Average volume fraction per cell: " + StringUtils::toString(avgVol, 'e')); + _log->info(" Variance of volume fraction per cell: " + StringUtils::toString(varVol, 'e')); + _log->info(" Minimum volume fraction cell: " + StringUtils::toString(minVol, 'e')); + _log->info(" Maximum volume fraction cell: " + StringUtils::toString(maxVol, 'e')); } ////////////////////////////////////////////////////////////////////// -double TetraMeshSpatialGrid::volume(int m) const +int TetraMeshSpatialGrid::numCells() const { - return _mesh->volume(m); + return _numTetra; } ////////////////////////////////////////////////////////////////////// -double TetraMeshSpatialGrid::diagonal(int m) const +double TetraMeshSpatialGrid::volume(int m) const { - return cbrt(3. * _mesh->volume(m)); + return _tetrahedra[m]->volume(); } ////////////////////////////////////////////////////////////////////// -int TetraMeshSpatialGrid::cellIndex(Position bfr) const +double TetraMeshSpatialGrid::diagonal(int m) const { - return _mesh->cellIndex(bfr); + // can use for loop but this is more readable + const Tetra* tetra = _tetrahedra[m]; + double a = tetra->getEdge(0, 1).norm2(); + double b = tetra->getEdge(0, 2).norm2(); + double c = tetra->getEdge(0, 3).norm2(); + double d = tetra->getEdge(1, 2).norm2(); + double e = tetra->getEdge(1, 3).norm2(); + double f = tetra->getEdge(2, 3).norm2(); + return sqrt(a + b + c + d + e + f) / sqrt(6.0); } - ////////////////////////////////////////////////////////////////////// Position TetraMeshSpatialGrid::centralPositionInCell(int m) const { - return _mesh->position(m); + return Position(_tetrahedra[m]->_centroid); } ////////////////////////////////////////////////////////////////////// Position TetraMeshSpatialGrid::randomPositionInCell(int m) const { - return _mesh->generatePosition(m); + double s = random()->uniform(); + double t = random()->uniform(); + double u = random()->uniform(); + return _tetrahedra[m]->generatePosition(s, t, u); } ////////////////////////////////////////////////////////////////////// std::unique_ptr TetraMeshSpatialGrid::createPathSegmentGenerator() const { - return _mesh->createPathSegmentGenerator(); + return std::make_unique(this); } ////////////////////////////////////////////////////////////////////// void TetraMeshSpatialGrid::writeGridPlotFiles(const SimulationItem* probe) const { - _mesh->writeGridPlotFiles(probe); + // create the plot files + SpatialGridPlotFile plotxy(probe, probe->itemName() + "_grid_xy"); + SpatialGridPlotFile plotxz(probe, probe->itemName() + "_grid_xz"); + SpatialGridPlotFile plotyz(probe, probe->itemName() + "_grid_yz"); + SpatialGridPlotFile plotxyz(probe, probe->itemName() + "_grid_xyz"); + + // for each site, compute the corresponding cell and output its edges + _log->info("Writing plot files for tetrahedralisation with " + std::to_string(_numTetra) + " tetrahedra"); + _log->infoSetElapsed(_numTetra); + int numDone = 0; + for (int i = 0; i < _numTetra; i++) + { + const Tetra* tetra = _tetrahedra[i]; + vector coords; + coords.reserve(12); + vector indices; + indices.reserve(16); + + for (int v = 0; v < 4; v++) + { + const Vec* vertex = tetra->_vertices[v]; + coords.push_back(vertex->x()); + coords.push_back(vertex->y()); + coords.push_back(vertex->z()); + + // get vertices of opposite face + std::array faceIndices = clockwiseVertices(v); + indices.push_back(3); // amount of vertices per face + indices.push_back(faceIndices[0]); + indices.push_back(faceIndices[1]); + indices.push_back(faceIndices[2]); + } + + if (tetra->zmin() <= 0 && tetra->zmax() >= 0) plotxy.writePolyhedron(coords, indices); + if (tetra->ymin() <= 0 && tetra->ymax() >= 0) plotxz.writePolyhedron(coords, indices); + if (tetra->xmin() <= 0 && tetra->xmax() >= 0) plotyz.writePolyhedron(coords, indices); + if (i <= 1000) + plotxyz.writePolyhedron(coords, indices); // like TetraMeshSpatialGrid, but why even write at all? + + // log message if the minimum time has elapsed + numDone++; + if (numDone % 2000 == 0) _log->infoIfElapsed("Computed tetrehedra: ", 2000); + } } ////////////////////////////////////////////////////////////////////// double TetraMeshSpatialGrid::numberDensity(int /*h*/, int m) const { - return _norm * _mesh->density(m); + return _norm * _rhov[m]; } ////////////////////////////////////////////////////////////////////// @@ -219,4 +903,149 @@ bool TetraMeshSpatialGrid::offersInterface(const std::type_info& interfaceTypeIn return SpatialGrid::offersInterface(interfaceTypeInfo); } +int TetraMeshSpatialGrid::cellIndex(Position bfr) const +{ + // Ensure the position is inside the domain + if (!contains(bfr)) return -1; + + // Determine the block in which the point falls + int i, j, k; + cellIndices(i, j, k, bfr, _nb, _nb, _nb); + int b = i * _nb2 + j * _nb + k; + + // Look for the closest centroid in this block using the search tree + Node* tree = _blocktrees[b]; + if (!tree) + { + _log->error("No search tree found for block"); + return -1; + } + + // Full traversal algorithm + int enteringFace = -1; + int m = tree->nearest(bfr, _centroids)->m(); + const Tetra* tetra = _tetrahedra[m]; + + Vec pos = tetra->_centroid; + Direction dir(bfr - pos); + double dist = dir.norm(); + dir /= dist; + + double ds; + int leavingFace; + while (true) + { + std::tie(ds, leavingFace) = tetra->traverse(pos, dir, enteringFace); + + // If no exit point was found, break + if (leavingFace == -1) break; + + // Move to the exit point + pos += ds * dir; + dist -= ds; + if (dist <= 0) return m; + + // Get neighbour information + m = tetra->_faces[leavingFace]._ntetra; + enteringFace = tetra->_faces[leavingFace]._nface; + + // If no next tetrahedron, break + if (m < 0) break; + + tetra = _tetrahedra[m]; + } + + // If search tree traversal failed, loop over all tetrahedra in the block + for (int t : _blocklists[b]) + if (_tetrahedra[t]->inside(bfr)) return t; + + _log->error("cellIndex failed to find the tetrahedron"); + return -1; +} +class TetraMeshSpatialGrid::MySegmentGenerator : public PathSegmentGenerator +{ + const TetraMeshSpatialGrid* _grid{nullptr}; + int _mr; + int _enteringFace; + +public: + MySegmentGenerator(const TetraMeshSpatialGrid* grid) : _grid(grid) {} + + bool next() override + { + if (state() == State::Unknown) + { + // try moving the photon packet inside the grid; if this is impossible, return an empty path + // this also changes the _state + if (!moveInside(_grid->extent(), _grid->_eps)) return false; + + // get the index of the cell containing the current position + _mr = _grid->cellIndex(r()); + _enteringFace = -1; + + // if the photon packet started outside the grid, return the corresponding nonzero-length segment; + // otherwise fall through to determine the first actual segment + if (ds() > 0.) return true; + } + + // intentionally falls through + if (state() == State::Inside) + { + double ds; + int leavingFace; + // loop in case no exit point was found (which should happen only rarely) + while (true) + { + const Tetra* tetra = _grid->_tetrahedra[_mr]; + Position pos = r(); + Direction dir = k(); + + std::tie(ds, leavingFace) = tetra->traverse(pos, dir, _enteringFace); + + // if no exit point was found, advance the current point by a small distance, + // recalculate the cell index, and return to the start of the loop + if (leavingFace == -1 || ds < _grid->_eps) + { + propagater(_grid->_eps); + _mr = _grid->cellIndex(r()); + + if (_mr < 0) + { + setState(State::Outside); + return false; + } + else + { + _enteringFace = -1; + } + } + // otherwise set the current point to the exit point and return the path segment + else + { + propagater(ds); + setSegment(_mr, ds); + _mr = tetra->_faces[leavingFace]._ntetra; + + if (_mr < 0) + { + setState(State::Outside); + return false; + } + else + { + _enteringFace = tetra->_faces[leavingFace]._nface; + return true; + } + } + } + } + + if (state() == State::Outside) + { + } + + return false; + } +}; + ////////////////////////////////////////////////////////////////////// diff --git a/SKIRT/core/TetraMeshSpatialGrid.hpp b/SKIRT/core/TetraMeshSpatialGrid.hpp index 22f3df03..3b772f7d 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.hpp +++ b/SKIRT/core/TetraMeshSpatialGrid.hpp @@ -9,7 +9,8 @@ #include "BoxSpatialGrid.hpp" #include "DensityInCellInterface.hpp" #include "Medium.hpp" -class TetraMeshSnapshot; + +class tetgenio; ////////////////////////////////////////////////////////////////////// @@ -33,9 +34,7 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid, public DensityInCellInterfac ENUM_VAL(Policy, DustDensity, "random from dust density distribution") ENUM_VAL(Policy, ElectronDensity, "random from electron density distribution") ENUM_VAL(Policy, GasDensity, "random from gas density distribution") - ENUM_VAL(Policy, File, "loaded from text column data file") ENUM_VAL(Policy, ImportedSites, "positions of particles, sites or cells in imported distribution") - ENUM_VAL(Policy, ImportedMesh, "employ imported Tetra mesh in medium system") ENUM_END() ITEM_CONCRETE(TetraMeshSpatialGrid, BoxSpatialGrid, "a Tetra tessellation-based spatial grid") @@ -44,7 +43,7 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid, public DensityInCellInterfac PROPERTY_ENUM(policy, Policy, "the policy for determining the positions of the sites") ATTRIBUTE_DEFAULT_VALUE(policy, "DustDensity") - PROPERTY_INT(numSites, "the number of random sites (or cells in the grid)") + PROPERTY_INT(numSites, "the number of random sites to be used as vertices") ATTRIBUTE_MIN_VALUE(numSites, "5") ATTRIBUTE_DEFAULT_VALUE(numSites, "500") ATTRIBUTE_RELEVANT_IF(numSites, "policyUniform|policyCentralPeak|policyDustDensity|" @@ -56,10 +55,6 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid, public DensityInCellInterfac PROPERTY_BOOL(refine, "refine the grid to have higher quality cells by adding more vertices") ATTRIBUTE_DEFAULT_VALUE(refine, "false"); - PROPERTY_DOUBLE(mindihedral, "the minimum dihedral angle per tetrahedron") - ATTRIBUTE_DEFAULT_VALUE(mindihedral, "3.5") - ATTRIBUTE_RELEVANT_IF(mindihedral, "refine") - ITEM_END() //============= Construction - Setup - Destruction ============= @@ -75,6 +70,20 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid, public DensityInCellInterfac the TetraMeshSnapshot class. */ void setupSelfBefore() override; +private: + class Node; + class Face; + class Tetra; + + /** Build the Delaunay triangulation */ + void buildDelaunay(tetgenio& out); + + void refineDelaunay(tetgenio& in, tetgenio& out); + + void buildMesh(); + + void storeTetrahedra(const tetgenio& out, bool restoreVertices); + //======================== Other Functions ======================= public: @@ -127,12 +136,43 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid, public DensityInCellInterfac class. */ bool offersInterface(const std::type_info& interfaceTypeInfo) const override; +private: + Node* buildTree(vector::iterator first, vector::iterator last, int depth) const; + + void buildSearchPerBlock(); + //======================== Data Members ======================== private: + Log* _log{nullptr}; + + // data members initialized during configuration + double _eps{0.}; // small fraction of extent + int _numTetra; + int _numVertices; // vertices are added/removed as the grid is built and refined + + // data members initialized when processing snapshot input and further completed by BuildMesh() + vector _tetrahedra; + vector _vertices; + vector _centroids; + + // data members initialized when processing snapshot input, but only if a density policy has been set + Array _rhov; // density for each cell (not normalized) + double _mass{0.}; // total effective mass + + // data members initialized by BuildSearch() + int _nb{0}; // number of blocks in each dimension (limit for indices i,j,k) + int _nb2{0}; // nb*nb + int _nb3{0}; // nb*nb*nb + vector> _blocklists; // list of cell indices per block, indexed on i*_nb2+j*_nb+k + vector _blocktrees; // root node of search tree or null for each block, indexed on i*_nb2+j*_nb+k + + // allow our path segment generator to access our private data members + class MySegmentGenerator; + friend class MySegmentGenerator; + // data members initialized during setup - TetraMeshSnapshot* _mesh{nullptr}; // Tetra mesh snapshot created here or obtained from medium component - double _norm{0.}; // in the latter case, normalization factor obtained from medium component + double _norm{0.}; // in the latter case, normalization factor obtained from medium component // data members initialized by setupSelfBefore() Random* _random{nullptr}; diff --git a/SKIRT/tetgen/LICENSE b/SKIRT/tetgen/LICENSE new file mode 100644 index 00000000..e253c3d9 --- /dev/null +++ b/SKIRT/tetgen/LICENSE @@ -0,0 +1,666 @@ +TetGen License +-------------- + +TetGen is distributed under a dual licensing scheme. You can +redistribute it and/or modify it under the terms of the GNU Affero +General Public License as published by the Free Software Foundation, +either version 3 of the License, or (at your option) any later +version. A copy of the GNU Affero General Public License is reproduced +below. + +If the terms and conditions of the AGPL v.3. would prevent you from +using TetGen, please consider the option to obtain a commercial +license for a fee. These licenses are offered by the Weierstrass +Institute for Applied Analysis and Stochastics (WIAS). As a rule, +licenses are provided "as-is", unlimited in time for a one time +fee. Please send corresponding requests to: +tetgen@wias-berlin.de. Please do not forget to include some +description of your company and the realm of its activities. + +===================================================================== +GNU AFFERO GENERAL PUBLIC LICENSE + +Version 3, 19 November 2007 + +Copyright © 2007 Free Software Foundation, Inc. +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +Preamble + +The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + +The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains +free software for all its users. + +When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + +Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + +A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come +about. The GNU General Public License permits making a modified +version and letting the public access it on a server without ever +releasing its source code to the public. + +The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + +An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing +under this license. + +The precise terms and conditions for copying, distribution and +modification follow. + +TERMS AND CONDITIONS + +0. Definitions. + +"This License" refers to version 3 of the GNU Affero General Public +License. + +"Copyright" also means copyright-like laws that apply to other kinds +of works, such as semiconductor masks. + +"The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + +To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of +an exact copy. The resulting work is called a "modified version" of +the earlier work or a work "based on" the earlier work. + +A "covered work" means either the unmodified Program or a work based +on the Program. + +To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + +To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user +through a computer network, with no transfer of a copy, is not +conveying. + +An interactive user interface displays "Appropriate Legal Notices" to +the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + +1. Source Code. + +The "source code" for a work means the preferred form of the work for +making modifications to it. "Object code" means any non-source form of +a work. + +A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + +The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + +The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + +The Corresponding Source need not include anything that users can +regenerate automatically from other parts of the Corresponding Source. + +The Corresponding Source for a work in source code form is that same +work. + +2. Basic Permissions. + +All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not convey, +without conditions so long as your license otherwise remains in +force. You may convey covered works to others for the sole purpose of +having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under the +conditions stated below. Sublicensing is not allowed; section 10 makes +it unnecessary. + +3. Protecting Users' Legal Rights From Anti-Circumvention Law. + +No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + +When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such +circumvention is effected by exercising rights under this License with +respect to the covered work, and you disclaim any intention to limit +operation or modification of the work as a means of enforcing, against +the work's users, your or third parties' legal rights to forbid +circumvention of technological measures. + +4. Conveying Verbatim Copies. + +You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + +5. Conveying Modified Source Versions. + +You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these +conditions: + +a) The work must carry prominent notices stating that you modified it, +and giving a relevant date. b) The work must carry prominent notices +stating that it is released under this License and any conditions +added under section 7. This requirement modifies the requirement in +section 4 to "keep intact all notices". c) You must license the +entire work, as a whole, under this License to anyone who comes into +possession of a copy. This License will therefore apply, along with +any applicable section 7 additional terms, to the whole of the work, +and all its parts, regardless of how they are packaged. This License +gives no permission to license the work in any other way, but it does +not invalidate such permission if you have separately received it. d) +If the work has interactive user interfaces, each must display +Appropriate Legal Notices; however, if the Program has interactive +interfaces that do not display Appropriate Legal Notices, your work +need not make them do so. A compilation of a covered work with other +separate and independent works, which are not by their nature +extensions of the covered work, and which are not combined with it +such as to form a larger program, in or on a volume of a storage or +distribution medium, is called an "aggregate" if the compilation and +its resulting copyright are not used to limit the access or legal +rights of the compilation's users beyond what the individual works +permit. Inclusion of a covered work in an aggregate does not cause +this License to apply to the other parts of the aggregate. + +6. Conveying Non-Source Forms. + +You may convey a covered work in object code form under the terms of +sections 4 and 5, provided that you also convey the machine-readable +Corresponding Source under the terms of this License, in one of these +ways: + +a) Convey the object code in, or embodied in, a physical product +(including a physical distribution medium), accompanied by the +Corresponding Source fixed on a durable physical medium customarily +used for software interchange. b) Convey the object code in, or +embodied in, a physical product (including a physical distribution +medium), accompanied by a written offer, valid for at least three +years and valid for as long as you offer spare parts or customer +support for that product model, to give anyone who possesses the +object code either (1) a copy of the Corresponding Source for all the +software in the product that is covered by this License, on a durable +physical medium customarily used for software interchange, for a price +no more than your reasonable cost of physically performing this +conveying of source, or (2) access to copy the Corresponding Source +from a network server at no charge. c) Convey individual copies of +the object code with a copy of the written offer to provide the +Corresponding Source. This alternative is allowed only occasionally +and noncommercially, and only if you received the object code with +such an offer, in accord with subsection 6b. d) Convey the object +code by offering access from a designated place (gratis or for a +charge), and offer equivalent access to the Corresponding Source in +the same way through the same place at no further charge. You need not +require recipients to copy the Corresponding Source along with the +object code. If the place to copy the object code is a network server, +the Corresponding Source may be on a different server (operated by you +or a third party) that supports equivalent copying facilities, +provided you maintain clear directions next to the object code saying +where to find the Corresponding Source. Regardless of what server +hosts the Corresponding Source, you remain obligated to ensure that it +is available for as long as needed to satisfy these requirements. e) +Convey the object code using peer-to-peer transmission, provided you +inform other peers where the object code and Corresponding Source of +the work are being offered to the general public at no charge under +subsection 6d. A separable portion of the object code, whose source +code is excluded from the Corresponding Source as a System Library, +need not be included in conveying the object code work. + +A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, +family, or household purposes, or (2) anything designed or sold for +incorporation into a dwelling. In determining whether a product is a +consumer product, doubtful cases shall be resolved in favor of +coverage. For a particular product received by a particular user, +"normally used" refers to a typical or common use of that class of +product, regardless of the status of the particular user or of the way +in which the particular user actually uses, or expects or is expected +to use, the product. A product is a consumer product regardless of +whether the product has substantial commercial, industrial or +non-consumer uses, unless such uses represent the only significant +mode of use of the product. + +"Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to +install and execute modified versions of a covered work in that User +Product from a modified version of its Corresponding Source. The +information must suffice to ensure that the continued functioning of +the modified object code is in no case prevented or interfered with +solely because modification has been made. + +If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + +The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or +updates for a work that has been modified or installed by the +recipient, or for the User Product in which it has been modified or +installed. Access to a network may be denied when the modification +itself materially and adversely affects the operation of the network +or violates the rules and protocols for communication across the +network. + +Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + +7. Additional Terms. + +"Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its +conditions. Additional permissions that are applicable to the entire +Program shall be treated as though they were included in this License, +to the extent that they are valid under applicable law. If additional +permissions apply only to part of the Program, that part may be used +separately under those permissions, but the entire Program remains +governed by this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders +of that material) supplement the terms of this License with terms: + +a) Disclaiming warranty or limiting liability differently from the +terms of sections 15 and 16 of this License; or b) Requiring +preservation of specified reasonable legal notices or author +attributions in that material or in the Appropriate Legal Notices +displayed by works containing it; or c) Prohibiting misrepresentation +of the origin of that material, or requiring that modified versions of +such material be marked in reasonable ways as different from the +original version; or d) Limiting the use for publicity purposes of +names of licensors or authors of the material; or e) Declining to +grant rights under trademark law for use of some trade names, +trademarks, or service marks; or f) Requiring indemnification of +licensors and authors of that material by anyone who conveys the +material (or modified versions of it) with contractual assumptions of +liability to the recipient, for any liability that these contractual +assumptions directly impose on those licensors and authors. All other +non-permissive additional terms are considered "further restrictions" +within the meaning of section 10. If the Program as you received it, +or any part of it, contains a notice stating that it is governed by +this License along with a term that is a further restriction, you may +remove that term. If a license document contains a further restriction +but permits relicensing or conveying under this License, you may add +to a covered work material governed by the terms of that license +document, provided that the further restriction does not survive such +relicensing or conveying. + +If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; the +above requirements apply either way. + +8. Termination. + +You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + +However, if you cease all violation of this License, then your license +from a particular copyright holder is reinstated (a) provisionally, +unless and until the copyright holder explicitly and finally +terminates your license, and (b) permanently, if the copyright holder +fails to notify you of the violation by some reasonable means prior to +60 days after the cessation. + +Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + +9. Acceptance Not Required for Having Copies. + +You are not required to accept this License in order to receive or run +a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + +10. Automatic Licensing of Downstream Recipients. + +Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + +An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + +11. Patents. + +A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + +A contributor's "essential patent claims" are all patent claims owned +or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + +In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + +If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + +A patent license is "discriminatory" if it does not include within the +scope of its coverage, prohibits the exercise of, or is conditioned on +the non-exercise of one or more of the rights that are specifically +granted under this License. You may not convey a covered work if you +are a party to an arrangement with a third party that is in the +business of distributing software, under which you make payment to the +third party based on the extent of your activity of conveying the +work, and under which the third party grants, to any of the parties +who would receive the covered work from you, a discriminatory patent +license (a) in connection with copies of the covered work conveyed by +you (or copies made from those copies), or (b) primarily for and in +connection with specific products or compilations that contain the +covered work, unless you entered into that arrangement, or that patent +license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + +12. No Surrender of Others' Freedom. + +If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under +this License and any other pertinent obligations, then as a +consequence you may not convey it at all. For example, if you agree to +terms that obligate you to collect a royalty for further conveying +from those to whom you convey the Program, the only way you could +satisfy both those terms and this License would be to refrain entirely +from conveying the Program. + +13. Remote Network Interaction; Use with the GNU General Public +License. + +Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your +version supports such interaction) an opportunity to receive the +Corresponding Source of your version by providing access to the +Corresponding Source from a network server at no charge, through some +standard or customary means of facilitating copying of software. This +Corresponding Source shall include the Corresponding Source for any +work covered by version 3 of the GNU General Public License that is +incorporated pursuant to the following paragraph. + +Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + +14. Revised Versions of this License. + +The Free Software Foundation may publish revised and/or new versions +of the GNU Affero General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever +published by the Free Software Foundation. + +If the Program specifies that a proxy can decide which future versions +of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + +Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + +15. Disclaimer of Warranty. + +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT +WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND +PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE +DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR +CORRECTION. + +16. Limitation of Liability. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR +CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT +NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR +LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM +TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER +PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +17. Interpretation of Sections 15 and 16. + +If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these +terms. + +To do so, attach the following notices to the program. It is safest to +attach them to the start of each source file to most effectively state +the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + Copyright (C) + + This program is free software: you can redistribute it and/or + modify it under the terms of the GNU Affero General Public License + as published by the Free Software Foundation, either version 3 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public + License along with this program. If not, see + . Also add information on how to + contact you by electronic and paper mail. + +If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for +the specific requirements. + +You should also get your employer (if you work as a programmer) or +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. For more information on this, and how to apply and follow +the GNU AGPL, see . \ No newline at end of file diff --git a/SKIRT/tetgen/tetgen.h b/SKIRT/tetgen/tetgen.h index 5a0a15ba..65741082 100644 --- a/SKIRT/tetgen/tetgen.h +++ b/SKIRT/tetgen/tetgen.h @@ -76,7 +76,6 @@ #include #include #include -#include // The types 'intptr_t' and 'uintptr_t' are signed and unsigned integer types, // respectively. They are guaranteed to be the same width as a pointer. @@ -174,9 +173,7 @@ class tetgenio typedef void (*GetSteinerOnFace)(void*, int, REAL*, REAL*); // A callback function for mesh refinement. - // typedef bool (* TetSizeFunc)(REAL*, REAL*, REAL*, REAL*, REAL*, REAL); - // SKIRT: changed this to std::function for lambda with captures - typedef std::function TetSizeFunc; + typedef bool (*TetSizeFunc)(REAL*, REAL*, REAL*, REAL*, REAL*, REAL); // Items are numbered starting from 'firstnumber' (0 or 1), default is 0. int firstnumber; @@ -2521,15 +2518,9 @@ class tetgenmesh initializetetgenmesh(); } - tetgenmesh() - { - initializetetgenmesh(); - } + tetgenmesh() { initializetetgenmesh(); } - ~tetgenmesh() - { - freememory(); - } // ~tetgenmesh() + ~tetgenmesh() { freememory(); } // ~tetgenmesh() }; // End of class tetgenmesh. @@ -2560,7 +2551,7 @@ void tetrahedralize(char* switches, tetgenio* in, tetgenio* out, tetgenio* addin // // //============================================================================// -inline void terminatetetgen(tetgenmesh* /*m*/, int x) +inline void terminatetetgen(tetgenmesh* m, int x) { #ifdef TETLIBRARY throw x; From 6b3db999e016d4594f9c6bff0e39d69befee13dc Mon Sep 17 00:00:00 2001 From: arlauwer Date: Sat, 30 Nov 2024 17:18:29 +0100 Subject: [PATCH 32/51] removed unnecessary code fixed compile errors --- SKIRT/core/SimulationItemRegistry.cpp | 6 -- SKIRT/core/TetraMeshSpatialGrid.cpp | 52 ++++++--------- SKIRT/core/TetraMeshSpatialGrid.hpp | 92 +++++++++------------------ SKIRT/core/VoronoiMeshSnapshot.hpp | 2 +- SKIRT/tetgen/tetgen.cxx | 3 +- 5 files changed, 52 insertions(+), 103 deletions(-) diff --git a/SKIRT/core/SimulationItemRegistry.cpp b/SKIRT/core/SimulationItemRegistry.cpp index a916051e..469df3ae 100755 --- a/SKIRT/core/SimulationItemRegistry.cpp +++ b/SKIRT/core/SimulationItemRegistry.cpp @@ -248,9 +248,6 @@ #include "TTauriDiskGeometry.hpp" #include "TemperatureProbe.hpp" #include "TemperatureWavelengthCellLibrary.hpp" -#include "TetraMeshGeometry.hpp" -#include "TetraMeshMedium.hpp" -#include "TetraMeshSource.hpp" #include "TetraMeshSpatialGrid.hpp" #include "ThemisDustMix.hpp" #include "ToddlersSED.hpp" @@ -320,7 +317,6 @@ SimulationItemRegistry::SimulationItemRegistry(string version, string format) ItemRegistry::add(); ItemRegistry::add(); ItemRegistry::add(); - ItemRegistry::add(); ItemRegistry::add(); ItemRegistry::add(); ItemRegistry::add(); @@ -443,7 +439,6 @@ SimulationItemRegistry::SimulationItemRegistry(string version, string format) ItemRegistry::add(); ItemRegistry::add(); ItemRegistry::add(); - ItemRegistry::add(); // geometry decorators ItemRegistry::add(); @@ -517,7 +512,6 @@ SimulationItemRegistry::SimulationItemRegistry(string version, string format) ItemRegistry::add(); ItemRegistry::add(); ItemRegistry::add(); - ItemRegistry::add(); // medium system options ItemRegistry::add(); diff --git a/SKIRT/core/TetraMeshSpatialGrid.cpp b/SKIRT/core/TetraMeshSpatialGrid.cpp index 5efec924..efb31be6 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.cpp +++ b/SKIRT/core/TetraMeshSpatialGrid.cpp @@ -6,20 +6,23 @@ #include "TetraMeshSpatialGrid.hpp" #include "Configuration.hpp" #include "FatalError.hpp" -#include "Log.hpp" #include "MediumSystem.hpp" #include "NR.hpp" -#include "PathSegmentGenerator.hpp" #include "Random.hpp" #include "SiteListInterface.hpp" #include "SpatialGridPlotFile.hpp" #include "StringUtils.hpp" -#include "TetraMeshInterface.hpp" #include "tetgen.h" ////////////////////////////////////////////////////////////////////// -TetraMeshSpatialGrid::~TetraMeshSpatialGrid() {} +TetraMeshSpatialGrid::~TetraMeshSpatialGrid() +{ + for (auto vertex : _vertices) delete vertex; + for (auto centroid : _centroids) delete centroid; + for (auto tetra : _tetrahedra) delete tetra; // vertices are already deleted + for (auto tree : _blocktrees) delete tree; +} ////////////////////////////////////////////////////////////////////// @@ -530,6 +533,7 @@ void TetraMeshSpatialGrid::setupSelfBefore() BoxSpatialGrid::setupSelfBefore(); _log = find(); + _eps = 1e-12 * widths().norm(); // determine an appropriate set of sites and construct the Tetra mesh switch (_policy) @@ -692,12 +696,12 @@ void TetraMeshSpatialGrid::refineDelaunay(tetgenio& in, tetgenio& out) _log->info("Refined triangulation"); } -void TetraMeshSpatialGrid::storeTetrahedra(const tetgenio& out, bool restoreVertices) +void TetraMeshSpatialGrid::storeTetrahedra(const tetgenio& out, bool storeVertices) { // tranfser TetGen data to TetraMeshSpatialGrid data containers _numTetra = out.numberoftetrahedra; - if (restoreVertices) + if (storeVertices) { // delete old vertices for (int i = 0; i < _numVertices; i++) delete _vertices[i]; @@ -828,13 +832,6 @@ Position TetraMeshSpatialGrid::randomPositionInCell(int m) const ////////////////////////////////////////////////////////////////////// -std::unique_ptr TetraMeshSpatialGrid::createPathSegmentGenerator() const -{ - return std::make_unique(this); -} - -////////////////////////////////////////////////////////////////////// - void TetraMeshSpatialGrid::writeGridPlotFiles(const SimulationItem* probe) const { // create the plot files @@ -884,25 +881,6 @@ void TetraMeshSpatialGrid::writeGridPlotFiles(const SimulationItem* probe) const ////////////////////////////////////////////////////////////////////// -double TetraMeshSpatialGrid::numberDensity(int /*h*/, int m) const -{ - return _norm * _rhov[m]; -} - -////////////////////////////////////////////////////////////////////// - -bool TetraMeshSpatialGrid::offersInterface(const std::type_info& interfaceTypeInfo) const -{ - if (interfaceTypeInfo == typeid(DensityInCellInterface)) - { - return false; - // if (_policy != Policy::ImportedMesh) return false; - // auto ms = find(false); - // return ms && ms->media().size() == 1; - } - return SpatialGrid::offersInterface(interfaceTypeInfo); -} - int TetraMeshSpatialGrid::cellIndex(Position bfr) const { // Ensure the position is inside the domain @@ -962,6 +940,9 @@ int TetraMeshSpatialGrid::cellIndex(Position bfr) const _log->error("cellIndex failed to find the tetrahedron"); return -1; } + +////////////////////////////////////////////////////////////////////// + class TetraMeshSpatialGrid::MySegmentGenerator : public PathSegmentGenerator { const TetraMeshSpatialGrid* _grid{nullptr}; @@ -1049,3 +1030,10 @@ class TetraMeshSpatialGrid::MySegmentGenerator : public PathSegmentGenerator }; ////////////////////////////////////////////////////////////////////// + +std::unique_ptr TetraMeshSpatialGrid::createPathSegmentGenerator() const +{ + return std::make_unique(this); +} + +////////////////////////////////////////////////////////////////////// diff --git a/SKIRT/core/TetraMeshSpatialGrid.hpp b/SKIRT/core/TetraMeshSpatialGrid.hpp index 3b772f7d..3812dfdd 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.hpp +++ b/SKIRT/core/TetraMeshSpatialGrid.hpp @@ -8,7 +8,9 @@ #include "BoxSpatialGrid.hpp" #include "DensityInCellInterface.hpp" +#include "Log.hpp" #include "Medium.hpp" +#include "PathSegmentGenerator.hpp" class tetgenio; @@ -25,7 +27,7 @@ class tetgenio; Furthermore, the user can opt to perform a relaxation step on the site positions to avoid overly elongated cells. */ -class TetraMeshSpatialGrid : public BoxSpatialGrid, public DensityInCellInterface +class TetraMeshSpatialGrid : public BoxSpatialGrid { /** The enumeration type indicating the policy for determining the positions of the sites. */ ENUM_DEF(Policy, Uniform, CentralPeak, DustDensity, ElectronDensity, GasDensity, File, ImportedSites, ImportedMesh) @@ -70,19 +72,40 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid, public DensityInCellInterfac the TetraMeshSnapshot class. */ void setupSelfBefore() override; + //==================== Private construction ==================== private: class Node; class Face; class Tetra; - /** Build the Delaunay triangulation */ + void buildMesh(); + void buildDelaunay(tetgenio& out); void refineDelaunay(tetgenio& in, tetgenio& out); - void buildMesh(); + void storeTetrahedra(const tetgenio& out, bool storeVertices); - void storeTetrahedra(const tetgenio& out, bool restoreVertices); + /** Private function to recursively build a binary search tree (see + en.wikipedia.org/wiki/Kd-tree) */ + Node* buildTree(vector::iterator first, vector::iterator last, int depth) const; + + /** This private function builds data structures that allow accelerating the operation of the + cellIndex() function. It assumes that the Voronoi mesh has already been built. + + The domain is partitioned using a linear cubodial grid into cells that are called \em + blocks. For each block, the function builds and stores a list of all Voronoi cells that + possibly overlap that block. Retrieving the list of cells possibly overlapping a given + point in the domain then comes down to locating the block containing the point (which is + trivial since the grid is linear). The current implementation uses a Voronoi cell's + enclosing cuboid to test for intersection with a block. Performing a precise intersection + test is \em really slow and the shortened block lists don't substantially accelerate the + cellIndex() function. + + To further reduce the search time within blocks that overlap with a large number of cells, + the function builds a binary search tree on the cell sites for those blocks (see for example + en.wikipedia.org/wiki/Kd-tree). */ + void buildSearchPerBlock(); //======================== Other Functions ======================= @@ -119,47 +142,21 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid, public DensityInCellInterfac produce the coordinates of the Tetra cell vertices. */ void writeGridPlotFiles(const SimulationItem* probe) const override; - /** This function implements the DensityInCellInterface interface. It returns the number - density for medium component \f$h\f$ in the grid cell with index \f$m\f$. For a Tetra - mesh grid, this interface can be offered only if the "ImportedMesh" policy has been - configured, and the medium system consists of a single component, which then, by - definition, supplies the Tetra mesh. Thus, the component index \f$h\f$ passed to this - function should always be zero; in fact, its value is actually ignored. */ - double numberDensity(int h, int m) const override; - -protected: - /** This function is used by the interface() function to ensure that the receiving item can - actually offer the specified interface. If the requested interface is the - DensityInCellInterface, the implementation in this class returns true if the "ImportedMesh" - policy has been configured and the medium system consists of a single component, and false - otherwise. For other requested interfaces, the function invokes its counterpart in the base - class. */ - bool offersInterface(const std::type_info& interfaceTypeInfo) const override; - -private: - Node* buildTree(vector::iterator first, vector::iterator last, int depth) const; - - void buildSearchPerBlock(); - //======================== Data Members ======================== private: + // data members initialized by setupSelfBefore() Log* _log{nullptr}; // data members initialized during configuration double _eps{0.}; // small fraction of extent - int _numTetra; - int _numVertices; // vertices are added/removed as the grid is built and refined - // data members initialized when processing snapshot input and further completed by BuildMesh() + int _numTetra; + int _numVertices; // vertices are added/removed as the grid is built and refined vector _tetrahedra; vector _vertices; vector _centroids; - // data members initialized when processing snapshot input, but only if a density policy has been set - Array _rhov; // density for each cell (not normalized) - double _mass{0.}; // total effective mass - // data members initialized by BuildSearch() int _nb{0}; // number of blocks in each dimension (limit for indices i,j,k) int _nb2{0}; // nb*nb @@ -170,35 +167,6 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid, public DensityInCellInterfac // allow our path segment generator to access our private data members class MySegmentGenerator; friend class MySegmentGenerator; - - // data members initialized during setup - double _norm{0.}; // in the latter case, normalization factor obtained from medium component - - // data members initialized by setupSelfBefore() - Random* _random{nullptr}; - int _numSamples{0}; - - // lists of medium components of each material type; - // list remains empty if no criteria are enabled for the corresponding material type - vector _dustMedia; - vector _electronMedia; - vector _gasMedia; - - // flags become true if corresponding criterion is enabled - // (i.e. configured maximum is nonzero and material type is present) - bool _hasAny{false}; - bool _hasDustAny{false}; - bool _hasDustFraction{false}; - bool _hasDustOpticalDepth{false}; - bool _hasDustDensityDispersion{false}; - bool _hasElectronFraction{false}; - bool _hasGasFraction{false}; - - // cached values for each material type (valid if corresponding flag is enabled) - double _dustMass{0.}; - double _dustKappa{0.}; - double _electronNumber{0.}; - double _gasNumber{0.}; }; ////////////////////////////////////////////////////////////////////// diff --git a/SKIRT/core/VoronoiMeshSnapshot.hpp b/SKIRT/core/VoronoiMeshSnapshot.hpp index ca3def78..e112dfee 100644 --- a/SKIRT/core/VoronoiMeshSnapshot.hpp +++ b/SKIRT/core/VoronoiMeshSnapshot.hpp @@ -181,7 +181,7 @@ class VoronoiMeshSnapshot : public Snapshot argument is true, the function performs a single relaxation step on the site positions. */ VoronoiMeshSnapshot(const SimulationItem* item, const Box& extent, const vector& sites, bool relax); - //=========== Private construction ========== + //========== Private construction ========== private: /** Private class to hold the information about a Voronoi cell that is relevant for calculating diff --git a/SKIRT/tetgen/tetgen.cxx b/SKIRT/tetgen/tetgen.cxx index 872578f8..8ed1b24a 100644 --- a/SKIRT/tetgen/tetgen.cxx +++ b/SKIRT/tetgen/tetgen.cxx @@ -28403,7 +28403,7 @@ bool tetgenmesh::checktet4split(triface *chktet, REAL* param, int& qflag) if (in->tetunsuitable != NULL) { // Execute the user-defined meshing sizing evaluation. - if (((in->tetunsuitable))(pa, pb, pc, pd, vol)) { // SKIRT: removed unused parameters here + if ((*(in->tetunsuitable))(pa, pb, pc, pd, NULL, 0)) { return true; } } @@ -36564,4 +36564,3 @@ void tetrahedralize(char *switches, tetgenio *in, tetgenio *out, // // // // //== main_cxx ================================================================// - From 8c9a368ab6f70025ceaa56f751b95de58c0875dd Mon Sep 17 00:00:00 2001 From: arlauwer Date: Sat, 30 Nov 2024 19:12:11 +0100 Subject: [PATCH 33/51] formatted code --- SKIRT/core/TetraMeshSnapshot.hpp | 369 -------------------- SKIRT/core/TetraMeshSpatialGrid.cpp | 512 ++++++++++++++-------------- SKIRT/core/TetraMeshSpatialGrid.hpp | 5 +- 3 files changed, 259 insertions(+), 627 deletions(-) delete mode 100644 SKIRT/core/TetraMeshSnapshot.hpp diff --git a/SKIRT/core/TetraMeshSnapshot.hpp b/SKIRT/core/TetraMeshSnapshot.hpp deleted file mode 100644 index 47dde464..00000000 --- a/SKIRT/core/TetraMeshSnapshot.hpp +++ /dev/null @@ -1,369 +0,0 @@ -/*////////////////////////////////////////////////////////////////// -//// The SKIRT project -- advanced radiative transfer //// -//// © Astronomical Observatory, Ghent University //// -///////////////////////////////////////////////////////////////// */ - -#ifndef TETRAMESHSNAPSHOT_HPP -#define TETRAMESHSNAPSHOT_HPP - -#include "Array.hpp" -#include "MediumSystem.hpp" -#include "Snapshot.hpp" -#include "TetraMeshSpatialGrid.hpp" -#include "tetgen.h" -#include "array" -class PathSegmentGenerator; -class SiteListInterface; -class SpatialGridPath; - -//////////////////////////////////////////////////////////////////// - -/** */ -class TetraMeshSnapshot : public Snapshot -{ - //================= Construction - Destruction ================= - -public: - /** The default constructor initializes the snapshot in an invalid state; see the description - of the required calling sequence in the Snapshot class header. */ - TetraMeshSnapshot(); - - /** The destructor releases any data structures allocated by this class. */ - ~TetraMeshSnapshot(); - - //========== Reading ========== - -public: - /** This function reads the snapshot data from the input file, honoring the options set through - the configuration functions, stores the data for later use, and closes the file by calling - the base class Snapshot::readAndClose() function. Sites located outside of the domain and - sites that are too close to another site are discarded. Sites with an associated - temperature above the cutoff temperature (if one has been configured) are assigned a - density value of zero, so that the corresponding cell has zero mass (regardless of the - imported mass/density properties). - - The function calls the private buildMesh() function to build the Tetra mesh based on the - imported site positions. If the snapshot configuration requires the ability to determine - the density at a given spatial position, the function also calls the private buildSearch() - function to create a data structure that accelerates locating the cell containing a given - point. - - During its operation, the function logs some statistical information about the imported - snapshot and the resulting data structures. */ - void readAndClose() override; - - //========== Configuration ========== - -public: - /** This function sets the extent of the spatial domain for the Tetra mesh. When using the - default constructor, this function must be called during configuration. There is no - default; failing to set the extent of the domain results in undefined behavior. */ - void setExtent(const Box& extent); - - //========== Specialty constructors ========== - -public: - TetraMeshSnapshot(const SimulationItem* item, const Box& extent, string filename, bool refine, double mindihedral); - - TetraMeshSnapshot(const SimulationItem* item, const Box& extent, SiteListInterface* sli, bool refine, - double mindihedral); - - TetraMeshSnapshot(const SimulationItem* item, const Box& extent, const vector& sites, bool refine, - double mindihedral); - - TetraMeshSnapshot(const SimulationItem* item, const Box& extent, bool refine, double mindihedral); - - //=========== Private construction ========== - -private: - class Node; - class Face; - class Tetra; - - void buildDelaunay(tetgenio& out); - - void refineDelaunay(tetgenio& in, tetgenio& out, double mindihedral); - - /** Given a list of generating sites (represented as partially initialized Cell - objects), this private function builds the Tetra tessellation and stores the - corresponding cell information, including any properties relevant for supporting the - interrogation capabilities offered by this class. All other data (such as Tetra cell - vertices, edges and faces) are discarded. In practice, the function adds the sites to a - Tetra++ container, computes the Tetra cells one by one, and copies the relevant cell - information (such as the list of neighboring cells) from the Tetra++ data structures into - its own. - - If the \em relax argument is true, the function performs a single relaxation step on the - site positions using Lloyd's algorithm (Lloyd 1982; Du, Faber and Gunzburger 1999, SIAM - review 41.4, pp 637-676; Dobbels 2017, master thesis). An intermediate Tetra tessellation - is built using the original site positions, and subsequently each site position is replaced - by the centroid (mass center) of the corresponding cell. The final tessellation is then - constructed with these adjusted site positions, which are distributed more uniformly, - thereby avoiding overly elongated cells in the Tetra tessellation. Relaxation can be - quite time-consuming because the Tetra tessellation must be constructed twice. */ - void buildMesh(bool refine, double mindihedral); - - void storeTetrahedra(const tetgenio& out); - - /** This private function calculates the densities and (cumulative) masses for all cells, and - logs some statistics. The function assumes that the cell volumes have been calculated, - either by building a Tetra tessellation, or by deriving the volume from mass and mass - density columns being imported. */ - void calculateDensityAndMass(); - - /** Private function to recursively build a binary search tree (see - en.wikipedia.org/wiki/Kd-tree) */ - Node* buildTree(vector::iterator first, vector::iterator last, int depth) const; - - /** This private function builds data structures that allow accelerating the operation of the - cellIndex() function. It assumes that the Tetra mesh has already been built. - - The domain is partitioned using a linear cubodial grid into cells that are called \em - blocks. For each block, the function builds and stores a list of all Tetra cells that - possibly overlap that block. Retrieving the list of cells possibly overlapping a given - point in the domain then comes down to locating the block containing the point (which is - trivial since the grid is linear). The current implementation uses a Tetra cell's - enclosing cuboid to test for intersection with a block. Performing a precise intersection - test is \em really slow and the shortened block lists don't substantially accelerate the - cellIndex() function. - - To further reduce the search time within blocks that overlap with a large number of cells, - the function builds a binary search tree on the cell sites for those blocks (see for example - en.wikipedia.org/wiki/Kd-tree). */ - void buildSearchPerBlock(); - - /** This private function builds a data structure that allows accelerating the operation of the - cellIndex() function without using the Tetra mesh. The domain is not partitioned in - blocks. The function builds a single binary search tree on all cell sites (see for example - en.wikipedia.org/wiki/Kd-tree). */ - void buildSearchSingle(); - - //====================== Output ===================== - -public: - /** This function outputs grid plot files as described for the SpatialGridPlotProbe. The - function reconstructs the Tetra tesselation in order to produce the coordinates of the - Tetra cell vertices. */ - void writeGridPlotFiles(const SimulationItem* probe) const; - - //=========== Interrogation ========== - -public: - /** This function returns the extent of the spatial domain as configured through the - setExtent() function. */ - Box extent() const override; - - /** This function returns the number of sites (or, equivalently, cells) in the snapshot. */ - int numEntities() const override; - - /** This function returns the position of the site with index \em m. If the index is out of - range, the behavior is undefined. */ - Position position(int m) const override; - - /** This function returns the volume of the Tetra cell with index \em m. If the index is out - of range, the behavior is undefined. */ - double volume(int m) const override; - - /** This function returns the bounding box (enclosing cuboid lined up with the coordinate axes) - of the Tetra cell with index \em m. If the index is out of range, the behavior is - undefined. */ - Box extent(int m) const; - - /** This function returns the mass density associated with the cell with index \em m. If no - density policy has been set or no mass information is being imported, or if the index is - out of range, the behavior is undefined. */ - double density(int m) const override; - - /** This function returns the mass density represented by the snapshot at a given point - \f${\bf{r}}\f$, or equivalently, the mass density associated with the cell containing the - given point. If the point is outside the domain, the function returns zero. If no density - policy has been set or no mass information is being imported, or if the search data - structures used by the cellIndex() function were not created during construction, the - behavior is undefined. */ - double density(Position bfr) const override; - - /** This function returns the total mass represented by the snapshot, in other words the sum of - the masses of all cells. If no density policy has been set or no mass information is being - imported, the behavior is undefined. */ - double mass() const override; - - /** This function returns a random position drawn uniformly from the (polyhedron) volume of the - cell with index \em m. If the index is out of range, the behavior is undefined. - - The function generates uniformly distributed random points in the enclosing cuboid until - one happens to be inside the cell. The candidate point is inside the cell if it is closer - to the cell's site position than to any neighbor cell's site positions. */ - Position generatePosition(int m) const override; - - /** This function returns a random position within the spatial domain of the snapshot, drawn - from the mass density distribution represented by the snapshot. The function first selects - a random cell from the discrete probability distribution formed by the respective cell - masses, and then generates a random position uniformly from the volume of that cell. If no - density policy has been set or no mass information is being imported, the behavior is - undefined. */ - Position generatePosition() const override; - - /** This function returns the cell index \f$0\le m \le N_{cells}-1\f$ for the cell containing - the specified point \f${\bf{r}}\f$. If the point is outside the domain, the function - returns -1. By definition of a Tetra tesselation, the closest site position determines - the Tetra cell containing the specified point. - - The function uses the search data structures created by the private BuildSearch() function - to accelerate its operation. It computes the appropriate block index from the coordinates - of the specified point, which provides a list of Tetra cells possibly overlapping the - point. If there is a search tree for this block, the function uses it to locate the nearest - point in \f${\cal{O}}(\log N)\f$ time. Otherwise it calculates the distance from the - specified point to the site positions for each of the possibly overlapping cells, - determining the nearest one in linear time. For a small number of cells this is more - efficient than using the search tree. - - If the search data structures were not created during construction (which happens when - using the default constructor without configuring a mass density policy), invoking the - cellIndex() function causes undefined behavior. */ - int cellIndex(Position bfr) const; - -protected: - /** This function returns a reference to an array containing the imported properties (in column - order) for the cell with index \f$0\le m \le N_\mathrm{ent}-1\f$. If the index is out of - range, the behavior is undefined. */ - const Array& properties(int m) const override; - - /** This function returns the index \f$0\le m \le N_\mathrm{ent}-1\f$ of the cell containing - the specified point \f${\bf{r}}\f$, or -1 if the point is outside the domain, if there - are no cells in the snapshot, or if the search data structures were not created. */ - int nearestEntity(Position bfr) const override; - -public: - /** This function sets the specified entity collection to the cell containing the specified - point \f${\bf{r}}\f$, or to the empty collection if the point is outside the domain or if - there are no cells in the snapshot. If the search data structures were not created, - invoking this function causes undefined behavior. */ - void getEntities(EntityCollection& entities, Position bfr) const override; - - /** This function replaces the contents of the specified entity collection by the set of cells - crossed by the specified path with starting point \f${\bf{r}}\f$ and direction - \f${\bf{k}}\f$. The weight of a cell is given by the length of the path segment inside the - cell. If the path does not cross the spatial domain of the snapshot, the collection will be - empty. If the search data structures were not created, invoking this function causes - undefined behavior. */ - void getEntities(EntityCollection& entities, Position bfr, Direction bfk) const override; - - //====================== Path construction ===================== - -public: - /** This function creates and hands over ownership of a path segment generator appropriate for - the adaptive mesh spatial grid, implemented as a private PathSegmentGenerator subclass. The - algorithm used to construct the path is described below. - - In the first stage, the function checks whether the start point is inside the domain. If - so, the current point is simply initialized to the start point. If not, the function - computes the path segment to the first intersection with one of the domain walls and moves - the current point inside the domain. Finally the function determines the current cell, i.e. - the cell containing the current point. - - In the second stage, the function loops over the algorithm that computes the exit point - from the current cell, i.e. the intersection of the ray formed by the current point and - the path direction with the current cell's boundary. By the nature of Tetra cells, this - algorithm also produces the ID of the neigboring cell without extra cost. If an exit point - is found, the loop adds a segment to the output path, updates the current point and the - current cell, and continues to the next iteration. If the exit is towards a domain wall, - the path is complete and the loop is terminated. If no exit point is found, which shouldn't - happen too often, this must be due to computational inaccuracies. In that case, no path - segment is added, the current point is advanced by a small amount, and the new current cell - is determined by calling the function whichcell(). - - The algorithm that computes the exit point has the following input data: - - - - - - - - -
\f$\bf{r}\f$ %Position of the current point
\f$\bf{k}\f$ %Direction of the ray, normalized
\f$m_r\f$ ID of the cell containing the current point
\f$m_i,\;i=0\ldots n-1\f$ IDs of the neighboring cells (\f$m_i>=0\f$) - or domain walls (\f$m_i<0\f$)
\f$\mathbf{p}(m)\f$ Site position for cell \f$m\f$ (implicit)
\f$x_\text{min},x_\text{max},y_\text{min},y_\text{max},z_\text{min},z_\text{max}\f$Domain boundaries (implicit)
- where the domain wall IDs are defined as follows: - - - - - - - - -
Domain wall ID Domain wall equation
-1 \f$x=x_\text{min}\f$
-2 \f$x=x_\text{max}\f$
-3 \f$y=y_\text{min}\f$
-4 \f$y=y_\text{max}\f$
-5 \f$z=z_\text{min}\f$
-6 \f$z=z_\text{max}\f$
- - The line containing the ray can be written as \f$\mathcal{L}(s)=\mathbf{r}+s\,\mathbf{k}\f$ - with \f$s\in\mathbb{R}\f$. The exit point can similarly be written as - \f$\mathbf{q}=\mathbf{r}+s_q\,\mathbf{k}\f$ with \f$s_q>0\f$, and the distance covered within - the cell is given by \f$s_q\f$. The ID of the cell next to the exit point is denoted \f$m_q\f$ - and is easily determined as explained below. - - The algorithm that computes the exit point proceeds as follows: - - Calculate the set of values \f$\{s_i\}\f$ for the intersection points between the line - \f$\mathcal{L}(s)\f$ and the planes defined by the neighbors or walls \f$m_i\f$ - (see below for details on the intersection calculation). - - Select the smallest nonnegative value \f$s_q=\min\{s_i|s_i>0\}\f$ in the set to determine - the exit point. The neighbor or wall ID with the same index \f$i\f$ determines the - corresponding \f$m_q\f$. - - If there is no nonnegative value in the set, no exit point has been found. - - To calculate \f$s_i\f$ for a regular neighbor \f$m_i\geq 0\f$, intersect the - line \f$\mathcal{L}(s)=\mathbf{r}+s\,\mathbf{k}\f$ with the plane bisecting the points - \f$\mathbf{p}(m_i)\f$ and \f$\mathbf{p}(m_r)\f$. An unnormalized vector perpendicular to - this plane is given by \f[\mathbf{n}=\mathbf{p}(m_i)-\mathbf{p}(m_r)\f] - and a point on the plane is given by - \f[\mathbf{p}=\frac{\mathbf{p}(m_i)+\mathbf{p}(m_r)}{2}.\f] The equation - of the plane can then be written as \f[\mathbf{n}\cdot(\mathbf{x}-\mathbf{p})=0.\f] - Substituting \f$\mathbf{x}=\mathbf{r}+s_i\,\mathbf{k}\f$ and solving for \f$s_i\f$ provides - \f[s_i=\frac{\mathbf{n}\cdot(\mathbf{p}-\mathbf{r})}{\mathbf{n}\cdot\mathbf{k}}.\f] - If \f$\mathbf{n}\cdot\mathbf{k}=0\f$ the line and the plane are parallel and there - is no intersection. In that case no \f$s_i\f$ is added to the set of candidate - exit points. - - To calculate \f$s_i\f$ for a wall \f$m_i<0\f$, substitute the appropriate normal and - position vectors for the wall plane in this last formula. For example, for the left wall - with \f$m_i=-1\f$ one has \f$\mathbf{n}=(-1,0,0)\f$ and \f$\mathbf{p}=(x_\text{min},0,0)\f$ - so that \f[s_i=\frac{x_\text{min}-r_x}{k_x}.\f] - */ - std::unique_ptr createPathSegmentGenerator() const; - - //======================== Data Members ======================== - -private: - // data members initialized during configuration - Box _extent; // the spatial domain of the mesh - double _eps{0.}; // small fraction of extent - int numTetra; - int numVertices; - - // input vertices - vector _sites; - vector _properties; - - // data members initialized when processing snapshot input and further completed by BuildMesh() - vector _tetrahedra; - vector _vertices; - vector _centroids; - - // data members initialized when processing snapshot input, but only if a density policy has been set - Array _rhov; // density for each cell (not normalized) - Array _cumrhov; // normalized cumulative density distribution for cells - double _mass{0.}; // total effective mass - - // data members initialized by BuildSearch() - int _nb{0}; // number of blocks in each dimension (limit for indices i,j,k) - int _nb2{0}; // nb*nb - int _nb3{0}; // nb*nb*nb - vector> _blocklists; // list of cell indices per block, indexed on i*_nb2+j*_nb+k - vector _blocktrees; // root node of search tree or null for each block, indexed on i*_nb2+j*_nb+k - - // allow our path segment generator to access our private data members - class MySegmentGenerator; - friend class MySegmentGenerator; -}; - -//////////////////////////////////////////////////////////////////// - -#endif diff --git a/SKIRT/core/TetraMeshSpatialGrid.cpp b/SKIRT/core/TetraMeshSpatialGrid.cpp index efb31be6..86f0b01d 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.cpp +++ b/SKIRT/core/TetraMeshSpatialGrid.cpp @@ -16,16 +16,6 @@ ////////////////////////////////////////////////////////////////////// -TetraMeshSpatialGrid::~TetraMeshSpatialGrid() -{ - for (auto vertex : _vertices) delete vertex; - for (auto centroid : _centroids) delete centroid; - for (auto tetra : _tetrahedra) delete tetra; // vertices are already deleted - for (auto tree : _blocktrees) delete tree; -} - -////////////////////////////////////////////////////////////////////// - namespace { // function to erase null pointers from a vector of pointers in one go; returns the new size @@ -109,6 +99,116 @@ namespace //////////////////////////////////////////////////////////////////// +TetraMeshSpatialGrid::~TetraMeshSpatialGrid() +{ + for (auto vertex : _vertices) delete vertex; + for (auto centroid : _centroids) delete centroid; + for (auto tetra : _tetrahedra) delete tetra; // vertices are already deleted + for (auto tree : _blocktrees) delete tree; +} + +////////////////////////////////////////////////////////////////////// + +void TetraMeshSpatialGrid::setupSelfBefore() +{ + BoxSpatialGrid::setupSelfBefore(); + + _log = find(); + _eps = 1e-12 * widths().norm(); + + // determine an appropriate set of sites and construct the Tetra mesh + switch (_policy) + { + case Policy::Uniform: + { + auto random = find(); + _vertices.resize(_numSites); + for (int m = 0; m != _numSites; ++m) _vertices[m] = new Vec((random->position(extent()))); + break; + } + case Policy::CentralPeak: + { + auto random = find(); + const int a = 1000; // steepness of the peak; the central 1/a portion is NOT covered + const double rscale = extent().rmax().norm(); + _vertices.resize(_numSites); + _vertices[0] = new Vec(0, 0, 0); + for (int m = 1; m != _numSites;) // skip first particle so that it remains (0,0,0) + { + double r = rscale * pow(1. / a, random->uniform()); // random distribution according to 1/x + Direction k = random->direction(); + Vec p = Position(r, k); + if (extent().contains(p)) _vertices[m++] = new Vec(p); // discard any points outside of the domain + } + break; + } + case Policy::DustDensity: + { + // build a list of media that have this material type with corresponding weights + vector media; + vector weights; + auto ms = find(); + for (auto medium : ms->media()) + if (medium->mix()->isDust()) media.push_back(medium); + for (auto medium : media) weights.push_back(medium->mass()); + vector sites = sampleMedia(media, weights, extent(), _numSites); + int n = sites.size(); + _vertices.resize(n); + for (int m = 0; m != n; ++m) _vertices[m] = new Vec(sites[m]); + break; + } + case Policy::ElectronDensity: + { + // build a list of media that have this material type with corresponding weights + vector media; + vector weights; + auto ms = find(); + for (auto medium : ms->media()) + if (medium->mix()->isElectrons()) media.push_back(medium); + for (auto medium : media) weights.push_back(medium->number()); + vector sites = sampleMedia(media, weights, extent(), _numSites); + int n = sites.size(); + _vertices.resize(n); + for (int m = 0; m != n; ++m) _vertices[m] = new Vec(sites[m]); + break; + } + case Policy::GasDensity: + { + // build a list of media that have this material type with corresponding weights + vector media; + vector weights; + auto ms = find(); + for (auto medium : ms->media()) + if (medium->mix()->isGas()) media.push_back(medium); + for (auto medium : media) weights.push_back(medium->number()); + vector sites = sampleMedia(media, weights, extent(), _numSites); + int n = sites.size(); + _vertices.resize(n); + for (int m = 0; m != n; ++m) _vertices[m] = new Vec(sites[m]); + break; + } + case Policy::ImportedSites: + { + auto sli = find()->interface(2); + // prepare the data + int n = sli->numSites(); + _vertices.resize(n); + for (int m = 0; m != n; ++m) _vertices[m] = new Vec(sli->sitePosition(m)); + break; + } + } + + if (_vertices.empty()) + { + throw FATALERROR("No vertices available for mesh generation"); + } + + buildMesh(); + buildSearchPerBlock(); +} + +////////////////////////////////////////////////////////////////////// + struct TetraMeshSpatialGrid::Face { Face() {} @@ -442,190 +542,8 @@ class TetraMeshSpatialGrid::Node } }; -TetraMeshSpatialGrid::Node* TetraMeshSpatialGrid::buildTree(vector::iterator first, vector::iterator last, - int depth) const -{ - auto length = last - first; - if (length > 0) - { - auto median = length >> 1; - std::nth_element(first, first + median, last, [this, depth](int m1, int m2) { - return m1 != m2 && lessthan(*_centroids[m1], *_centroids[m2], depth % 3); - }); - return new TetraMeshSpatialGrid::Node(*(first + median), depth, buildTree(first, first + median, depth + 1), - buildTree(first + median + 1, last, depth + 1)); - } - return nullptr; -} - -//////////////////////////////////////////////////////////////////// - -void TetraMeshSpatialGrid::buildSearchPerBlock() -{ - // abort if there are no cells - if (!_numTetra) return; - - _log->info("Building data structures to accelerate searching the tetrahedralisation"); - - // ------------- block lists ------------- - _nb = max(3, min(250, static_cast(cbrt(_numTetra)))); - _nb2 = _nb * _nb; - _nb3 = _nb * _nb * _nb; - - // initialize a vector of nb * nb * nb lists - _blocklists.resize(_nb3); - - // we add the tetrahedra to all blocks they potentially overlap with - // this will slow down the search tree but if no search tree is present - // we can simply loop over all tetrahedra inside the block - int i1, j1, k1, i2, j2, k2; - for (int c = 0; c != _numTetra; ++c) - { - cellIndices(i1, j1, k1, _tetrahedra[c]->rmin() - Vec(_eps, _eps, _eps), _nb, _nb, _nb); - cellIndices(i2, j2, k2, _tetrahedra[c]->rmax() + Vec(_eps, _eps, _eps), _nb, _nb, _nb); - for (int i = i1; i <= i2; i++) - for (int j = j1; j <= j2; j++) - for (int k = k1; k <= k2; k++) _blocklists[i * _nb2 + j * _nb + k].push_back(c); - } - - // compile block list statistics - int minRefsPerBlock = INT_MAX; - int maxRefsPerBlock = 0; - int64_t totalBlockRefs = 0; - for (int b = 0; b < _nb3; b++) - { - int refs = _blocklists[b].size(); - totalBlockRefs += refs; - minRefsPerBlock = min(minRefsPerBlock, refs); - maxRefsPerBlock = max(maxRefsPerBlock, refs); - } - double avgRefsPerBlock = double(totalBlockRefs) / _nb3; - - // log block list statistics - _log->info(" Number of blocks in search grid: " + std::to_string(_nb3) + " (" + std::to_string(_nb) + "^3)"); - _log->info(" Average number of cells per block: " + StringUtils::toString(avgRefsPerBlock, 'f', 1)); - _log->info(" Minimum number of cells per block: " + std::to_string(minRefsPerBlock)); - _log->info(" Maximum number of cells per block: " + std::to_string(maxRefsPerBlock)); - - // ------------- search trees ------------- - - // for each block that contains more than a predefined number of cells, - // construct a search tree on the site locations of the cells - _blocktrees.resize(_nb3); - for (int b = 0; b < _nb3; b++) - { - vector& ids = _blocklists[b]; - if (ids.size() > 9) _blocktrees[b] = buildTree(ids.begin(), ids.end(), 0); - } - - // compile and log search tree statistics - int numTrees = 0; - for (int b = 0; b < _nb3; b++) - if (_blocktrees[b]) numTrees++; - _log->info(" Number of search trees: " + std::to_string(numTrees) + " (" - + StringUtils::toString(100. * numTrees / _nb3, 'f', 1) + "% of blocks)"); -} - //////////////////////////////////////////////////////////////////// -void TetraMeshSpatialGrid::setupSelfBefore() -{ - BoxSpatialGrid::setupSelfBefore(); - - _log = find(); - _eps = 1e-12 * widths().norm(); - - // determine an appropriate set of sites and construct the Tetra mesh - switch (_policy) - { - case Policy::Uniform: - { - auto random = find(); - _vertices.resize(_numSites); - for (int m = 0; m != _numSites; ++m) _vertices[m] = new Vec((random->position(extent()))); - break; - } - case Policy::CentralPeak: - { - auto random = find(); - const int a = 1000; // steepness of the peak; the central 1/a portion is NOT covered - const double rscale = extent().rmax().norm(); - _vertices.resize(_numSites); - _vertices[0] = new Vec(0, 0, 0); - for (int m = 1; m != _numSites;) // skip first particle so that it remains (0,0,0) - { - double r = rscale * pow(1. / a, random->uniform()); // random distribution according to 1/x - Direction k = random->direction(); - Vec p = Position(r, k); - if (extent().contains(p)) _vertices[m++] = new Vec(p); // discard any points outside of the domain - } - break; - } - case Policy::DustDensity: - { - // build a list of media that have this material type with corresponding weights - vector media; - vector weights; - auto ms = find(); - for (auto medium : ms->media()) - if (medium->mix()->isDust()) media.push_back(medium); - for (auto medium : media) weights.push_back(medium->mass()); - vector sites = sampleMedia(media, weights, extent(), _numSites); - int n = sites.size(); - _vertices.resize(n); - for (int m = 0; m != n; ++m) _vertices[m] = new Vec(sites[m]); - break; - } - case Policy::ElectronDensity: - { - // build a list of media that have this material type with corresponding weights - vector media; - vector weights; - auto ms = find(); - for (auto medium : ms->media()) - if (medium->mix()->isElectrons()) media.push_back(medium); - for (auto medium : media) weights.push_back(medium->number()); - vector sites = sampleMedia(media, weights, extent(), _numSites); - int n = sites.size(); - _vertices.resize(n); - for (int m = 0; m != n; ++m) _vertices[m] = new Vec(sites[m]); - break; - } - case Policy::GasDensity: - { - // build a list of media that have this material type with corresponding weights - vector media; - vector weights; - auto ms = find(); - for (auto medium : ms->media()) - if (medium->mix()->isGas()) media.push_back(medium); - for (auto medium : media) weights.push_back(medium->number()); - vector sites = sampleMedia(media, weights, extent(), _numSites); - int n = sites.size(); - _vertices.resize(n); - for (int m = 0; m != n; ++m) _vertices[m] = new Vec(sites[m]); - break; - } - case Policy::ImportedSites: - { - auto sli = find()->interface(2); - // prepare the data - int n = sli->numSites(); - _vertices.resize(n); - for (int m = 0; m != n; ++m) _vertices[m] = new Vec(sli->sitePosition(m)); - break; - } - } - - if (_vertices.empty()) - { - throw FATALERROR("No vertices available for mesh generation"); - } - - buildMesh(); - buildSearchPerBlock(); -} - void TetraMeshSpatialGrid::buildMesh() { tetgenio delaunay, refined; @@ -787,6 +705,92 @@ void TetraMeshSpatialGrid::storeTetrahedra(const tetgenio& out, bool storeVertic ////////////////////////////////////////////////////////////////////// +TetraMeshSpatialGrid::Node* TetraMeshSpatialGrid::buildTree(vector::iterator first, vector::iterator last, + int depth) const +{ + auto length = last - first; + if (length > 0) + { + auto median = length >> 1; + std::nth_element(first, first + median, last, [this, depth](int m1, int m2) { + return m1 != m2 && lessthan(*_centroids[m1], *_centroids[m2], depth % 3); + }); + return new TetraMeshSpatialGrid::Node(*(first + median), depth, buildTree(first, first + median, depth + 1), + buildTree(first + median + 1, last, depth + 1)); + } + return nullptr; +} + +//////////////////////////////////////////////////////////////////// + +void TetraMeshSpatialGrid::buildSearchPerBlock() +{ + // abort if there are no cells + if (!_numTetra) return; + + _log->info("Building data structures to accelerate searching the tetrahedralisation"); + + // ------------- block lists ------------- + _nb = max(3, min(250, static_cast(cbrt(_numTetra)))); + _nb2 = _nb * _nb; + _nb3 = _nb * _nb * _nb; + + // initialize a vector of nb * nb * nb lists + _blocklists.resize(_nb3); + + // we add the tetrahedra to all blocks they potentially overlap with + // this will slow down the search tree but if no search tree is present + // we can simply loop over all tetrahedra inside the block + int i1, j1, k1, i2, j2, k2; + for (int c = 0; c != _numTetra; ++c) + { + cellIndices(i1, j1, k1, _tetrahedra[c]->rmin() - Vec(_eps, _eps, _eps), _nb, _nb, _nb); + cellIndices(i2, j2, k2, _tetrahedra[c]->rmax() + Vec(_eps, _eps, _eps), _nb, _nb, _nb); + for (int i = i1; i <= i2; i++) + for (int j = j1; j <= j2; j++) + for (int k = k1; k <= k2; k++) _blocklists[i * _nb2 + j * _nb + k].push_back(c); + } + + // compile block list statistics + int minRefsPerBlock = INT_MAX; + int maxRefsPerBlock = 0; + int64_t totalBlockRefs = 0; + for (int b = 0; b < _nb3; b++) + { + int refs = _blocklists[b].size(); + totalBlockRefs += refs; + minRefsPerBlock = min(minRefsPerBlock, refs); + maxRefsPerBlock = max(maxRefsPerBlock, refs); + } + double avgRefsPerBlock = double(totalBlockRefs) / _nb3; + + // log block list statistics + _log->info(" Number of blocks in search grid: " + std::to_string(_nb3) + " (" + std::to_string(_nb) + "^3)"); + _log->info(" Average number of cells per block: " + StringUtils::toString(avgRefsPerBlock, 'f', 1)); + _log->info(" Minimum number of cells per block: " + std::to_string(minRefsPerBlock)); + _log->info(" Maximum number of cells per block: " + std::to_string(maxRefsPerBlock)); + + // ------------- search trees ------------- + + // for each block that contains more than a predefined number of cells, + // construct a search tree on the site locations of the cells + _blocktrees.resize(_nb3); + for (int b = 0; b < _nb3; b++) + { + vector& ids = _blocklists[b]; + if (ids.size() > 9) _blocktrees[b] = buildTree(ids.begin(), ids.end(), 0); + } + + // compile and log search tree statistics + int numTrees = 0; + for (int b = 0; b < _nb3; b++) + if (_blocktrees[b]) numTrees++; + _log->info(" Number of search trees: " + std::to_string(numTrees) + " (" + + StringUtils::toString(100. * numTrees / _nb3, 'f', 1) + "% of blocks)"); +} + +////////////////////////////////////////////////////////////////////// + int TetraMeshSpatialGrid::numCells() const { return _numTetra; @@ -815,6 +819,68 @@ double TetraMeshSpatialGrid::diagonal(int m) const } ////////////////////////////////////////////////////////////////////// +int TetraMeshSpatialGrid::cellIndex(Position bfr) const +{ + // Ensure the position is inside the domain + if (!contains(bfr)) return -1; + + // Determine the block in which the point falls + int i, j, k; + cellIndices(i, j, k, bfr, _nb, _nb, _nb); + int b = i * _nb2 + j * _nb + k; + + // Look for the closest centroid in this block using the search tree + Node* tree = _blocktrees[b]; + if (!tree) + { + _log->error("No search tree found for block"); + return -1; + } + + // Full traversal algorithm + int enteringFace = -1; + int m = tree->nearest(bfr, _centroids)->m(); + const Tetra* tetra = _tetrahedra[m]; + + Vec pos = tetra->_centroid; + Direction dir(bfr - pos); + double dist = dir.norm(); + dir /= dist; + + double ds; + int leavingFace; + while (true) + { + std::tie(ds, leavingFace) = tetra->traverse(pos, dir, enteringFace); + + // If no exit point was found, break + if (leavingFace == -1) break; + + // Move to the exit point + pos += ds * dir; + dist -= ds; + if (dist <= 0) return m; + + // Get neighbour information + m = tetra->_faces[leavingFace]._ntetra; + enteringFace = tetra->_faces[leavingFace]._nface; + + // If no next tetrahedron, break + if (m < 0) break; + + tetra = _tetrahedra[m]; + } + + // If search tree traversal failed, loop over all tetrahedra in the block + for (int t : _blocklists[b]) + if (_tetrahedra[t]->inside(bfr)) return t; + + _log->error("cellIndex failed to find the tetrahedron"); + return -1; +} + +////////////////////////////////////////////////////////////////////// + Position TetraMeshSpatialGrid::centralPositionInCell(int m) const { return Position(_tetrahedra[m]->_centroid); @@ -881,68 +947,6 @@ void TetraMeshSpatialGrid::writeGridPlotFiles(const SimulationItem* probe) const ////////////////////////////////////////////////////////////////////// -int TetraMeshSpatialGrid::cellIndex(Position bfr) const -{ - // Ensure the position is inside the domain - if (!contains(bfr)) return -1; - - // Determine the block in which the point falls - int i, j, k; - cellIndices(i, j, k, bfr, _nb, _nb, _nb); - int b = i * _nb2 + j * _nb + k; - - // Look for the closest centroid in this block using the search tree - Node* tree = _blocktrees[b]; - if (!tree) - { - _log->error("No search tree found for block"); - return -1; - } - - // Full traversal algorithm - int enteringFace = -1; - int m = tree->nearest(bfr, _centroids)->m(); - const Tetra* tetra = _tetrahedra[m]; - - Vec pos = tetra->_centroid; - Direction dir(bfr - pos); - double dist = dir.norm(); - dir /= dist; - - double ds; - int leavingFace; - while (true) - { - std::tie(ds, leavingFace) = tetra->traverse(pos, dir, enteringFace); - - // If no exit point was found, break - if (leavingFace == -1) break; - - // Move to the exit point - pos += ds * dir; - dist -= ds; - if (dist <= 0) return m; - - // Get neighbour information - m = tetra->_faces[leavingFace]._ntetra; - enteringFace = tetra->_faces[leavingFace]._nface; - - // If no next tetrahedron, break - if (m < 0) break; - - tetra = _tetrahedra[m]; - } - - // If search tree traversal failed, loop over all tetrahedra in the block - for (int t : _blocklists[b]) - if (_tetrahedra[t]->inside(bfr)) return t; - - _log->error("cellIndex failed to find the tetrahedron"); - return -1; -} - -////////////////////////////////////////////////////////////////////// - class TetraMeshSpatialGrid::MySegmentGenerator : public PathSegmentGenerator { const TetraMeshSpatialGrid* _grid{nullptr}; diff --git a/SKIRT/core/TetraMeshSpatialGrid.hpp b/SKIRT/core/TetraMeshSpatialGrid.hpp index 3812dfdd..ffcf7879 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.hpp +++ b/SKIRT/core/TetraMeshSpatialGrid.hpp @@ -46,14 +46,11 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid ATTRIBUTE_DEFAULT_VALUE(policy, "DustDensity") PROPERTY_INT(numSites, "the number of random sites to be used as vertices") - ATTRIBUTE_MIN_VALUE(numSites, "5") + ATTRIBUTE_MIN_VALUE(numSites, "4") ATTRIBUTE_DEFAULT_VALUE(numSites, "500") ATTRIBUTE_RELEVANT_IF(numSites, "policyUniform|policyCentralPeak|policyDustDensity|" "policyElectronDensity|policyGasDensity") - PROPERTY_STRING(filename, "the name of the file containing the site positions") - ATTRIBUTE_RELEVANT_IF(filename, "policyFile") - PROPERTY_BOOL(refine, "refine the grid to have higher quality cells by adding more vertices") ATTRIBUTE_DEFAULT_VALUE(refine, "false"); From b4059f822e905030648a85e90b22a917020c64d8 Mon Sep 17 00:00:00 2001 From: arlauwer Date: Mon, 2 Dec 2024 09:27:38 +0100 Subject: [PATCH 34/51] formatting --- SKIRT/core/TetraMeshSpatialGrid.cpp | 405 +++++++++++++++------------- SKIRT/core/TetraMeshSpatialGrid.hpp | 10 +- 2 files changed, 219 insertions(+), 196 deletions(-) diff --git a/SKIRT/core/TetraMeshSpatialGrid.cpp b/SKIRT/core/TetraMeshSpatialGrid.cpp index 86f0b01d..53a3e066 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.cpp +++ b/SKIRT/core/TetraMeshSpatialGrid.cpp @@ -97,116 +97,6 @@ namespace } } -//////////////////////////////////////////////////////////////////// - -TetraMeshSpatialGrid::~TetraMeshSpatialGrid() -{ - for (auto vertex : _vertices) delete vertex; - for (auto centroid : _centroids) delete centroid; - for (auto tetra : _tetrahedra) delete tetra; // vertices are already deleted - for (auto tree : _blocktrees) delete tree; -} - -////////////////////////////////////////////////////////////////////// - -void TetraMeshSpatialGrid::setupSelfBefore() -{ - BoxSpatialGrid::setupSelfBefore(); - - _log = find(); - _eps = 1e-12 * widths().norm(); - - // determine an appropriate set of sites and construct the Tetra mesh - switch (_policy) - { - case Policy::Uniform: - { - auto random = find(); - _vertices.resize(_numSites); - for (int m = 0; m != _numSites; ++m) _vertices[m] = new Vec((random->position(extent()))); - break; - } - case Policy::CentralPeak: - { - auto random = find(); - const int a = 1000; // steepness of the peak; the central 1/a portion is NOT covered - const double rscale = extent().rmax().norm(); - _vertices.resize(_numSites); - _vertices[0] = new Vec(0, 0, 0); - for (int m = 1; m != _numSites;) // skip first particle so that it remains (0,0,0) - { - double r = rscale * pow(1. / a, random->uniform()); // random distribution according to 1/x - Direction k = random->direction(); - Vec p = Position(r, k); - if (extent().contains(p)) _vertices[m++] = new Vec(p); // discard any points outside of the domain - } - break; - } - case Policy::DustDensity: - { - // build a list of media that have this material type with corresponding weights - vector media; - vector weights; - auto ms = find(); - for (auto medium : ms->media()) - if (medium->mix()->isDust()) media.push_back(medium); - for (auto medium : media) weights.push_back(medium->mass()); - vector sites = sampleMedia(media, weights, extent(), _numSites); - int n = sites.size(); - _vertices.resize(n); - for (int m = 0; m != n; ++m) _vertices[m] = new Vec(sites[m]); - break; - } - case Policy::ElectronDensity: - { - // build a list of media that have this material type with corresponding weights - vector media; - vector weights; - auto ms = find(); - for (auto medium : ms->media()) - if (medium->mix()->isElectrons()) media.push_back(medium); - for (auto medium : media) weights.push_back(medium->number()); - vector sites = sampleMedia(media, weights, extent(), _numSites); - int n = sites.size(); - _vertices.resize(n); - for (int m = 0; m != n; ++m) _vertices[m] = new Vec(sites[m]); - break; - } - case Policy::GasDensity: - { - // build a list of media that have this material type with corresponding weights - vector media; - vector weights; - auto ms = find(); - for (auto medium : ms->media()) - if (medium->mix()->isGas()) media.push_back(medium); - for (auto medium : media) weights.push_back(medium->number()); - vector sites = sampleMedia(media, weights, extent(), _numSites); - int n = sites.size(); - _vertices.resize(n); - for (int m = 0; m != n; ++m) _vertices[m] = new Vec(sites[m]); - break; - } - case Policy::ImportedSites: - { - auto sli = find()->interface(2); - // prepare the data - int n = sli->numSites(); - _vertices.resize(n); - for (int m = 0; m != n; ++m) _vertices[m] = new Vec(sli->sitePosition(m)); - break; - } - } - - if (_vertices.empty()) - { - throw FATALERROR("No vertices available for mesh generation"); - } - - buildMesh(); - buildSearchPerBlock(); -} - ////////////////////////////////////////////////////////////////////// struct TetraMeshSpatialGrid::Face @@ -218,7 +108,7 @@ struct TetraMeshSpatialGrid::Face // setters & getters required? - Vec _normal; + Vec _normal; // outward facing normal int _ntetra; // index of neighbouring tetrahedron int _nface; // neighbouring face index }; @@ -228,14 +118,13 @@ struct TetraMeshSpatialGrid::Face class TetraMeshSpatialGrid::Tetra : public Box { private: - double _volume; - -public: - const std::array _vertices; - std::array _faces; Vec _centroid; + std::array _vertices; + std::array _faces; public: + ~Tetra() {} // vertices are deleted in ~TetraMeshSpatialGrid + Tetra(const std::array& vertices, const std::array& faces) : _vertices(vertices), _faces(faces) { double xmin = DBL_MAX; @@ -255,10 +144,7 @@ class TetraMeshSpatialGrid::Tetra : public Box } setExtent(Box(xmin, ymin, zmin, xmax, ymax, zmax)); - // volume - _volume = 1 / 6. * abs(Vec::dot(Vec::cross(getEdge(0, 1), getEdge(0, 2)), getEdge(0, 3))); - - // barycenter + // average position of all vertices for (int i = 0; i < 4; i++) _centroid += *_vertices[i]; _centroid /= 4; @@ -266,8 +152,8 @@ class TetraMeshSpatialGrid::Tetra : public Box for (int f = 0; f < 4; f++) { std::array cv = clockwiseVertices(f); - Vec e12 = getEdge(cv[0], cv[1]); - Vec e13 = getEdge(cv[0], cv[2]); + Vec e12 = edge(cv[0], cv[1]); + Vec e13 = edge(cv[0], cv[2]); Vec normal = Vec::cross(e12, e13); normal /= normal.norm(); @@ -280,24 +166,27 @@ class TetraMeshSpatialGrid::Tetra : public Box { int leavingFace = -1; double ds = DBL_MAX; - // loop in case no exit point was found (which should happen only rarely) // find entering face using a single Plücker product if (enteringFace == -1) enteringFace = findEnteringFace(pos, dir); // the translated Plücker moment in the local coordinate system Vec moment = Vec::cross(dir, pos - *_vertices[enteringFace]); + + // clockwise vertices around vertex 0 std::array cv = clockwiseVertices(enteringFace); - // 2 step decision tree - double prod0 = Vec::dot(moment, getEdge(cv[0], enteringFace)); + // determine orientations for use in the decision tree + double prod0 = Vec::dot(moment, edge(cv[0], enteringFace)); int clock0 = prod0 < 0; // if clockwise move clockwise else move cclockwise int i = clock0 ? 1 : 2; - double prodi = Vec::dot(moment, getEdge(cv[i], enteringFace)); + double prodi = Vec::dot(moment, edge(cv[i], enteringFace)); int cclocki = prodi >= 0; // use plane intersection algorithm if Plücker products are ambiguous + // this is actually more strict than the algorithm described by Maria (2017) + // but these edge cases are incredibly rare and can cause issues if (prod0 == 0. || prodi == 0.) { for (int face : cv) @@ -324,7 +213,7 @@ class TetraMeshSpatialGrid::Tetra : public Box // 0 0 -> 1 // 1 0 -> 0 // 0 1 -> 0 - constexpr int dtable[2][2] = {{1, 0}, {0, 2}}; + static constexpr int dtable[2][2] = {{1, 0}, {0, 2}}; leavingFace = cv[dtable[clock0][cclocki]]; const Vec& n = _faces[leavingFace]._normal; const Vec& v = *_vertices[enteringFace]; @@ -337,7 +226,32 @@ class TetraMeshSpatialGrid::Tetra : public Box //////////////////////////////////////////////////////////////////// - Vec getEdge(int t1, int t2) const { return *_vertices[t2] - *_vertices[t1]; } + int findEnteringFace(const Vec& pos, const Direction& dir) const + { + int enteringFace = -1; + // clockwise and cclockwise adjacent faces when checking edge v1->v2 + static constexpr int etable[6][2] = {{3, 2}, {1, 3}, {2, 1}, {3, 0}, {0, 2}, {1, 0}}; // make this static? + // try all 6 edges because of very rare edge cases where ray is inside edge + // having only 1 non-zero Plücker product + int e = 0; + for (int v1 = 0; v1 < 3; v1++) + { + for (int v2 = v1 + 1; v2 < 4; v2++) + { + + Vec moment12 = Vec::cross(dir, pos - *_vertices[v1]); + double prod12 = Vec::dot(moment12, edge(v1, v2)); + if (prod12 != 0.) + { + enteringFace = prod12 < 0 ? etable[e][0] : etable[e][1]; + break; + } + e++; + } + if (enteringFace != -1) break; + } + return enteringFace; + } //////////////////////////////////////////////////////////////////// @@ -347,7 +261,7 @@ class TetraMeshSpatialGrid::Tetra : public Box // if (!Box::contains(bfr)) return false; // could optimize this slightly by using same vertex for 3 faces and do final face seperately - + // but this function acts only as a backup for (int f = 0; f < 4; f++) { const Face& face = _faces[f]; @@ -359,12 +273,6 @@ class TetraMeshSpatialGrid::Tetra : public Box return true; } - //////////////////////////////////////////////////////////////////// - - double volume() const { return _volume; } - - //////////////////////////////////////////////////////////////////// - double generateBarycentric(double& s, double& t, double& u) const { // https://vcg.isti.cnr.it/activities/OLD/geometryegraphics/pointintetraedro.html @@ -390,8 +298,12 @@ class TetraMeshSpatialGrid::Tetra : public Box //////////////////////////////////////////////////////////////////// - Position generatePosition(double s, double t, double u) const + Position generatePosition(Random* random) const { + double s = random->uniform(); + double t = random->uniform(); + double u = random->uniform(); + double r = generateBarycentric(s, t, u); return Position(r * *_vertices[0] + u * *_vertices[1] + t * *_vertices[2] + s * *_vertices[3]); @@ -399,35 +311,41 @@ class TetraMeshSpatialGrid::Tetra : public Box //////////////////////////////////////////////////////////////////// - int findEnteringFace(const Vec& pos, const Direction& dir) const + Vec edge(int t1, int t2) const { return *_vertices[t2] - *_vertices[t1]; } + + //////////////////////////////////////////////////////////////////// + + double volume() const { return 1 / 6. * abs(Vec::dot(Vec::cross(edge(0, 1), edge(0, 2)), edge(0, 3))); } + + double diagonal() const { - int enteringFace = -1; - // clockwise and cclockwise adjacent faces when checking edge v1->v2 - constexpr int etable[6][2] = {{3, 2}, {1, 3}, {2, 1}, {3, 0}, {0, 2}, {1, 0}}; - // try all 6 edges because of rare edge cases where ray is inside edge - // having only 1 non-zero Plücker product - int e = 0; - for (int v1 = 0; v1 < 3; v1++) + double sum = 0.0; + for (int i = 0; i < 3; ++i) { - for (int v2 = v1 + 1; v2 < 4; v2++) + for (int j = i + 1; j < 4; ++j) { - - Vec moment12 = Vec::cross(dir, pos - *_vertices[v1]); - double prod12 = Vec::dot(moment12, getEdge(v1, v2)); - if (prod12 != 0.) - { - enteringFace = prod12 < 0 ? etable[e][0] : etable[e][1]; - break; - } - e++; + sum += edge(i, j).norm2(); } - if (enteringFace != -1) break; } - return enteringFace; + return sqrt(sum / 6.0); } + + //////////////////////////////////////////////////////////////////// + + const Face& face(int f) const { return _faces[f]; } + + //////////////////////////////////////////////////////////////////// + + const Vec& centroid() const { return _centroid; } + + //////////////////////////////////////////////////////////////////// + + const Vec& vertex(int v) const { return *_vertices[v]; } + + //////////////////////////////////////////////////////////////////// }; -//////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////// class TetraMeshSpatialGrid::Node { @@ -466,19 +384,19 @@ class TetraMeshSpatialGrid::Node Node* right() const { return _right; } // returns the apropriate child for the specified query point - Node* child(Vec bfr, const vector& points) const + Node* child(Vec bfr, const vector& points) const { return lessthan(bfr, *points[_m], _axis) ? _left : _right; } // returns the other child than the one that would be apropriate for the specified query point - Node* otherChild(Vec bfr, const vector& points) const + Node* otherChild(Vec bfr, const vector& points) const { return lessthan(bfr, *points[_m], _axis) ? _right : _left; } // returns the squared distance from the query point to the split plane - double squaredDistanceToSplitPlane(Vec bfr, const vector& points) const + double squaredDistanceToSplitPlane(Vec bfr, const vector& points) const { switch (_axis) { @@ -494,7 +412,7 @@ class TetraMeshSpatialGrid::Node } // returns the node in this subtree that represents the site nearest to the query point - Node* nearest(Vec bfr, const vector& points) + Node* nearest(Vec bfr, const vector& points) { // recursively descend the tree until a leaf node is reached, going left or right depending on // whether the specified point is less than or greater than the current node in the split dimension @@ -544,6 +462,116 @@ class TetraMeshSpatialGrid::Node //////////////////////////////////////////////////////////////////// +TetraMeshSpatialGrid::~TetraMeshSpatialGrid() +{ + for (auto vertex : _vertices) delete vertex; + for (auto centroid : _centroids) delete centroid; + for (auto tetra : _tetrahedra) delete tetra; // vertices are already deleted + for (auto tree : _blocktrees) delete tree; +} + +////////////////////////////////////////////////////////////////////// + +void TetraMeshSpatialGrid::setupSelfBefore() +{ + BoxSpatialGrid::setupSelfBefore(); + + _log = find(); + _eps = 1e-12 * widths().norm(); + + // determine an appropriate set of sites and construct the Tetra mesh + switch (_policy) + { + case Policy::Uniform: + { + auto random = find(); + _vertices.resize(_numSites); + for (int m = 0; m != _numSites; ++m) _vertices[m] = new Vec((random->position(extent()))); + break; + } + case Policy::CentralPeak: + { + auto random = find(); + const int a = 1000; // steepness of the peak; the central 1/a portion is NOT covered + const double rscale = extent().rmax().norm(); + _vertices.resize(_numSites); + _vertices[0] = new Vec(0, 0, 0); + for (int m = 1; m != _numSites;) // skip first particle so that it remains (0,0,0) + { + double r = rscale * pow(1. / a, random->uniform()); // random distribution according to 1/x + Direction k = random->direction(); + Vec p = Position(r, k); + if (extent().contains(p)) _vertices[m++] = new Vec(p); // discard any points outside of the domain + } + break; + } + case Policy::DustDensity: + { + // build a list of media that have this material type with corresponding weights + vector media; + vector weights; + auto ms = find(); + for (auto medium : ms->media()) + if (medium->mix()->isDust()) media.push_back(medium); + for (auto medium : media) weights.push_back(medium->mass()); + vector sites = sampleMedia(media, weights, extent(), _numSites); + int n = sites.size(); + _vertices.resize(n); + for (int m = 0; m != n; ++m) _vertices[m] = new Vec(sites[m]); + break; + } + case Policy::ElectronDensity: + { + // build a list of media that have this material type with corresponding weights + vector media; + vector weights; + auto ms = find(); + for (auto medium : ms->media()) + if (medium->mix()->isElectrons()) media.push_back(medium); + for (auto medium : media) weights.push_back(medium->number()); + vector sites = sampleMedia(media, weights, extent(), _numSites); + int n = sites.size(); + _vertices.resize(n); + for (int m = 0; m != n; ++m) _vertices[m] = new Vec(sites[m]); + break; + } + case Policy::GasDensity: + { + // build a list of media that have this material type with corresponding weights + vector media; + vector weights; + auto ms = find(); + for (auto medium : ms->media()) + if (medium->mix()->isGas()) media.push_back(medium); + for (auto medium : media) weights.push_back(medium->number()); + vector sites = sampleMedia(media, weights, extent(), _numSites); + int n = sites.size(); + _vertices.resize(n); + for (int m = 0; m != n; ++m) _vertices[m] = new Vec(sites[m]); + break; + } + case Policy::ImportedSites: + { + auto sli = find()->interface(2); + // prepare the data + int n = sli->numSites(); + _vertices.resize(n); + for (int m = 0; m != n; ++m) _vertices[m] = new Vec(sli->sitePosition(m)); + break; + } + } + + if (_vertices.empty()) + { + throw FATALERROR("No vertices available for mesh generation"); + } + + buildMesh(); + buildSearchPerBlock(); +} + +//////////////////////////////////////////////////////////////////// + void TetraMeshSpatialGrid::buildMesh() { tetgenio delaunay, refined; @@ -673,7 +701,7 @@ void TetraMeshSpatialGrid::storeTetrahedra(const tetgenio& out, bool storeVertic _tetrahedra[i] = new Tetra(vertices, faces); - _centroids.push_back(&_tetrahedra[i]->_centroid); + _centroids.push_back(&_tetrahedra[i]->centroid()); } // compile statistics @@ -809,12 +837,12 @@ double TetraMeshSpatialGrid::diagonal(int m) const { // can use for loop but this is more readable const Tetra* tetra = _tetrahedra[m]; - double a = tetra->getEdge(0, 1).norm2(); - double b = tetra->getEdge(0, 2).norm2(); - double c = tetra->getEdge(0, 3).norm2(); - double d = tetra->getEdge(1, 2).norm2(); - double e = tetra->getEdge(1, 3).norm2(); - double f = tetra->getEdge(2, 3).norm2(); + double a = tetra->edge(0, 1).norm2(); + double b = tetra->edge(0, 2).norm2(); + double c = tetra->edge(0, 3).norm2(); + double d = tetra->edge(1, 2).norm2(); + double e = tetra->edge(1, 3).norm2(); + double f = tetra->edge(2, 3).norm2(); return sqrt(a + b + c + d + e + f) / sqrt(6.0); } ////////////////////////////////////////////////////////////////////// @@ -831,24 +859,19 @@ int TetraMeshSpatialGrid::cellIndex(Position bfr) const // Look for the closest centroid in this block using the search tree Node* tree = _blocktrees[b]; - if (!tree) - { - _log->error("No search tree found for block"); - return -1; - } - - // Full traversal algorithm - int enteringFace = -1; + if (!tree) throw FATALERROR("No search tree found for block " + std::to_string(b)); int m = tree->nearest(bfr, _centroids)->m(); const Tetra* tetra = _tetrahedra[m]; - Vec pos = tetra->_centroid; + // traverse from centroid towards bfr until we pass it + Vec pos = tetra->centroid(); Direction dir(bfr - pos); double dist = dir.norm(); dir /= dist; double ds; int leavingFace; + int enteringFace = -1; while (true) { std::tie(ds, leavingFace) = tetra->traverse(pos, dir, enteringFace); @@ -862,8 +885,8 @@ int TetraMeshSpatialGrid::cellIndex(Position bfr) const if (dist <= 0) return m; // Get neighbour information - m = tetra->_faces[leavingFace]._ntetra; - enteringFace = tetra->_faces[leavingFace]._nface; + m = tetra->face(leavingFace)._ntetra; + enteringFace = tetra->face(leavingFace)._nface; // If no next tetrahedron, break if (m < 0) break; @@ -871,29 +894,25 @@ int TetraMeshSpatialGrid::cellIndex(Position bfr) const tetra = _tetrahedra[m]; } - // If search tree traversal failed, loop over all tetrahedra in the block + // If traversal algorithm failed to find correct cell, loop over all tetrahedra in the block for (int t : _blocklists[b]) if (_tetrahedra[t]->inside(bfr)) return t; - _log->error("cellIndex failed to find the tetrahedron"); - return -1; + throw FATALERROR("Can't find random position in cell"); } ////////////////////////////////////////////////////////////////////// Position TetraMeshSpatialGrid::centralPositionInCell(int m) const { - return Position(_tetrahedra[m]->_centroid); + return Position(_tetrahedra[m]->centroid()); } ////////////////////////////////////////////////////////////////////// Position TetraMeshSpatialGrid::randomPositionInCell(int m) const { - double s = random()->uniform(); - double t = random()->uniform(); - double u = random()->uniform(); - return _tetrahedra[m]->generatePosition(s, t, u); + return _tetrahedra[m]->generatePosition(random()); } ////////////////////////////////////////////////////////////////////// @@ -920,10 +939,10 @@ void TetraMeshSpatialGrid::writeGridPlotFiles(const SimulationItem* probe) const for (int v = 0; v < 4; v++) { - const Vec* vertex = tetra->_vertices[v]; - coords.push_back(vertex->x()); - coords.push_back(vertex->y()); - coords.push_back(vertex->z()); + const Vec vertex = tetra->vertex(v); + coords.push_back(vertex.x()); + coords.push_back(vertex.y()); + coords.push_back(vertex.z()); // get vertices of opposite face std::array faceIndices = clockwiseVertices(v); @@ -1009,7 +1028,7 @@ class TetraMeshSpatialGrid::MySegmentGenerator : public PathSegmentGenerator { propagater(ds); setSegment(_mr, ds); - _mr = tetra->_faces[leavingFace]._ntetra; + _mr = tetra->face(leavingFace)._ntetra; if (_mr < 0) { @@ -1018,7 +1037,7 @@ class TetraMeshSpatialGrid::MySegmentGenerator : public PathSegmentGenerator } else { - _enteringFace = tetra->_faces[leavingFace]._nface; + _enteringFace = tetra->face(leavingFace)._nface; return true; } } diff --git a/SKIRT/core/TetraMeshSpatialGrid.hpp b/SKIRT/core/TetraMeshSpatialGrid.hpp index ffcf7879..bb2ae8a1 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.hpp +++ b/SKIRT/core/TetraMeshSpatialGrid.hpp @@ -30,7 +30,7 @@ class tetgenio; class TetraMeshSpatialGrid : public BoxSpatialGrid { /** The enumeration type indicating the policy for determining the positions of the sites. */ - ENUM_DEF(Policy, Uniform, CentralPeak, DustDensity, ElectronDensity, GasDensity, File, ImportedSites, ImportedMesh) + ENUM_DEF(Policy, Uniform, CentralPeak, DustDensity, ElectronDensity, GasDensity, ImportedSites) ENUM_VAL(Policy, Uniform, "random from uniform distribution") ENUM_VAL(Policy, CentralPeak, "random from distribution with a steep central peak") ENUM_VAL(Policy, DustDensity, "random from dust density distribution") @@ -104,7 +104,7 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid en.wikipedia.org/wiki/Kd-tree). */ void buildSearchPerBlock(); - //======================== Other Functions ======================= + //======================== Interrogation ======================= public: /** This function returns the number of cells in the grid. */ @@ -128,12 +128,16 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid /** This function returns a random location from the cell with index \f$m\f$. */ Position randomPositionInCell(int m) const override; + //====================== Path construction ===================== + /** This function creates and hands over ownership of a path segment generator (an instance of a PathSegmentGenerator subclass) appropriate for this spatial grid type. For the Tetra mesh grid, the path segment generator is actually implemented in the TetraMeshSnapshot class. */ std::unique_ptr createPathSegmentGenerator() const override; + //====================== Output ===================== + /** This function outputs the grid plot files; it is provided here because the regular mechanism does not apply. The function reconstructs the Tetra tesselation in order to produce the coordinates of the Tetra cell vertices. */ @@ -152,7 +156,7 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid int _numVertices; // vertices are added/removed as the grid is built and refined vector _tetrahedra; vector _vertices; - vector _centroids; + vector _centroids; // data members initialized by BuildSearch() int _nb{0}; // number of blocks in each dimension (limit for indices i,j,k) From 606c952c66fc641fcff7c77fe84939237f6f0966 Mon Sep 17 00:00:00 2001 From: arlauwer Date: Wed, 11 Dec 2024 08:24:06 +0100 Subject: [PATCH 35/51] used block search structure --- SKIRT/core/TetraMeshSpatialGrid.cpp | 812 +++++++++++----------------- SKIRT/core/TetraMeshSpatialGrid.hpp | 40 +- 2 files changed, 327 insertions(+), 525 deletions(-) diff --git a/SKIRT/core/TetraMeshSpatialGrid.cpp b/SKIRT/core/TetraMeshSpatialGrid.cpp index 53a3e066..78610147 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.cpp +++ b/SKIRT/core/TetraMeshSpatialGrid.cpp @@ -14,26 +14,16 @@ #include "StringUtils.hpp" #include "tetgen.h" +using std::array; + ////////////////////////////////////////////////////////////////////// namespace { - // function to erase null pointers from a vector of pointers in one go; returns the new size - template size_t eraseNullPointers(vector& v) + // returns the linear index for element (i,j,k) in a p*p*p table + inline int index(int p, int i, int j, int k) { - // scan to the first null (or to the end) - auto from = v.begin(); - while (from != v.end() && *from) ++from; - - // copy the tail, overwriting any nulls - auto to = from; - while (from != v.end()) - { - if (*from) *to++ = *from; - ++from; - } - v.erase(to, v.end()); - return v.size(); + return ((i * p) + j) * p + k; } // sample random positions from the given media components with the given relative weigths @@ -58,37 +48,7 @@ namespace return rv; } - bool lessthan(Vec p1, Vec p2, int axis) - { - switch (axis) - { - case 0: // split on x - if (p1.x() < p2.x()) return true; - if (p1.x() > p2.x()) return false; - if (p1.y() < p2.y()) return true; - if (p1.y() > p2.y()) return false; - if (p1.z() < p2.z()) return true; - return false; - case 1: // split on y - if (p1.y() < p2.y()) return true; - if (p1.y() > p2.y()) return false; - if (p1.z() < p2.z()) return true; - if (p1.z() > p2.z()) return false; - if (p1.x() < p2.x()) return true; - return false; - case 2: // split on z - if (p1.z() < p2.z()) return true; - if (p1.z() > p2.z()) return false; - if (p1.x() < p2.x()) return true; - if (p1.x() > p2.x()) return false; - if (p1.y() < p2.y()) return true; - return false; - default: // this should never happen - return false; - } - } - - std::array clockwiseVertices(int face) + inline std::array clockwiseVertices(int face) { std::array cv = {(face + 3) % 4, (face + 2) % 4, (face + 1) % 4}; // if face is even we should swap two edges @@ -99,14 +59,139 @@ namespace ////////////////////////////////////////////////////////////////////// -struct TetraMeshSpatialGrid::Face +// This is a helper class for organizing cuboidal cells in a smart grid, so that +// it is easy to retrieve the first cell that overlaps a given point in space. +// The Box object on which this class is based specifies a cuboid guaranteed to +// enclose all cells in the grid. +class TetraMeshSpatialGrid::CellGrid { - Face() {} + // data members initialized during construction + const vector& _tetra; // reference to the original list of tetrahedra + int _gridsize; // number of grid cells in each spatial direction + Array _xgrid, _ygrid, _zgrid; // the m+1 grid separation points for each spatial direction + vector> _listv; // the m*m*m lists of indices for cells overlapping each grid cell + int _pmin, _pmax, _ptotal; // minimum, maximum nr of cells in list; total nr of cells in listv + +public: + // The constructor creates a cuboidal grid of the specified number of grid cells in each + // spatial direction, and for each of the grid cells it builds a list of all cells + // overlapping the grid cell. In an attempt to distribute the cells evenly over the + // grid cells, the sizes of the grid cells in each spatial direction are chosen so that + // the cell centers are evenly distributed over the grid cells. + CellGrid(const vector& tetra, Box extent, int gridsize) : _tetra(tetra), _gridsize(gridsize) + { + // build the grids in each spatial direction + makegrid(0, gridsize, _xgrid, extent.xmin(), extent.xmax()); + makegrid(1, gridsize, _ygrid, extent.ymin(), extent.ymax()); + makegrid(2, gridsize, _zgrid, extent.zmin(), extent.zmax()); + + // make room for p*p*p grid cells + _listv.resize(gridsize * gridsize * gridsize); + + // add each cell to the list for every grid cell that it overlaps + int n = _tetra.size(); + for (int m = 0; m != n; ++m) + { + Box boundingBox = _tetra[m].extent(); + + // find indices for first and last grid cell overlapped by cell, in each spatial direction + int i1 = NR::locateClip(_xgrid, boundingBox.xmin()); + int i2 = NR::locateClip(_xgrid, boundingBox.xmax()); + int j1 = NR::locateClip(_ygrid, boundingBox.ymin()); + int j2 = NR::locateClip(_ygrid, boundingBox.ymax()); + int k1 = NR::locateClip(_zgrid, boundingBox.zmin()); + int k2 = NR::locateClip(_zgrid, boundingBox.zmax()); + + // add the cell to all grid cells in that 3D range + for (int i = i1; i <= i2; i++) + for (int j = j1; j <= j2; j++) + for (int k = k1; k <= k2; k++) + { + _listv[index(gridsize, i, j, k)].push_back(m); + } + } + + // calculate statistics + _pmin = n; + _pmax = 0; + _ptotal = 0; + for (int index = 0; index < gridsize * gridsize * gridsize; index++) + { + int size = _listv[index].size(); + _pmin = min(_pmin, size); + _pmax = max(_pmax, size); + _ptotal += size; + } + } + + void makegrid(int axis, int gridsize, Array& grid, double cmin, double cmax) + { + int n = _tetra.size(); + + // determine the cell distribution by binning at a decent resolution + int nbins = gridsize * 100; + double binwidth = (cmax - cmin) / nbins; + vector bins(nbins); + for (const Tetra& tetra : _tetra) + { + double center = tetra.centroid(axis); + bins[static_cast((center - cmin) / binwidth)] += 1; + } + + // determine grid separation points based on the cumulative distribution + grid.resize(gridsize + 1); + grid[0] = -std::numeric_limits::infinity(); + int percell = n / gridsize; // target number of particles per cell + int cumul = 0; // cumulative number of particles in processed bins + int gridindex = 1; // index of the next grid separation point to be filled + for (int binindex = 0; binindex < nbins; binindex++) + { + cumul += bins[binindex]; + if (cumul > percell * gridindex) + { + grid[gridindex] = cmin + (binindex + 1) * binwidth; + gridindex += 1; + if (gridindex >= gridsize) break; + } + } + grid[gridsize] = std::numeric_limits::infinity(); + } + + // This function returns the smallest number of cells overlapping a single grid cell. + int minCellRefsPerCell() const { return _pmin; } - // normals are calculated in the constructor of Tetra - Face(int ntetra, int nface) : _ntetra(ntetra), _nface(nface) {} + // This function returns the largest number of cells overlapping a single grid cell. + int maxCellRefsPerCell() const { return _pmax; } - // setters & getters required? + // This function returns the total number of cell references for all cells in the grid. + int totalCellRefs() const { return _ptotal; } + + // This function returns the index (in the list originally passed to the constructor) + // of the first cell in the list that overlaps the specified position, + // or -1 if none of the cells in the list overlap the specified position. + int cellIndexFor(Position r) const + { + // locate the grid cell containing the specified position + int i = NR::locateClip(_xgrid, r.x()); + int j = NR::locateClip(_ygrid, r.y()); + int k = NR::locateClip(_zgrid, r.z()); + + // search the list of cells for that grid cell + for (int m : _listv[index(_gridsize, i, j, k)]) + { + if (_tetra[m].contains(r)) return m; + } + return -1; + } +}; + +////////////////////////////////////////////////////////////////////// + +struct TetraMeshSpatialGrid::Face +{ + Face() {}; + + Face(int ntetra, int nface, Vec normal) : _ntetra(ntetra), _nface(nface), _normal(normal) {} Vec _normal; // outward facing normal int _ntetra; // index of neighbouring tetrahedron @@ -115,17 +200,18 @@ struct TetraMeshSpatialGrid::Face //////////////////////////////////////////////////////////////////// -class TetraMeshSpatialGrid::Tetra : public Box +class TetraMeshSpatialGrid::Tetra { private: - Vec _centroid; - std::array _vertices; - std::array _faces; + const vector& _vertices; // reference to the full list of vertices + Box _extent; // bounding box of the tetrahedron + Vec _centroid; // barycenter of the tetrahedron + array _vertexIndices; // indices of the vertices in the full list + array _faces; // face information public: - ~Tetra() {} // vertices are deleted in ~TetraMeshSpatialGrid - - Tetra(const std::array& vertices, const std::array& faces) : _vertices(vertices), _faces(faces) + Tetra(const vector& vertices, const array& vertexIndices, const array& faces) + : _vertices(vertices), _vertexIndices(vertexIndices), _faces(faces) { double xmin = DBL_MAX; double ymin = DBL_MAX; @@ -133,99 +219,26 @@ class TetraMeshSpatialGrid::Tetra : public Box double xmax = -DBL_MAX; double ymax = -DBL_MAX; double zmax = -DBL_MAX; - for (const Vec* vertex : _vertices) + for (int vi : _vertexIndices) { - xmin = min(xmin, vertex->x()); - ymin = min(ymin, vertex->y()); - zmin = min(zmin, vertex->z()); - xmax = max(xmax, vertex->x()); - ymax = max(ymax, vertex->y()); - zmax = max(zmax, vertex->z()); - } - setExtent(Box(xmin, ymin, zmin, xmax, ymax, zmax)); - - // average position of all vertices - for (int i = 0; i < 4; i++) _centroid += *_vertices[i]; - _centroid /= 4; + const Vec& vertex = vertices[vi]; - // calculate normal facing out - for (int f = 0; f < 4; f++) - { - std::array cv = clockwiseVertices(f); - Vec e12 = edge(cv[0], cv[1]); - Vec e13 = edge(cv[0], cv[2]); - Vec normal = Vec::cross(e12, e13); - normal /= normal.norm(); + xmin = min(xmin, vertex.x()); + ymin = min(ymin, vertex.y()); + zmin = min(zmin, vertex.z()); + xmax = max(xmax, vertex.x()); + ymax = max(ymax, vertex.y()); + zmax = max(zmax, vertex.z()); - Face& face = _faces[f]; - face._normal = normal; + _centroid += vertex; } - } + // set bounding box + _extent = Box(xmin, ymin, zmin, xmax, ymax, zmax); - std::pair traverse(Vec pos, Direction dir, int& enteringFace) const - { - int leavingFace = -1; - double ds = DBL_MAX; - - // find entering face using a single Plücker product - if (enteringFace == -1) enteringFace = findEnteringFace(pos, dir); - - // the translated Plücker moment in the local coordinate system - Vec moment = Vec::cross(dir, pos - *_vertices[enteringFace]); - - // clockwise vertices around vertex 0 - std::array cv = clockwiseVertices(enteringFace); - - // determine orientations for use in the decision tree - double prod0 = Vec::dot(moment, edge(cv[0], enteringFace)); - int clock0 = prod0 < 0; - // if clockwise move clockwise else move cclockwise - int i = clock0 ? 1 : 2; - double prodi = Vec::dot(moment, edge(cv[i], enteringFace)); - int cclocki = prodi >= 0; - - // use plane intersection algorithm if Plücker products are ambiguous - // this is actually more strict than the algorithm described by Maria (2017) - // but these edge cases are incredibly rare and can cause issues - if (prod0 == 0. || prodi == 0.) - { - for (int face : cv) - { - const Vec& n = _faces[face]._normal; - double ndotk = Vec::dot(n, dir); - if (ndotk > 0) - { - const Vec& v = *_vertices[enteringFace]; - double dq = Vec::dot(n, v - pos) / ndotk; - if (dq < ds) - { - ds = dq; - leavingFace = face; - } - } - } - } - // use Maria (2017) algorithm otherwise - else - { - // decision table for clock0 and cclocki - // 1 1 -> 2 - // 0 0 -> 1 - // 1 0 -> 0 - // 0 1 -> 0 - static constexpr int dtable[2][2] = {{1, 0}, {0, 2}}; - leavingFace = cv[dtable[clock0][cclocki]]; - const Vec& n = _faces[leavingFace]._normal; - const Vec& v = *_vertices[enteringFace]; - double ndotk = Vec::dot(n, dir); - ds = Vec::dot(n, v - pos) / ndotk; - } - - return std::make_pair(ds, leavingFace); + // average position of all vertices + _centroid /= 4; } - //////////////////////////////////////////////////////////////////// - int findEnteringFace(const Vec& pos, const Direction& dir) const { int enteringFace = -1; @@ -238,8 +251,7 @@ class TetraMeshSpatialGrid::Tetra : public Box { for (int v2 = v1 + 1; v2 < 4; v2++) { - - Vec moment12 = Vec::cross(dir, pos - *_vertices[v1]); + Vec moment12 = Vec::cross(dir, pos - vertex(v1)); double prod12 = Vec::dot(moment12, edge(v1, v2)); if (prod12 != 0.) { @@ -253,29 +265,25 @@ class TetraMeshSpatialGrid::Tetra : public Box return enteringFace; } - //////////////////////////////////////////////////////////////////// - - bool inside(const Position& bfr) const + bool contains(const Position& bfr) const { - // since we use the k-d tree this will only slow the CellIndex - // if (!Box::contains(bfr)) return false; + if (!_extent.contains(bfr)) return false; - // could optimize this slightly by using same vertex for 3 faces and do final face seperately - // but this function acts only as a backup + // could optimize this slightly by using same vertex for 3 faces and do final face seperately, but this is more readable for (int f = 0; f < 4; f++) { const Face& face = _faces[f]; - const Vec* vertex = _vertices[(f + 1) % 4]; // any vertex that is on the face + Vec v = vertex((f + 1) % 4); // any vertex that is on the face // if point->face is opposite direction as the outward pointing normal, the point is outside - if (Vec::dot(*vertex - bfr, face._normal) < 0) return false; + if (Vec::dot(v - bfr, face._normal) < 0) return false; } return true; } + // https://vcg.isti.cnr.it/activities/OLD/geometryegraphics/pointintetraedro.html double generateBarycentric(double& s, double& t, double& u) const { - // https://vcg.isti.cnr.it/activities/OLD/geometryegraphics/pointintetraedro.html if (s + t > 1.0) { // cut'n fold the cube into a prism s = 1.0 - s; @@ -296,8 +304,6 @@ class TetraMeshSpatialGrid::Tetra : public Box return 1 - u - t - s; } - //////////////////////////////////////////////////////////////////// - Position generatePosition(Random* random) const { double s = random->uniform(); @@ -306,14 +312,14 @@ class TetraMeshSpatialGrid::Tetra : public Box double r = generateBarycentric(s, t, u); - return Position(r * *_vertices[0] + u * *_vertices[1] + t * *_vertices[2] + s * *_vertices[3]); + return Position(r * vertex(0) + u * vertex(1) + t * vertex(2) + s * vertex(3)); } - //////////////////////////////////////////////////////////////////// - - Vec edge(int t1, int t2) const { return *_vertices[t2] - *_vertices[t1]; } + // get the vertex using local indices [0, 3] + Vec vertex(int t) const { return _vertices[_vertexIndices[t]]; } - //////////////////////////////////////////////////////////////////// + // get the edge t1->t2 using local indices [0, 3] + Vec edge(int t1, int t2) const { return vertex(t2) - vertex(t1); } double volume() const { return 1 / 6. * abs(Vec::dot(Vec::cross(edge(0, 1), edge(0, 2)), edge(0, 3))); } @@ -330,144 +336,29 @@ class TetraMeshSpatialGrid::Tetra : public Box return sqrt(sum / 6.0); } - //////////////////////////////////////////////////////////////////// - - const Face& face(int f) const { return _faces[f]; } - - //////////////////////////////////////////////////////////////////// + const array& faces() const { return _faces; } const Vec& centroid() const { return _centroid; } - //////////////////////////////////////////////////////////////////// - - const Vec& vertex(int v) const { return *_vertices[v]; } - - //////////////////////////////////////////////////////////////////// -}; - -////////////////////////////////////////////////////////////////////// - -class TetraMeshSpatialGrid::Node -{ -private: - int _m; // index in _cells to the site defining the split at this node - int _axis; // split axis for this node (0,1,2) - Node* _up; // ptr to the parent node - Node* _left; // ptr to the left child node - Node* _right; // ptr to the right child node - - // returns the square of its argument - static double sqr(double x) { return x * x; } - -public: - // constructor stores the specified site index and child pointers (which may be null) - Node(int m, int depth, Node* left, Node* right) : _m(m), _axis(depth % 3), _up(0), _left(left), _right(right) - { - if (_left) _left->setParent(this); - if (_right) _right->setParent(this); - } - - // destructor destroys the children - ~Node() + const double centroid(int axis) const { - delete _left; - delete _right; - } - - // sets parent pointer (called from parent's constructor) - void setParent(Node* up) { _up = up; } - - // returns the corresponding data member - int m() const { return _m; } - Node* up() const { return _up; } - Node* left() const { return _left; } - Node* right() const { return _right; } - - // returns the apropriate child for the specified query point - Node* child(Vec bfr, const vector& points) const - { - return lessthan(bfr, *points[_m], _axis) ? _left : _right; - } - - // returns the other child than the one that would be apropriate for the specified query point - Node* otherChild(Vec bfr, const vector& points) const - { - return lessthan(bfr, *points[_m], _axis) ? _right : _left; - } - - // returns the squared distance from the query point to the split plane - double squaredDistanceToSplitPlane(Vec bfr, const vector& points) const - { - switch (_axis) + switch (axis % 3) { - case 0: // split on x - return sqr(points[_m]->x() - bfr.x()); - case 1: // split on y - return sqr(points[_m]->y() - bfr.y()); - case 2: // split on z - return sqr(points[_m]->z() - bfr.z()); - default: // this should never happen - return 0; + case 0: return _centroid.x(); + case 1: return _centroid.y(); + case 2: return _centroid.z(); + default: return 0.0; } } - // returns the node in this subtree that represents the site nearest to the query point - Node* nearest(Vec bfr, const vector& points) - { - // recursively descend the tree until a leaf node is reached, going left or right depending on - // whether the specified point is less than or greater than the current node in the split dimension - Node* current = this; - while (Node* child = current->child(bfr, points)) current = child; - - // unwind the recursion, looking for the nearest node while climbing up - Node* best = current; - double bestSD = (*points[best->m()] - bfr).norm2(); - while (true) - { - // if the current node is closer than the current best, then it becomes the current best - double currentSD = (*points[current->m()] - bfr).norm2(); - if (currentSD < bestSD) - { - best = current; - bestSD = currentSD; - } - - // if there could be points on the other side of the splitting plane for the current node - // that are closer to the search point than the current best, then ... - double splitSD = current->squaredDistanceToSplitPlane(bfr, points); - if (splitSD < bestSD) - { - // move down the other branch of the tree from the current node looking for closer points, - // following the same recursive process as the entire search - Node* other = current->otherChild(bfr, points); - if (other) - { - Node* otherBest = other->nearest(bfr, points); - double otherBestSD = (*points[otherBest->m()] - bfr).norm2(); - if (otherBestSD < bestSD) - { - best = otherBest; - bestSD = otherBestSD; - } - } - } - - // move up to the parent until we meet the top node - if (current == this) break; - current = current->up(); - } - return best; - } + const Box& extent() const { return _extent; } }; //////////////////////////////////////////////////////////////////// TetraMeshSpatialGrid::~TetraMeshSpatialGrid() { - for (auto vertex : _vertices) delete vertex; - for (auto centroid : _centroids) delete centroid; - for (auto tetra : _tetrahedra) delete tetra; // vertices are already deleted - for (auto tree : _blocktrees) delete tree; + delete _grid; } ////////////////////////////////////////////////////////////////////// @@ -486,7 +377,7 @@ void TetraMeshSpatialGrid::setupSelfBefore() { auto random = find(); _vertices.resize(_numSites); - for (int m = 0; m != _numSites; ++m) _vertices[m] = new Vec((random->position(extent()))); + for (int m = 0; m != _numSites; ++m) _vertices[m] = random->position(extent()); break; } case Policy::CentralPeak: @@ -495,13 +386,13 @@ void TetraMeshSpatialGrid::setupSelfBefore() const int a = 1000; // steepness of the peak; the central 1/a portion is NOT covered const double rscale = extent().rmax().norm(); _vertices.resize(_numSites); - _vertices[0] = new Vec(0, 0, 0); + _vertices[0] = Vec(0, 0, 0); for (int m = 1; m != _numSites;) // skip first particle so that it remains (0,0,0) { double r = rscale * pow(1. / a, random->uniform()); // random distribution according to 1/x Direction k = random->direction(); - Vec p = Position(r, k); - if (extent().contains(p)) _vertices[m++] = new Vec(p); // discard any points outside of the domain + Position p(r, k); + if (extent().contains(p)) _vertices[m++] = p; // discard any points outside of the domain } break; } @@ -517,7 +408,7 @@ void TetraMeshSpatialGrid::setupSelfBefore() vector sites = sampleMedia(media, weights, extent(), _numSites); int n = sites.size(); _vertices.resize(n); - for (int m = 0; m != n; ++m) _vertices[m] = new Vec(sites[m]); + for (int m = 0; m != n; ++m) _vertices[m] = sites[m]; break; } case Policy::ElectronDensity: @@ -532,7 +423,7 @@ void TetraMeshSpatialGrid::setupSelfBefore() vector sites = sampleMedia(media, weights, extent(), _numSites); int n = sites.size(); _vertices.resize(n); - for (int m = 0; m != n; ++m) _vertices[m] = new Vec(sites[m]); + for (int m = 0; m != n; ++m) _vertices[m] = sites[m]; break; } case Policy::GasDensity: @@ -547,7 +438,7 @@ void TetraMeshSpatialGrid::setupSelfBefore() vector sites = sampleMedia(media, weights, extent(), _numSites); int n = sites.size(); _vertices.resize(n); - for (int m = 0; m != n; ++m) _vertices[m] = new Vec(sites[m]); + for (int m = 0; m != n; ++m) _vertices[m] = sites[m]; break; } case Policy::ImportedSites: @@ -556,7 +447,7 @@ void TetraMeshSpatialGrid::setupSelfBefore() // prepare the data int n = sli->numSites(); _vertices.resize(n); - for (int m = 0; m != n; ++m) _vertices[m] = new Vec(sli->sitePosition(m)); + for (int m = 0; m != n; ++m) _vertices[m] = sli->sitePosition(m); break; } } @@ -566,8 +457,10 @@ void TetraMeshSpatialGrid::setupSelfBefore() throw FATALERROR("No vertices available for mesh generation"); } + _numVertices = _vertices.size(); + buildMesh(); - buildSearchPerBlock(); + buildSearch(); } //////////////////////////////////////////////////////////////////// @@ -587,22 +480,19 @@ void TetraMeshSpatialGrid::buildMesh() } } +//////////////////////////////////////////////////////////////////// + void TetraMeshSpatialGrid::buildDelaunay(tetgenio& out) { - // remove sites outside of the domain - int numOutside = 0; + // remove vertices outside of the domain + auto sitesEnd = std::remove_if(_vertices.begin(), _vertices.end(), [this](const Vec& vertex) { + if (!contains(vertex)) return true; // remove vertex + return false; + }); + _vertices.erase(sitesEnd, _vertices.end()); + int numOutside = _numVertices - _vertices.size(); _numVertices = _vertices.size(); - for (int m = 0; m != _numVertices; ++m) - { - if (!contains(*_vertices[m])) - { - delete _vertices[m]; - _vertices[m] = 0; - numOutside++; - } - } - if (numOutside) _numVertices = eraseNullPointers(_vertices); - _log->info("removed " + StringUtils::toString(numOutside, 'd') + " vertices outside of the domain"); + if (numOutside) _log->info("removed " + StringUtils::toString(numOutside, 'd') + " vertices outside of the domain"); tetgenio in; tetgenbehavior behavior; @@ -612,9 +502,9 @@ void TetraMeshSpatialGrid::buildDelaunay(tetgenio& out) in.pointlist = new REAL[in.numberofpoints * 3]; for (int i = 0; i < in.numberofpoints; i++) { - in.pointlist[i * 3 + 0] = _vertices[i]->x(); - in.pointlist[i * 3 + 1] = _vertices[i]->y(); - in.pointlist[i * 3 + 2] = _vertices[i]->z(); + in.pointlist[i * 3 + 0] = _vertices[i].x(); + in.pointlist[i * 3 + 1] = _vertices[i].y(); + in.pointlist[i * 3 + 2] = _vertices[i].z(); } behavior.psc = 1; // -s build Delaunay tetrahedralisation @@ -624,14 +514,15 @@ void TetraMeshSpatialGrid::buildDelaunay(tetgenio& out) _log->info("Built Delaunay triangulation"); } +//////////////////////////////////////////////////////////////////// + void TetraMeshSpatialGrid::refineDelaunay(tetgenio& in, tetgenio& out) { tetgenbehavior behavior; // tetgen refine options behavior.refine = 1; // -r - behavior.quality = 1; // -q - // use default tetgen options for quality + behavior.quality = 1; // -q with default tetgen options for quality // correct output options for out behavior.neighout = 2; // -nn behavior.facesout = 1; // -f @@ -642,16 +533,16 @@ void TetraMeshSpatialGrid::refineDelaunay(tetgenio& in, tetgenio& out) _log->info("Refined triangulation"); } +//////////////////////////////////////////////////////////////////// + void TetraMeshSpatialGrid::storeTetrahedra(const tetgenio& out, bool storeVertices) { // tranfser TetGen data to TetraMeshSpatialGrid data containers - _numTetra = out.numberoftetrahedra; + _numCells = out.numberoftetrahedra; + // replace old vertices if (storeVertices) { - // delete old vertices - for (int i = 0; i < _numVertices; i++) delete _vertices[i]; - _numVertices = out.numberofpoints; _vertices.resize(_numVertices); @@ -661,56 +552,63 @@ void TetraMeshSpatialGrid::storeTetrahedra(const tetgenio& out, bool storeVertic double y = out.pointlist[3 * i + 1]; double z = out.pointlist[3 * i + 2]; - _vertices[i] = new Vec(x, y, z); + _vertices[i] = Vec(x, y, z); } } - _tetrahedra.resize(_numTetra); - for (int i = 0; i < _numTetra; i++) + _tetrahedra.reserve(_numCells); // can't make empty Tetra so don't use resize + for (int i = 0; i < _numCells; i++) { - std::array vertices; + std::array vertexIndices; std::array faces; // vertices for (int c = 0; c < 4; c++) { - vertices[c] = _vertices[out.tetrahedronlist[4 * i + c]]; + vertexIndices[c] = out.tetrahedronlist[4 * i + c]; } // faces - for (int c = 0; c < 4; c++) + for (int f = 0; f < 4; f++) { // -1 if no neighbor - int ntetra = out.neighborlist[4 * i + c]; + int ntetra = out.neighborlist[4 * i + f]; // find which face is shared with neighbor int nface = -1; if (ntetra != -1) { - for (int cn = 0; cn < 4; cn++) + for (int fn = 0; fn < 4; fn++) { - if (out.neighborlist[4 * ntetra + cn] == i) + if (out.neighborlist[4 * ntetra + fn] == i) { - nface = cn; + nface = fn; break; } } } - faces[c] = Face(ntetra, nface); - } - _tetrahedra[i] = new Tetra(vertices, faces); + // compute outward facing normal of face + std::array cv = clockwiseVertices(f); + Vec v0 = _vertices[vertexIndices[cv[0]]]; + Vec e12 = _vertices[vertexIndices[cv[1]]] - v0; + Vec e13 = _vertices[vertexIndices[cv[2]]] - v0; + Vec normal = Vec::cross(e12, e13); + normal /= normal.norm(); + + faces[f] = Face(ntetra, nface, normal); + } - _centroids.push_back(&_tetrahedra[i]->centroid()); + _tetrahedra.emplace_back(_vertices, vertexIndices, faces); } // compile statistics double minVol = DBL_MAX; double maxVol = 0.; double totalVol2 = 0.; - for (int m = 0; m < _numTetra; m++) + for (int m = 0; m < _numCells; m++) { - double vol = _tetrahedra[m]->volume(); + double vol = _tetrahedra[m].volume(); totalVol2 += vol * vol; minVol = min(minVol, vol); maxVol = max(maxVol, vol); @@ -718,201 +616,72 @@ void TetraMeshSpatialGrid::storeTetrahedra(const tetgenio& out, bool storeVertic double V = Box::volume(); minVol /= V; maxVol /= V; - double avgVol = 1 / (double)_numTetra; - double varVol = (totalVol2 / _numTetra / (V * V) - avgVol * avgVol); + double avgVol = 1 / (double)_numCells; + double varVol = (totalVol2 / _numCells / (V * V) - avgVol * avgVol); // log neighbor statistics _log->info("Done computing tetrahedralisation"); _log->info(" Number of vertices " + std::to_string(_numVertices)); - _log->info(" Number of tetrahedra " + std::to_string(_numTetra)); + _log->info(" Number of tetrahedra " + std::to_string(_numCells)); _log->info(" Average volume fraction per cell: " + StringUtils::toString(avgVol, 'e')); _log->info(" Variance of volume fraction per cell: " + StringUtils::toString(varVol, 'e')); _log->info(" Minimum volume fraction cell: " + StringUtils::toString(minVol, 'e')); _log->info(" Maximum volume fraction cell: " + StringUtils::toString(maxVol, 'e')); } -////////////////////////////////////////////////////////////////////// - -TetraMeshSpatialGrid::Node* TetraMeshSpatialGrid::buildTree(vector::iterator first, vector::iterator last, - int depth) const -{ - auto length = last - first; - if (length > 0) - { - auto median = length >> 1; - std::nth_element(first, first + median, last, [this, depth](int m1, int m2) { - return m1 != m2 && lessthan(*_centroids[m1], *_centroids[m2], depth % 3); - }); - return new TetraMeshSpatialGrid::Node(*(first + median), depth, buildTree(first, first + median, depth + 1), - buildTree(first + median + 1, last, depth + 1)); - } - return nullptr; -} - //////////////////////////////////////////////////////////////////// -void TetraMeshSpatialGrid::buildSearchPerBlock() +void TetraMeshSpatialGrid::buildSearch() { - // abort if there are no cells - if (!_numTetra) return; - - _log->info("Building data structures to accelerate searching the tetrahedralisation"); - - // ------------- block lists ------------- - _nb = max(3, min(250, static_cast(cbrt(_numTetra)))); - _nb2 = _nb * _nb; - _nb3 = _nb * _nb * _nb; - - // initialize a vector of nb * nb * nb lists - _blocklists.resize(_nb3); - - // we add the tetrahedra to all blocks they potentially overlap with - // this will slow down the search tree but if no search tree is present - // we can simply loop over all tetrahedra inside the block - int i1, j1, k1, i2, j2, k2; - for (int c = 0; c != _numTetra; ++c) - { - cellIndices(i1, j1, k1, _tetrahedra[c]->rmin() - Vec(_eps, _eps, _eps), _nb, _nb, _nb); - cellIndices(i2, j2, k2, _tetrahedra[c]->rmax() + Vec(_eps, _eps, _eps), _nb, _nb, _nb); - for (int i = i1; i <= i2; i++) - for (int j = j1; j <= j2; j++) - for (int k = k1; k <= k2; k++) _blocklists[i * _nb2 + j * _nb + k].push_back(c); - } - - // compile block list statistics - int minRefsPerBlock = INT_MAX; - int maxRefsPerBlock = 0; - int64_t totalBlockRefs = 0; - for (int b = 0; b < _nb3; b++) - { - int refs = _blocklists[b].size(); - totalBlockRefs += refs; - minRefsPerBlock = min(minRefsPerBlock, refs); - maxRefsPerBlock = max(maxRefsPerBlock, refs); - } - double avgRefsPerBlock = double(totalBlockRefs) / _nb3; - - // log block list statistics - _log->info(" Number of blocks in search grid: " + std::to_string(_nb3) + " (" + std::to_string(_nb) + "^3)"); - _log->info(" Average number of cells per block: " + StringUtils::toString(avgRefsPerBlock, 'f', 1)); - _log->info(" Minimum number of cells per block: " + std::to_string(minRefsPerBlock)); - _log->info(" Maximum number of cells per block: " + std::to_string(maxRefsPerBlock)); - - // ------------- search trees ------------- - - // for each block that contains more than a predefined number of cells, - // construct a search tree on the site locations of the cells - _blocktrees.resize(_nb3); - for (int b = 0; b < _nb3; b++) - { - vector& ids = _blocklists[b]; - if (ids.size() > 9) _blocktrees[b] = buildTree(ids.begin(), ids.end(), 0); - } - - // compile and log search tree statistics - int numTrees = 0; - for (int b = 0; b < _nb3; b++) - if (_blocktrees[b]) numTrees++; - _log->info(" Number of search trees: " + std::to_string(numTrees) + " (" - + StringUtils::toString(100. * numTrees / _nb3, 'f', 1) + "% of blocks)"); + int gridsize = max(20, static_cast(pow(_tetrahedra.size(), 1. / 3.) / 5)); + string size = std::to_string(gridsize); + _log->info("Constructing intermediate " + size + "x" + size + "x" + size + " grid for cells..."); + _grid = new CellGrid(_tetrahedra, extent(), gridsize); + _log->info(" Smallest number of cells per grid cell: " + std::to_string(_grid->minCellRefsPerCell())); + _log->info(" Largest number of cells per grid cell: " + std::to_string(_grid->maxCellRefsPerCell())); + _log->info(" Average number of cells per grid cell: " + + StringUtils::toString(_grid->totalCellRefs() / double(gridsize * gridsize * gridsize), 'f', 1)); } ////////////////////////////////////////////////////////////////////// int TetraMeshSpatialGrid::numCells() const { - return _numTetra; + return _numCells; } ////////////////////////////////////////////////////////////////////// double TetraMeshSpatialGrid::volume(int m) const { - return _tetrahedra[m]->volume(); + return _tetrahedra[m].volume(); } ////////////////////////////////////////////////////////////////////// double TetraMeshSpatialGrid::diagonal(int m) const { - // can use for loop but this is more readable - const Tetra* tetra = _tetrahedra[m]; - double a = tetra->edge(0, 1).norm2(); - double b = tetra->edge(0, 2).norm2(); - double c = tetra->edge(0, 3).norm2(); - double d = tetra->edge(1, 2).norm2(); - double e = tetra->edge(1, 3).norm2(); - double f = tetra->edge(2, 3).norm2(); - return sqrt(a + b + c + d + e + f) / sqrt(6.0); + return _tetrahedra[m].diagonal(); } ////////////////////////////////////////////////////////////////////// int TetraMeshSpatialGrid::cellIndex(Position bfr) const { - // Ensure the position is inside the domain - if (!contains(bfr)) return -1; - - // Determine the block in which the point falls - int i, j, k; - cellIndices(i, j, k, bfr, _nb, _nb, _nb); - int b = i * _nb2 + j * _nb + k; - - // Look for the closest centroid in this block using the search tree - Node* tree = _blocktrees[b]; - if (!tree) throw FATALERROR("No search tree found for block " + std::to_string(b)); - int m = tree->nearest(bfr, _centroids)->m(); - const Tetra* tetra = _tetrahedra[m]; - - // traverse from centroid towards bfr until we pass it - Vec pos = tetra->centroid(); - Direction dir(bfr - pos); - double dist = dir.norm(); - dir /= dist; - - double ds; - int leavingFace; - int enteringFace = -1; - while (true) - { - std::tie(ds, leavingFace) = tetra->traverse(pos, dir, enteringFace); - - // If no exit point was found, break - if (leavingFace == -1) break; - - // Move to the exit point - pos += ds * dir; - dist -= ds; - if (dist <= 0) return m; - - // Get neighbour information - m = tetra->face(leavingFace)._ntetra; - enteringFace = tetra->face(leavingFace)._nface; - - // If no next tetrahedron, break - if (m < 0) break; - - tetra = _tetrahedra[m]; - } - - // If traversal algorithm failed to find correct cell, loop over all tetrahedra in the block - for (int t : _blocklists[b]) - if (_tetrahedra[t]->inside(bfr)) return t; - - throw FATALERROR("Can't find random position in cell"); + return _grid->cellIndexFor(bfr); } ////////////////////////////////////////////////////////////////////// Position TetraMeshSpatialGrid::centralPositionInCell(int m) const { - return Position(_tetrahedra[m]->centroid()); + return Position(_tetrahedra[m].centroid()); } ////////////////////////////////////////////////////////////////////// Position TetraMeshSpatialGrid::randomPositionInCell(int m) const { - return _tetrahedra[m]->generatePosition(random()); + return _tetrahedra[m].generatePosition(random()); } ////////////////////////////////////////////////////////////////////// @@ -926,12 +695,12 @@ void TetraMeshSpatialGrid::writeGridPlotFiles(const SimulationItem* probe) const SpatialGridPlotFile plotxyz(probe, probe->itemName() + "_grid_xyz"); // for each site, compute the corresponding cell and output its edges - _log->info("Writing plot files for tetrahedralisation with " + std::to_string(_numTetra) + " tetrahedra"); - _log->infoSetElapsed(_numTetra); + _log->info("Writing plot files for tetrahedralisation with " + std::to_string(_numCells) + " tetrahedra"); + _log->infoSetElapsed(_numCells); int numDone = 0; - for (int i = 0; i < _numTetra; i++) + for (int i = 0; i < _numCells; i++) { - const Tetra* tetra = _tetrahedra[i]; + const Tetra& tetra = _tetrahedra[i]; vector coords; coords.reserve(12); vector indices; @@ -939,7 +708,7 @@ void TetraMeshSpatialGrid::writeGridPlotFiles(const SimulationItem* probe) const for (int v = 0; v < 4; v++) { - const Vec vertex = tetra->vertex(v); + const Vec vertex = tetra.vertex(v); coords.push_back(vertex.x()); coords.push_back(vertex.y()); coords.push_back(vertex.z()); @@ -952,9 +721,10 @@ void TetraMeshSpatialGrid::writeGridPlotFiles(const SimulationItem* probe) const indices.push_back(faceIndices[2]); } - if (tetra->zmin() <= 0 && tetra->zmax() >= 0) plotxy.writePolyhedron(coords, indices); - if (tetra->ymin() <= 0 && tetra->ymax() >= 0) plotxz.writePolyhedron(coords, indices); - if (tetra->xmin() <= 0 && tetra->xmax() >= 0) plotyz.writePolyhedron(coords, indices); + const Box& extent = tetra.extent(); + if (extent.zmin() <= 0 && extent.zmax() >= 0) plotxy.writePolyhedron(coords, indices); + if (extent.ymin() <= 0 && extent.ymax() >= 0) plotxz.writePolyhedron(coords, indices); + if (extent.xmin() <= 0 && extent.xmax() >= 0) plotyz.writePolyhedron(coords, indices); if (i <= 1000) plotxyz.writePolyhedron(coords, indices); // like TetraMeshSpatialGrid, but why even write at all? @@ -1000,11 +770,67 @@ class TetraMeshSpatialGrid::MySegmentGenerator : public PathSegmentGenerator // loop in case no exit point was found (which should happen only rarely) while (true) { - const Tetra* tetra = _grid->_tetrahedra[_mr]; + const Tetra tetra = _grid->_tetrahedra[_mr]; + const array& faces = tetra.faces(); Position pos = r(); Direction dir = k(); - std::tie(ds, leavingFace) = tetra->traverse(pos, dir, _enteringFace); + int leavingFace = -1; + double ds = DBL_MAX; + + // find entering face using a single Plücker product + if (_enteringFace == -1) _enteringFace = tetra.findEnteringFace(pos, dir); + + // the translated Plücker moment in the local coordinate system + Vec moment = Vec::cross(dir, pos - tetra.vertex(_enteringFace)); + + // clockwise vertices around vertex 0 + std::array cv = clockwiseVertices(_enteringFace); + + // determine orientations for use in the decision tree + double prod0 = Vec::dot(moment, tetra.edge(cv[0], _enteringFace)); + int clock0 = prod0 < 0; + // if clockwise move clockwise else move cclockwise + int i = clock0 ? 1 : 2; + double prodi = Vec::dot(moment, tetra.edge(cv[i], _enteringFace)); + int cclocki = prodi >= 0; + + // use plane intersection algorithm if Plücker products are ambiguous + // this is actually more strict than the algorithm described by Maria (2017) + // but these edge cases are incredibly rare and can cause issues + if (prod0 == 0. || prodi == 0.) + { + for (int face : cv) + { + const Vec& n = faces[face]._normal; + double ndotk = Vec::dot(n, dir); + if (ndotk > 0) + { + const Vec& v = tetra.vertex(_enteringFace); + double dq = Vec::dot(n, v - pos) / ndotk; + if (dq < ds) + { + ds = dq; + leavingFace = face; + } + } + } + } + // use Maria (2017) algorithm otherwise + else + { + // decision table for clock0 and cclocki + // 1 1 -> 2 + // 0 0 -> 1 + // 1 0 -> 0 + // 0 1 -> 0 + static constexpr int dtable[2][2] = {{1, 0}, {0, 2}}; + leavingFace = cv[dtable[clock0][cclocki]]; + const Vec& n = faces[leavingFace]._normal; + const Vec& v = tetra.vertex(_enteringFace); + double ndotk = Vec::dot(n, dir); + ds = Vec::dot(n, v - pos) / ndotk; + } // if no exit point was found, advance the current point by a small distance, // recalculate the cell index, and return to the start of the loop @@ -1028,7 +854,7 @@ class TetraMeshSpatialGrid::MySegmentGenerator : public PathSegmentGenerator { propagater(ds); setSegment(_mr, ds); - _mr = tetra->face(leavingFace)._ntetra; + _mr = faces[leavingFace]._ntetra; if (_mr < 0) { @@ -1037,7 +863,7 @@ class TetraMeshSpatialGrid::MySegmentGenerator : public PathSegmentGenerator } else { - _enteringFace = tetra->face(leavingFace)._nface; + _enteringFace = faces[leavingFace]._nface; return true; } } diff --git a/SKIRT/core/TetraMeshSpatialGrid.hpp b/SKIRT/core/TetraMeshSpatialGrid.hpp index bb2ae8a1..49f12c59 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.hpp +++ b/SKIRT/core/TetraMeshSpatialGrid.hpp @@ -83,29 +83,9 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid void storeTetrahedra(const tetgenio& out, bool storeVertices); - /** Private function to recursively build a binary search tree (see - en.wikipedia.org/wiki/Kd-tree) */ - Node* buildTree(vector::iterator first, vector::iterator last, int depth) const; - - /** This private function builds data structures that allow accelerating the operation of the - cellIndex() function. It assumes that the Voronoi mesh has already been built. - - The domain is partitioned using a linear cubodial grid into cells that are called \em - blocks. For each block, the function builds and stores a list of all Voronoi cells that - possibly overlap that block. Retrieving the list of cells possibly overlapping a given - point in the domain then comes down to locating the block containing the point (which is - trivial since the grid is linear). The current implementation uses a Voronoi cell's - enclosing cuboid to test for intersection with a block. Performing a precise intersection - test is \em really slow and the shortened block lists don't substantially accelerate the - cellIndex() function. - - To further reduce the search time within blocks that overlap with a large number of cells, - the function builds a binary search tree on the cell sites for those blocks (see for example - en.wikipedia.org/wiki/Kd-tree). */ - void buildSearchPerBlock(); + void buildSearch(); //======================== Interrogation ======================= - public: /** This function returns the number of cells in the grid. */ int numCells() const override; @@ -152,18 +132,14 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid // data members initialized during configuration double _eps{0.}; // small fraction of extent - int _numTetra; + int _numCells; // number of Tetra cells and centroids int _numVertices; // vertices are added/removed as the grid is built and refined - vector _tetrahedra; - vector _vertices; - vector _centroids; - - // data members initialized by BuildSearch() - int _nb{0}; // number of blocks in each dimension (limit for indices i,j,k) - int _nb2{0}; // nb*nb - int _nb3{0}; // nb*nb*nb - vector> _blocklists; // list of cell indices per block, indexed on i*_nb2+j*_nb+k - vector _blocktrees; // root node of search tree or null for each block, indexed on i*_nb2+j*_nb+k + vector _tetrahedra; + vector _vertices; + + // data members initialized after reading the input file if a density policy has been set + class CellGrid; + CellGrid* _grid{nullptr}; // smart grid for locating the cell at a given location // allow our path segment generator to access our private data members class MySegmentGenerator; From 6fde64d541e5ea652c9807a8d223afc79c4125db Mon Sep 17 00:00:00 2001 From: arlauwer Date: Wed, 11 Dec 2024 10:54:09 +0100 Subject: [PATCH 36/51] temp: moved Tetra and Face to hpp to make it compile reformatted code --- SKIRT/core/TetraMeshSpatialGrid.cpp | 434 +- SKIRT/core/TetraMeshSpatialGrid.hpp | 59 +- SKIRT/core/VoronoiMeshSnapshot.cpp | 2 +- SKIRT/core/VoronoiMeshSnapshot.hpp | 2 +- SKIRT/tetgen/CMakeLists.txt | 4 +- SKIRT/tetgen/tetgen.cxx | 1 + SKIRT/tetgen/tetgen.h | 5672 +++++++++++++------------- SKIRT/utils/PathSegmentGenerator.hpp | 4 - 8 files changed, 3030 insertions(+), 3148 deletions(-) diff --git a/SKIRT/core/TetraMeshSpatialGrid.cpp b/SKIRT/core/TetraMeshSpatialGrid.cpp index 78610147..46db85ad 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.cpp +++ b/SKIRT/core/TetraMeshSpatialGrid.cpp @@ -57,44 +57,200 @@ namespace } } +//////////////////////////////////////////////////////////////////// + +TetraMeshSpatialGrid::Tetra::Tetra(const vector& vertices, const array& vertexIndices, + const array& faces) + : _vertices(vertices), _vertexIndices(vertexIndices), _faces(faces) +{ + double xmin = DBL_MAX; + double ymin = DBL_MAX; + double zmin = DBL_MAX; + double xmax = -DBL_MAX; + double ymax = -DBL_MAX; + double zmax = -DBL_MAX; + for (int vi : _vertexIndices) + { + const Vec& vertex = vertices[vi]; + + xmin = min(xmin, vertex.x()); + ymin = min(ymin, vertex.y()); + zmin = min(zmin, vertex.z()); + xmax = max(xmax, vertex.x()); + ymax = max(ymax, vertex.y()); + zmax = max(zmax, vertex.z()); + + _centroid += vertex; + } + // set bounding box + _extent = Box(xmin, ymin, zmin, xmax, ymax, zmax); + + // average position of all vertices + _centroid /= 4; +} + +int TetraMeshSpatialGrid::Tetra::findEnteringFace(const Vec& pos, const Direction& dir) const +{ + int enteringFace = -1; + // clockwise and cclockwise adjacent faces when checking edge v1->v2 + static constexpr int etable[6][2] = {{3, 2}, {1, 3}, {2, 1}, {3, 0}, {0, 2}, {1, 0}}; // make this static? + // try all 6 edges because of very rare edge cases where ray is inside edge + // having only 1 non-zero Plücker product + int e = 0; + for (int v1 = 0; v1 < 3; v1++) + { + for (int v2 = v1 + 1; v2 < 4; v2++) + { + Vec moment12 = Vec::cross(dir, pos - vertex(v1)); + double prod12 = Vec::dot(moment12, edge(v1, v2)); + if (prod12 != 0.) + { + enteringFace = prod12 < 0 ? etable[e][0] : etable[e][1]; + break; + } + e++; + } + if (enteringFace != -1) break; + } + return enteringFace; +} + +bool TetraMeshSpatialGrid::Tetra::contains(const Position& bfr) const +{ + if (!_extent.contains(bfr)) return false; + + // could optimize this slightly by using same vertex for 3 faces and do final face seperately, but this is more readable + for (int f = 0; f < 4; f++) + { + const Face& face = _faces[f]; + Vec v = vertex((f + 1) % 4); // any vertex that is on the face + + // if point->face is opposite direction as the outward pointing normal, the point is outside + if (Vec::dot(v - bfr, face._normal) < 0) return false; + } + return true; +} + +// https://vcg.isti.cnr.it/activities/OLD/geometryegraphics/pointintetraedro.html +double TetraMeshSpatialGrid::Tetra::generateBarycentric(double& s, double& t, double& u) const +{ + if (s + t > 1.0) + { // cut'n fold the cube into a prism + s = 1.0 - s; + t = 1.0 - t; + } + if (t + u > 1.0) + { // cut'n fold the prism into a tetrahedron + double tmp = u; + u = 1.0 - s - t; + t = 1.0 - tmp; + } + else if (s + t + u > 1.0) + { + double tmp = u; + u = s + t + u - 1.0; + s = 1 - t - tmp; + } + return 1 - u - t - s; +} + +Position TetraMeshSpatialGrid::Tetra::generatePosition(Random* random) const +{ + double s = random->uniform(); + double t = random->uniform(); + double u = random->uniform(); + + double r = generateBarycentric(s, t, u); + + return Position(r * vertex(0) + u * vertex(1) + t * vertex(2) + s * vertex(3)); +} + +// get the vertex using local indices [0, 3] +Vec TetraMeshSpatialGrid::Tetra::vertex(int t) const +{ + return _vertices[_vertexIndices[t]]; +} + +// get the edge t1->t2 using local indices [0, 3] +Vec TetraMeshSpatialGrid::Tetra::edge(int t1, int t2) const +{ + return vertex(t2) - vertex(t1); +} + +double TetraMeshSpatialGrid::Tetra::volume() const +{ + return 1 / 6. * abs(Vec::dot(Vec::cross(edge(0, 1), edge(0, 2)), edge(0, 3))); +} + +double TetraMeshSpatialGrid::Tetra::diagonal() const +{ + double sum = 0.0; + for (int i = 0; i < 3; ++i) + { + for (int j = i + 1; j < 4; ++j) + { + sum += edge(i, j).norm2(); + } + } + return sqrt(sum / 6.0); +} + +const array& TetraMeshSpatialGrid::Tetra::faces() const +{ + return _faces; +} + +const Vec& TetraMeshSpatialGrid::Tetra::centroid() const +{ + return _centroid; +} + +const Box& TetraMeshSpatialGrid::Tetra::extent() const +{ + return _extent; +} + ////////////////////////////////////////////////////////////////////// -// This is a helper class for organizing cuboidal cells in a smart grid, so that -// it is easy to retrieve the first cell that overlaps a given point in space. -// The Box object on which this class is based specifies a cuboid guaranteed to -// enclose all cells in the grid. -class TetraMeshSpatialGrid::CellGrid +// This helper class contains all cell information such as vertices and tetrahedra. +// It also organizes cuboidal blocks in a smart grid, such that it is easy to retrieve +// all cells inside a certain block given a position. +class TetraMeshSpatialGrid::BlockGrid { - // data members initialized during construction - const vector& _tetra; // reference to the original list of tetrahedra - int _gridsize; // number of grid cells in each spatial direction + int _numCells; // number of Tetra cells and centroids + int _numVertices; // vertices are added/removed as the grid is built and refined + vector _tetrahedra; // list of all tetrahedra + vector _vertices; // list of all vertices + int _gridsize; // number of grid blocks in each spatial direction Array _xgrid, _ygrid, _zgrid; // the m+1 grid separation points for each spatial direction - vector> _listv; // the m*m*m lists of indices for cells overlapping each grid cell - int _pmin, _pmax, _ptotal; // minimum, maximum nr of cells in list; total nr of cells in listv + vector> _listv; // the m*m*m lists of indices for blocks overlapping each grid block + int _pmin, _pmax; // minimum, maximum nr of blocks in list; total nr of blocks in listv public: - // The constructor creates a cuboidal grid of the specified number of grid cells in each - // spatial direction, and for each of the grid cells it builds a list of all cells - // overlapping the grid cell. In an attempt to distribute the cells evenly over the - // grid cells, the sizes of the grid cells in each spatial direction are chosen so that - // the cell centers are evenly distributed over the grid cells. - CellGrid(const vector& tetra, Box extent, int gridsize) : _tetra(tetra), _gridsize(gridsize) + BlockGrid(){}; + + // The constructor creates a cuboidal grid of the specified number of grid blocks in each + // spatial direction, and for each of the grid blocks it builds a list of all blocks + // overlapping the grid block. In an attempt to distribute the blocks evenly over the + // grid blocks, the sizes of the grid blocks in each spatial direction are chosen so that + // the block centers are evenly distributed over the grid blocks. + BlockGrid(const vector& tetrahedra, Box extent, int gridsize) : _tetrahedra(tetrahedra), _gridsize(gridsize) { // build the grids in each spatial direction makegrid(0, gridsize, _xgrid, extent.xmin(), extent.xmax()); makegrid(1, gridsize, _ygrid, extent.ymin(), extent.ymax()); makegrid(2, gridsize, _zgrid, extent.zmin(), extent.zmax()); - // make room for p*p*p grid cells + // make room for p*p*p grid blocks _listv.resize(gridsize * gridsize * gridsize); - // add each cell to the list for every grid cell that it overlaps - int n = _tetra.size(); + // add each block to the list for every grid block that it overlaps + int n = _tetrahedra.size(); for (int m = 0; m != n; ++m) { - Box boundingBox = _tetra[m].extent(); + Box boundingBox = _tetrahedra[m].extent(); - // find indices for first and last grid cell overlapped by cell, in each spatial direction + // find indices for first and last grid block overlapped by block, in each spatial direction int i1 = NR::locateClip(_xgrid, boundingBox.xmin()); int i2 = NR::locateClip(_xgrid, boundingBox.xmax()); int j1 = NR::locateClip(_ygrid, boundingBox.ymin()); @@ -102,7 +258,7 @@ class TetraMeshSpatialGrid::CellGrid int k1 = NR::locateClip(_zgrid, boundingBox.zmin()); int k2 = NR::locateClip(_zgrid, boundingBox.zmax()); - // add the cell to all grid cells in that 3D range + // add the block to all grid blocks in that 3D range for (int i = i1; i <= i2; i++) for (int j = j1; j <= j2; j++) for (int k = k1; k <= k2; k++) @@ -114,40 +270,44 @@ class TetraMeshSpatialGrid::CellGrid // calculate statistics _pmin = n; _pmax = 0; - _ptotal = 0; for (int index = 0; index < gridsize * gridsize * gridsize; index++) { int size = _listv[index].size(); _pmin = min(_pmin, size); _pmax = max(_pmax, size); - _ptotal += size; } } void makegrid(int axis, int gridsize, Array& grid, double cmin, double cmax) { - int n = _tetra.size(); + int n = _tetrahedra.size(); - // determine the cell distribution by binning at a decent resolution + // determine the block distribution by binning at a decent resolution int nbins = gridsize * 100; double binwidth = (cmax - cmin) / nbins; vector bins(nbins); - for (const Tetra& tetra : _tetra) + for (const Tetra& tetra : _tetrahedra) { - double center = tetra.centroid(axis); + double center; + switch (axis) + { + case 0: center = tetra.centroid().x(); break; + case 1: center = tetra.centroid().y(); break; + case 2: center = tetra.centroid().z(); break; + } bins[static_cast((center - cmin) / binwidth)] += 1; } // determine grid separation points based on the cumulative distribution grid.resize(gridsize + 1); grid[0] = -std::numeric_limits::infinity(); - int percell = n / gridsize; // target number of particles per cell - int cumul = 0; // cumulative number of particles in processed bins - int gridindex = 1; // index of the next grid separation point to be filled + int perblock = n / gridsize; // target number of particles per block + int cumul = 0; // cumulative number of particles in processed bins + int gridindex = 1; // index of the next grid separation point to be filled for (int binindex = 0; binindex < nbins; binindex++) { cumul += bins[binindex]; - if (cumul > percell * gridindex) + if (cumul > perblock * gridindex) { grid[gridindex] = cmin + (binindex + 1) * binwidth; gridindex += 1; @@ -157,208 +317,36 @@ class TetraMeshSpatialGrid::CellGrid grid[gridsize] = std::numeric_limits::infinity(); } - // This function returns the smallest number of cells overlapping a single grid cell. - int minCellRefsPerCell() const { return _pmin; } + // This function returns the smallest number of blocks overlapping a single grid block. + int minCellRefsPerBlock() const { return _pmin; } - // This function returns the largest number of cells overlapping a single grid cell. - int maxCellRefsPerCell() const { return _pmax; } - - // This function returns the total number of cell references for all cells in the grid. - int totalCellRefs() const { return _ptotal; } + // This function returns the largest number of blocks overlapping a single grid block. + int maxCellRefsPerBlock() const { return _pmax; } // This function returns the index (in the list originally passed to the constructor) - // of the first cell in the list that overlaps the specified position, - // or -1 if none of the cells in the list overlap the specified position. + // of the first block in the list that overlaps the specified position, + // or -1 if none of the blocks in the list overlap the specified position. int cellIndexFor(Position r) const { - // locate the grid cell containing the specified position + // locate the grid block containing the specified position int i = NR::locateClip(_xgrid, r.x()); int j = NR::locateClip(_ygrid, r.y()); int k = NR::locateClip(_zgrid, r.z()); - // search the list of cells for that grid cell + // search the list of blocks for that grid block for (int m : _listv[index(_gridsize, i, j, k)]) { - if (_tetra[m].contains(r)) return m; + if (_tetrahedra[m].contains(r)) return m; } return -1; } }; -////////////////////////////////////////////////////////////////////// - -struct TetraMeshSpatialGrid::Face -{ - Face() {}; - - Face(int ntetra, int nface, Vec normal) : _ntetra(ntetra), _nface(nface), _normal(normal) {} - - Vec _normal; // outward facing normal - int _ntetra; // index of neighbouring tetrahedron - int _nface; // neighbouring face index -}; - -//////////////////////////////////////////////////////////////////// - -class TetraMeshSpatialGrid::Tetra -{ -private: - const vector& _vertices; // reference to the full list of vertices - Box _extent; // bounding box of the tetrahedron - Vec _centroid; // barycenter of the tetrahedron - array _vertexIndices; // indices of the vertices in the full list - array _faces; // face information - -public: - Tetra(const vector& vertices, const array& vertexIndices, const array& faces) - : _vertices(vertices), _vertexIndices(vertexIndices), _faces(faces) - { - double xmin = DBL_MAX; - double ymin = DBL_MAX; - double zmin = DBL_MAX; - double xmax = -DBL_MAX; - double ymax = -DBL_MAX; - double zmax = -DBL_MAX; - for (int vi : _vertexIndices) - { - const Vec& vertex = vertices[vi]; - - xmin = min(xmin, vertex.x()); - ymin = min(ymin, vertex.y()); - zmin = min(zmin, vertex.z()); - xmax = max(xmax, vertex.x()); - ymax = max(ymax, vertex.y()); - zmax = max(zmax, vertex.z()); - - _centroid += vertex; - } - // set bounding box - _extent = Box(xmin, ymin, zmin, xmax, ymax, zmax); - - // average position of all vertices - _centroid /= 4; - } - - int findEnteringFace(const Vec& pos, const Direction& dir) const - { - int enteringFace = -1; - // clockwise and cclockwise adjacent faces when checking edge v1->v2 - static constexpr int etable[6][2] = {{3, 2}, {1, 3}, {2, 1}, {3, 0}, {0, 2}, {1, 0}}; // make this static? - // try all 6 edges because of very rare edge cases where ray is inside edge - // having only 1 non-zero Plücker product - int e = 0; - for (int v1 = 0; v1 < 3; v1++) - { - for (int v2 = v1 + 1; v2 < 4; v2++) - { - Vec moment12 = Vec::cross(dir, pos - vertex(v1)); - double prod12 = Vec::dot(moment12, edge(v1, v2)); - if (prod12 != 0.) - { - enteringFace = prod12 < 0 ? etable[e][0] : etable[e][1]; - break; - } - e++; - } - if (enteringFace != -1) break; - } - return enteringFace; - } - - bool contains(const Position& bfr) const - { - if (!_extent.contains(bfr)) return false; - - // could optimize this slightly by using same vertex for 3 faces and do final face seperately, but this is more readable - for (int f = 0; f < 4; f++) - { - const Face& face = _faces[f]; - Vec v = vertex((f + 1) % 4); // any vertex that is on the face - - // if point->face is opposite direction as the outward pointing normal, the point is outside - if (Vec::dot(v - bfr, face._normal) < 0) return false; - } - return true; - } - - // https://vcg.isti.cnr.it/activities/OLD/geometryegraphics/pointintetraedro.html - double generateBarycentric(double& s, double& t, double& u) const - { - if (s + t > 1.0) - { // cut'n fold the cube into a prism - s = 1.0 - s; - t = 1.0 - t; - } - if (t + u > 1.0) - { // cut'n fold the prism into a tetrahedron - double tmp = u; - u = 1.0 - s - t; - t = 1.0 - tmp; - } - else if (s + t + u > 1.0) - { - double tmp = u; - u = s + t + u - 1.0; - s = 1 - t - tmp; - } - return 1 - u - t - s; - } - - Position generatePosition(Random* random) const - { - double s = random->uniform(); - double t = random->uniform(); - double u = random->uniform(); - - double r = generateBarycentric(s, t, u); - - return Position(r * vertex(0) + u * vertex(1) + t * vertex(2) + s * vertex(3)); - } - - // get the vertex using local indices [0, 3] - Vec vertex(int t) const { return _vertices[_vertexIndices[t]]; } - - // get the edge t1->t2 using local indices [0, 3] - Vec edge(int t1, int t2) const { return vertex(t2) - vertex(t1); } - - double volume() const { return 1 / 6. * abs(Vec::dot(Vec::cross(edge(0, 1), edge(0, 2)), edge(0, 3))); } - - double diagonal() const - { - double sum = 0.0; - for (int i = 0; i < 3; ++i) - { - for (int j = i + 1; j < 4; ++j) - { - sum += edge(i, j).norm2(); - } - } - return sqrt(sum / 6.0); - } - - const array& faces() const { return _faces; } - - const Vec& centroid() const { return _centroid; } - - const double centroid(int axis) const - { - switch (axis % 3) - { - case 0: return _centroid.x(); - case 1: return _centroid.y(); - case 2: return _centroid.z(); - default: return 0.0; - } - } - - const Box& extent() const { return _extent; } -}; - //////////////////////////////////////////////////////////////////// TetraMeshSpatialGrid::~TetraMeshSpatialGrid() { - delete _grid; + delete _blocks; } ////////////////////////////////////////////////////////////////////// @@ -556,7 +544,7 @@ void TetraMeshSpatialGrid::storeTetrahedra(const tetgenio& out, bool storeVertic } } - _tetrahedra.reserve(_numCells); // can't make empty Tetra so don't use resize + _tetrahedra.reserve(_numCells); // no default constructor for Tetra for (int i = 0; i < _numCells; i++) { std::array vertexIndices; @@ -635,12 +623,12 @@ void TetraMeshSpatialGrid::buildSearch() { int gridsize = max(20, static_cast(pow(_tetrahedra.size(), 1. / 3.) / 5)); string size = std::to_string(gridsize); - _log->info("Constructing intermediate " + size + "x" + size + "x" + size + " grid for cells..."); - _grid = new CellGrid(_tetrahedra, extent(), gridsize); - _log->info(" Smallest number of cells per grid cell: " + std::to_string(_grid->minCellRefsPerCell())); - _log->info(" Largest number of cells per grid cell: " + std::to_string(_grid->maxCellRefsPerCell())); - _log->info(" Average number of cells per grid cell: " - + StringUtils::toString(_grid->totalCellRefs() / double(gridsize * gridsize * gridsize), 'f', 1)); + _log->info("Constructing intermediate " + size + "x" + size + "x" + size + " grid for tetrahedra..."); + _blocks = new BlockGrid(_tetrahedra, extent(), gridsize); + _log->info(" Smallest number of tetrahedra per grid block: " + std::to_string(_blocks->minCellRefsPerBlock())); + _log->info(" Largest number of tetrahedra per grid block: " + std::to_string(_blocks->maxCellRefsPerBlock())); + _log->info(" Average number of tetrahedra per grid block: " + + StringUtils::toString(_numCells / double(gridsize * gridsize * gridsize), 'f', 1)); } ////////////////////////////////////////////////////////////////////// @@ -667,7 +655,7 @@ double TetraMeshSpatialGrid::diagonal(int m) const int TetraMeshSpatialGrid::cellIndex(Position bfr) const { - return _grid->cellIndexFor(bfr); + return _blocks->cellIndexFor(bfr); } ////////////////////////////////////////////////////////////////////// @@ -765,8 +753,6 @@ class TetraMeshSpatialGrid::MySegmentGenerator : public PathSegmentGenerator // intentionally falls through if (state() == State::Inside) { - double ds; - int leavingFace; // loop in case no exit point was found (which should happen only rarely) while (true) { diff --git a/SKIRT/core/TetraMeshSpatialGrid.hpp b/SKIRT/core/TetraMeshSpatialGrid.hpp index 49f12c59..e06d1c5c 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.hpp +++ b/SKIRT/core/TetraMeshSpatialGrid.hpp @@ -11,6 +11,7 @@ #include "Log.hpp" #include "Medium.hpp" #include "PathSegmentGenerator.hpp" +#include class tetgenio; @@ -39,7 +40,7 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid ENUM_VAL(Policy, ImportedSites, "positions of particles, sites or cells in imported distribution") ENUM_END() - ITEM_CONCRETE(TetraMeshSpatialGrid, BoxSpatialGrid, "a Tetra tessellation-based spatial grid") + ITEM_CONCRETE(TetraMeshSpatialGrid, BoxSpatialGrid, "a tetrahedral spatial grid") ATTRIBUTE_TYPE_DISPLAYED_IF(TetraMeshSpatialGrid, "Level2") PROPERTY_ENUM(policy, Policy, "the policy for determining the positions of the sites") @@ -71,9 +72,53 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid //==================== Private construction ==================== private: - class Node; - class Face; - class Tetra; + struct Face + { + Face(){}; + + Face(int ntetra, int nface, Vec normal) : _ntetra(ntetra), _nface(nface), _normal(normal) {} + + int _ntetra; // index of neighbouring tetrahedron + int _nface; // neighbouring face index + Vec _normal; // outward facing normal + }; + + class Tetra + { + private: + const vector& _vertices; // reference to the full list of vertices + Box _extent; // bounding box of the tetrahedron + Vec _centroid; // barycenter of the tetrahedron + std::array _vertexIndices; // indices of the vertices in the full list + std::array _faces; // face information + + public: + Tetra(const vector& vertices, const std::array& vertexIndices, const std::array& faces); + + int findEnteringFace(const Vec& pos, const Direction& dir) const; + + bool contains(const Position& bfr) const; + + double generateBarycentric(double& s, double& t, double& u) const; + + Position generatePosition(Random* random) const; + + Vec vertex(int t) const; + + Vec edge(int t1, int t2) const; + + double volume() const; + + double diagonal() const; + + const std::array& faces() const; + + const Vec& centroid() const; + + const Box& extent() const; + }; + + class BlockGrid; void buildMesh(); @@ -137,9 +182,9 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid vector _tetrahedra; vector _vertices; - // data members initialized after reading the input file if a density policy has been set - class CellGrid; - CellGrid* _grid{nullptr}; // smart grid for locating the cell at a given location + // smart grid that contains all cell information + // allows for efficiently locating the cell at a given location + BlockGrid* _blocks{nullptr}; // allow our path segment generator to access our private data members class MySegmentGenerator; diff --git a/SKIRT/core/VoronoiMeshSnapshot.cpp b/SKIRT/core/VoronoiMeshSnapshot.cpp index 84ce4685..3f3b683b 100644 --- a/SKIRT/core/VoronoiMeshSnapshot.cpp +++ b/SKIRT/core/VoronoiMeshSnapshot.cpp @@ -97,7 +97,7 @@ class VoronoiMeshSnapshot::Cell : public Box // enclosing box // constructor derives the site position from the first three property values and stores the user properties; // the other data members are set to zero or empty - Cell(const Array& prop) : _r(prop[0], prop[1], prop[2]), _properties{prop} {} // shouldn't this use _positionIndex instead of 0 1 2? + Cell(const Array& prop) : _r(prop[0], prop[1], prop[2]), _properties{prop} {} // adjusts the site position with the specified offset void relax(double cx, double cy, double cz) { _r += Vec(cx, cy, cz); } diff --git a/SKIRT/core/VoronoiMeshSnapshot.hpp b/SKIRT/core/VoronoiMeshSnapshot.hpp index 1c26d2de..2629ace4 100644 --- a/SKIRT/core/VoronoiMeshSnapshot.hpp +++ b/SKIRT/core/VoronoiMeshSnapshot.hpp @@ -181,7 +181,7 @@ class VoronoiMeshSnapshot : public Snapshot argument is true, the function performs a single relaxation step on the site positions. */ VoronoiMeshSnapshot(const SimulationItem* item, const Box& extent, const vector& sites, bool relax); - //========== Private construction ========== + //=========== Private construction ========== private: /** Private class to hold the information about a Voronoi cell that is relevant for calculating diff --git a/SKIRT/tetgen/CMakeLists.txt b/SKIRT/tetgen/CMakeLists.txt index fd8956e3..9da3481c 100644 --- a/SKIRT/tetgen/CMakeLists.txt +++ b/SKIRT/tetgen/CMakeLists.txt @@ -23,9 +23,7 @@ file(GLOB HEADERS "*.h") # create the library target add_library(${TARGET} STATIC ${SOURCES} ${HEADERS}) -target_compile_definitions(tetgen PRIVATE -DTETLIBRARY) -# Generate position independent code -# set_target_properties(tetgen PROPERTIES POSITION_INDEPENDENT_CODE ON) +target_compile_definitions(tetgen PRIVATE -DTETLIBRARY) # to compile TetGen as a library # adjust C++ compiler flags to our needs set(NO_EXTRA_WARNINGS true) # to avoid warnings in the tetgen code (warnings still present) diff --git a/SKIRT/tetgen/tetgen.cxx b/SKIRT/tetgen/tetgen.cxx index 8ed1b24a..899a47b6 100644 --- a/SKIRT/tetgen/tetgen.cxx +++ b/SKIRT/tetgen/tetgen.cxx @@ -36564,3 +36564,4 @@ void tetrahedralize(char *switches, tetgenio *in, tetgenio *out, // // // // //== main_cxx ================================================================// + diff --git a/SKIRT/tetgen/tetgen.h b/SKIRT/tetgen/tetgen.h index 65741082..ef67b5d0 100644 --- a/SKIRT/tetgen/tetgen.h +++ b/SKIRT/tetgen/tetgen.h @@ -45,13 +45,15 @@ // // //============================================================================// + #ifndef tetgenH #define tetgenH // To compile TetGen as a library instead of an executable program, define // the TETLIBRARY symbol. -#define TETLIBRARY +// #define TETLIBRARY + // TetGen default uses the double-precision (64 bit) for a real number. // Alternatively, one can use the single-precision (32 bit) 'float' if the @@ -71,10 +73,10 @@ // manipulate strings and arrays, compute common mathematical operations, // get date and time information. -#include #include #include #include +#include #include // The types 'intptr_t' and 'uintptr_t' are signed and unsigned integer types, @@ -104,534 +106,494 @@ // // //============================================================================// -class tetgenio -{ +class tetgenio { public: - // A "polygon" describes a simple polygon (no holes). It is not necessarily - // convex. Each polygon contains a number of corners (points) and the same - // number of sides (edges). The points of the polygon must be given in - // either counterclockwise or clockwise order and they form a ring, so - // every two consecutive points forms an edge of the polygon. - typedef struct - { - int* vertexlist; - int numberofvertices; - } polygon; - - // A "facet" describes a polygonal region possibly with holes, edges, and - // points floating in it. Each facet consists of a list of polygons and - // a list of hole points (which lie strictly inside holes). - typedef struct - { - polygon* polygonlist; - int numberofpolygons; - REAL* holelist; - int numberofholes; - } facet; - - // A "voroedge" is an edge of the Voronoi diagram. It corresponds to a - // Delaunay face. Each voroedge is either a line segment connecting - // two Voronoi vertices or a ray starting from a Voronoi vertex to an - // "infinite vertex". 'v1' and 'v2' are two indices pointing to the - // list of Voronoi vertices. 'v1' must be non-negative, while 'v2' may - // be -1 if it is a ray, in this case, the unit normal of this ray is - // given in 'vnormal'. - typedef struct - { - int v1, v2; - REAL vnormal[3]; - } voroedge; - - // A "vorofacet" is an facet of the Voronoi diagram. It corresponds to a - // Delaunay edge. Each Voronoi facet is a convex polygon formed by a - // list of Voronoi edges, it may not be closed. 'c1' and 'c2' are two - // indices pointing into the list of Voronoi cells, i.e., the two cells - // share this facet. 'elist' is an array of indices pointing into the - // list of Voronoi edges, 'elist[0]' saves the number of Voronoi edges - // (including rays) of this facet. - typedef struct - { - int c1, c2; - int* elist; - } vorofacet; - - // Additional parameters associated with an input (or mesh) vertex. - // These informations are provided by CAD libraries. - typedef struct - { - REAL uv[2]; - int tag; - int type; // 0, 1, or 2. - } pointparam; - - // Callback functions for meshing PSCs. - typedef REAL (*GetVertexParamOnEdge)(void*, int, int); - typedef void (*GetSteinerOnEdge)(void*, int, REAL, REAL*); - typedef void (*GetVertexParamOnFace)(void*, int, int, REAL*); - typedef void (*GetEdgeSteinerParamOnFace)(void*, int, REAL, int, REAL*); - typedef void (*GetSteinerOnFace)(void*, int, REAL*, REAL*); - - // A callback function for mesh refinement. - typedef bool (*TetSizeFunc)(REAL*, REAL*, REAL*, REAL*, REAL*, REAL); - - // Items are numbered starting from 'firstnumber' (0 or 1), default is 0. - int firstnumber; - - // Dimension of the mesh (2 or 3), default is 3. - int mesh_dim; - - // Does the lines in .node file contain index or not, default is 1. - int useindex; - - // 'pointlist': An array of point coordinates. The first point's x - // coordinate is at index [0] and its y coordinate at index [1], its - // z coordinate is at index [2], followed by the coordinates of the - // remaining points. Each point occupies three REALs. - // 'pointattributelist': An array of point attributes. Each point's - // attributes occupy 'numberofpointattributes' REALs. - // 'pointmtrlist': An array of metric tensors at points. Each point's - // tensor occupies 'numberofpointmtr' REALs. - // 'pointmarkerlist': An array of point markers; one integer per point. - // 'point2tetlist': An array of tetrahedra indices; one integer per point. - REAL* pointlist; - REAL* pointattributelist; - REAL* pointmtrlist; - int* pointmarkerlist; - int* point2tetlist; - pointparam* pointparamlist; - int numberofpoints; - int numberofpointattributes; - int numberofpointmtrs; - - // 'tetrahedronlist': An array of tetrahedron corners. The first - // tetrahedron's first corner is at index [0], followed by its other - // corners, followed by six nodes on the edges of the tetrahedron if the - // second order option (-o2) is applied. Each tetrahedron occupies - // 'numberofcorners' ints. The second order nodes are ouput only. - // 'tetrahedronattributelist': An array of tetrahedron attributes. Each - // tetrahedron's attributes occupy 'numberoftetrahedronattributes' REALs. - // 'tetrahedronvolumelist': An array of constraints, i.e. tetrahedron's - // volume; one REAL per element. Input only. - // 'neighborlist': An array of tetrahedron neighbors; 4 ints per element. - // 'tet2facelist': An array of tetrahedron face indices; 4 ints per element. - // 'tet2edgelist': An array of tetrahedron edge indices; 6 ints per element. - int* tetrahedronlist; - REAL* tetrahedronattributelist; - REAL* tetrahedronvolumelist; - int* neighborlist; - int* tet2facelist; - int* tet2edgelist; - int numberoftetrahedra; - int numberofcorners; - int numberoftetrahedronattributes; - - // 'facetlist': An array of facets. Each entry is a structure of facet. - // 'facetmarkerlist': An array of facet markers; one int per facet. - facet* facetlist; - int* facetmarkerlist; - int numberoffacets; - - // 'holelist': An array of holes (in volume). Each hole is given by a - // seed (point) which lies strictly inside it. The first seed's x, y and z - // coordinates are at indices [0], [1] and [2], followed by the - // remaining seeds. Three REALs per hole. - REAL* holelist; - int numberofholes; - // 'regionlist': An array of regions (subdomains). Each region is given by - // a seed (point) which lies strictly inside it. The first seed's x, y and - // z coordinates are at indices [0], [1] and [2], followed by the regional - // attribute at index [3], followed by the maximum volume at index [4]. - // Five REALs per region. - // Note that each regional attribute is used only if you select the 'A' - // switch, and each volume constraint is used only if you select the - // 'a' switch (with no number following). - REAL* regionlist; - int numberofregions; - - // 'refine_elem_list': An array of tetrahedra to be refined. The first - // tetrahedron's first corner is at index [0], followed by its other - // corners. Four integers per element. - // 'refine_elem_vol_list': An array of constraints, i.e. tetrahedron's - // volume; one REAL per element. - int* refine_elem_list; - REAL* refine_elem_vol_list; - int numberofrefineelems; - - // 'facetconstraintlist': An array of facet constraints. Each constraint - // specifies a maximum area bound on the subfaces of that facet. The - // first facet constraint is given by a facet marker at index [0] and its - // maximum area bound at index [1], followed by the remaining facet con- - // straints. Two REALs per facet constraint. Note: the facet marker is - // actually an integer. - REAL* facetconstraintlist; - int numberoffacetconstraints; - - // 'segmentconstraintlist': An array of segment constraints. Each constraint - // specifies a maximum length bound on the subsegments of that segment. - // The first constraint is given by the two endpoints of the segment at - // index [0] and [1], and the maximum length bound at index [2], followed - // by the remaining segment constraints. Three REALs per constraint. - // Note the segment endpoints are actually integers. - REAL* segmentconstraintlist; - int numberofsegmentconstraints; - - // 'trifacelist': An array of face (triangle) corners. The first face's - // three corners are at indices [0], [1] and [2], followed by the remaining - // faces. Three ints per face. - // 'trifacemarkerlist': An array of face markers; one int per face. - // 'o2facelist': An array of second order nodes (on the edges) of the face. - // It is output only if the second order option (-o2) is applied. The - // first face's three second order nodes are at [0], [1], and [2], - // followed by the remaining faces. Three ints per face. - // 'face2tetlist': An array of tetrahedra indices; 2 ints per face. - // 'face2edgelist': An array of edge indices; 3 ints per face. - int* trifacelist; - int* trifacemarkerlist; - int* o2facelist; - int* face2tetlist; - int* face2edgelist; - int numberoftrifaces; - - // 'edgelist': An array of edge endpoints. The first edge's endpoints - // are at indices [0] and [1], followed by the remaining edges. - // Two ints per edge. - // 'edgemarkerlist': An array of edge markers; one int per edge. - // 'o2edgelist': An array of midpoints of edges. It is output only if the - // second order option (-o2) is applied. One int per edge. - // 'edge2tetlist': An array of tetrahedra indices. One int per edge. - int* edgelist; - int* edgemarkerlist; - int* o2edgelist; - int* edge2tetlist; - int numberofedges; - - // 'vpointlist': An array of Voronoi vertex coordinates (like pointlist). - // 'vedgelist': An array of Voronoi edges. Each entry is a 'voroedge'. - // 'vfacetlist': An array of Voronoi facets. Each entry is a 'vorofacet'. - // 'vcelllist': An array of Voronoi cells. Each entry is an array of - // indices pointing into 'vfacetlist'. The 0th entry is used to store - // the length of this array. - REAL* vpointlist; - voroedge* vedgelist; - vorofacet* vfacetlist; - int** vcelllist; - int numberofvpoints; - int numberofvedges; - int numberofvfacets; - int numberofvcells; - - // Variable (and callback functions) for meshing PSCs. - void* geomhandle; - GetVertexParamOnEdge getvertexparamonedge; - GetSteinerOnEdge getsteineronedge; - GetVertexParamOnFace getvertexparamonface; - GetEdgeSteinerParamOnFace getedgesteinerparamonface; - GetSteinerOnFace getsteineronface; - - // A callback function. - TetSizeFunc tetunsuitable; - - // Input & output routines. - bool load_node_call(FILE* infile, int markers, int uvflag, char*); - bool load_node(char*); - bool load_edge(char*); - bool load_face(char*); - bool load_tet(char*); - bool load_vol(char*); - bool load_var(char*); - bool load_mtr(char*); - bool load_elem(char*); - bool load_poly(char*); - bool load_off(char*); - bool load_ply(char*); - bool load_stl(char*); - bool load_vtk(char*); - bool load_medit(char*, int); - bool load_neumesh(char*, int); - bool load_plc(char*, int); - bool load_tetmesh(char*, int); - void save_nodes(const char*); - void save_elements(const char*); - void save_faces(const char*); - void save_edges(char*); - void save_neighbors(char*); - void save_poly(const char*); - void save_faces2smesh(char*); - - // Read line and parse string functions. - char* readline(char* string, FILE* infile, int* linenumber); - char* findnextfield(char* string); - char* readnumberline(char* string, FILE* infile, char* infilename); - char* findnextnumber(char* string); - - static void init(polygon* p) - { - p->vertexlist = (int*)NULL; - p->numberofvertices = 0; + // A "polygon" describes a simple polygon (no holes). It is not necessarily + // convex. Each polygon contains a number of corners (points) and the same + // number of sides (edges). The points of the polygon must be given in + // either counterclockwise or clockwise order and they form a ring, so + // every two consecutive points forms an edge of the polygon. + typedef struct { + int *vertexlist; + int numberofvertices; + } polygon; + + // A "facet" describes a polygonal region possibly with holes, edges, and + // points floating in it. Each facet consists of a list of polygons and + // a list of hole points (which lie strictly inside holes). + typedef struct { + polygon *polygonlist; + int numberofpolygons; + REAL *holelist; + int numberofholes; + } facet; + + // A "voroedge" is an edge of the Voronoi diagram. It corresponds to a + // Delaunay face. Each voroedge is either a line segment connecting + // two Voronoi vertices or a ray starting from a Voronoi vertex to an + // "infinite vertex". 'v1' and 'v2' are two indices pointing to the + // list of Voronoi vertices. 'v1' must be non-negative, while 'v2' may + // be -1 if it is a ray, in this case, the unit normal of this ray is + // given in 'vnormal'. + typedef struct { + int v1, v2; + REAL vnormal[3]; + } voroedge; + + // A "vorofacet" is an facet of the Voronoi diagram. It corresponds to a + // Delaunay edge. Each Voronoi facet is a convex polygon formed by a + // list of Voronoi edges, it may not be closed. 'c1' and 'c2' are two + // indices pointing into the list of Voronoi cells, i.e., the two cells + // share this facet. 'elist' is an array of indices pointing into the + // list of Voronoi edges, 'elist[0]' saves the number of Voronoi edges + // (including rays) of this facet. + typedef struct { + int c1, c2; + int *elist; + } vorofacet; + + + // Additional parameters associated with an input (or mesh) vertex. + // These informations are provided by CAD libraries. + typedef struct { + REAL uv[2]; + int tag; + int type; // 0, 1, or 2. + } pointparam; + + // Callback functions for meshing PSCs. + typedef REAL (* GetVertexParamOnEdge)(void*, int, int); + typedef void (* GetSteinerOnEdge)(void*, int, REAL, REAL*); + typedef void (* GetVertexParamOnFace)(void*, int, int, REAL*); + typedef void (* GetEdgeSteinerParamOnFace)(void*, int, REAL, int, REAL*); + typedef void (* GetSteinerOnFace)(void*, int, REAL*, REAL*); + + // A callback function for mesh refinement. + typedef bool (* TetSizeFunc)(REAL*, REAL*, REAL*, REAL*, REAL*, REAL); + + // Items are numbered starting from 'firstnumber' (0 or 1), default is 0. + int firstnumber; + + // Dimension of the mesh (2 or 3), default is 3. + int mesh_dim; + + // Does the lines in .node file contain index or not, default is 1. + int useindex; + + // 'pointlist': An array of point coordinates. The first point's x + // coordinate is at index [0] and its y coordinate at index [1], its + // z coordinate is at index [2], followed by the coordinates of the + // remaining points. Each point occupies three REALs. + // 'pointattributelist': An array of point attributes. Each point's + // attributes occupy 'numberofpointattributes' REALs. + // 'pointmtrlist': An array of metric tensors at points. Each point's + // tensor occupies 'numberofpointmtr' REALs. + // 'pointmarkerlist': An array of point markers; one integer per point. + // 'point2tetlist': An array of tetrahedra indices; one integer per point. + REAL *pointlist; + REAL *pointattributelist; + REAL *pointmtrlist; + int *pointmarkerlist; + int *point2tetlist; + pointparam *pointparamlist; + int numberofpoints; + int numberofpointattributes; + int numberofpointmtrs; + + // 'tetrahedronlist': An array of tetrahedron corners. The first + // tetrahedron's first corner is at index [0], followed by its other + // corners, followed by six nodes on the edges of the tetrahedron if the + // second order option (-o2) is applied. Each tetrahedron occupies + // 'numberofcorners' ints. The second order nodes are ouput only. + // 'tetrahedronattributelist': An array of tetrahedron attributes. Each + // tetrahedron's attributes occupy 'numberoftetrahedronattributes' REALs. + // 'tetrahedronvolumelist': An array of constraints, i.e. tetrahedron's + // volume; one REAL per element. Input only. + // 'neighborlist': An array of tetrahedron neighbors; 4 ints per element. + // 'tet2facelist': An array of tetrahedron face indices; 4 ints per element. + // 'tet2edgelist': An array of tetrahedron edge indices; 6 ints per element. + int *tetrahedronlist; + REAL *tetrahedronattributelist; + REAL *tetrahedronvolumelist; + int *neighborlist; + int *tet2facelist; + int *tet2edgelist; + int numberoftetrahedra; + int numberofcorners; + int numberoftetrahedronattributes; + + // 'facetlist': An array of facets. Each entry is a structure of facet. + // 'facetmarkerlist': An array of facet markers; one int per facet. + facet *facetlist; + int *facetmarkerlist; + int numberoffacets; + + // 'holelist': An array of holes (in volume). Each hole is given by a + // seed (point) which lies strictly inside it. The first seed's x, y and z + // coordinates are at indices [0], [1] and [2], followed by the + // remaining seeds. Three REALs per hole. + REAL *holelist; + int numberofholes; + + // 'regionlist': An array of regions (subdomains). Each region is given by + // a seed (point) which lies strictly inside it. The first seed's x, y and + // z coordinates are at indices [0], [1] and [2], followed by the regional + // attribute at index [3], followed by the maximum volume at index [4]. + // Five REALs per region. + // Note that each regional attribute is used only if you select the 'A' + // switch, and each volume constraint is used only if you select the + // 'a' switch (with no number following). + REAL *regionlist; + int numberofregions; + + // 'refine_elem_list': An array of tetrahedra to be refined. The first + // tetrahedron's first corner is at index [0], followed by its other + // corners. Four integers per element. + // 'refine_elem_vol_list': An array of constraints, i.e. tetrahedron's + // volume; one REAL per element. + int *refine_elem_list; + REAL *refine_elem_vol_list; + int numberofrefineelems; + + // 'facetconstraintlist': An array of facet constraints. Each constraint + // specifies a maximum area bound on the subfaces of that facet. The + // first facet constraint is given by a facet marker at index [0] and its + // maximum area bound at index [1], followed by the remaining facet con- + // straints. Two REALs per facet constraint. Note: the facet marker is + // actually an integer. + REAL *facetconstraintlist; + int numberoffacetconstraints; + + // 'segmentconstraintlist': An array of segment constraints. Each constraint + // specifies a maximum length bound on the subsegments of that segment. + // The first constraint is given by the two endpoints of the segment at + // index [0] and [1], and the maximum length bound at index [2], followed + // by the remaining segment constraints. Three REALs per constraint. + // Note the segment endpoints are actually integers. + REAL *segmentconstraintlist; + int numberofsegmentconstraints; + + + // 'trifacelist': An array of face (triangle) corners. The first face's + // three corners are at indices [0], [1] and [2], followed by the remaining + // faces. Three ints per face. + // 'trifacemarkerlist': An array of face markers; one int per face. + // 'o2facelist': An array of second order nodes (on the edges) of the face. + // It is output only if the second order option (-o2) is applied. The + // first face's three second order nodes are at [0], [1], and [2], + // followed by the remaining faces. Three ints per face. + // 'face2tetlist': An array of tetrahedra indices; 2 ints per face. + // 'face2edgelist': An array of edge indices; 3 ints per face. + int *trifacelist; + int *trifacemarkerlist; + int *o2facelist; + int *face2tetlist; + int *face2edgelist; + int numberoftrifaces; + + // 'edgelist': An array of edge endpoints. The first edge's endpoints + // are at indices [0] and [1], followed by the remaining edges. + // Two ints per edge. + // 'edgemarkerlist': An array of edge markers; one int per edge. + // 'o2edgelist': An array of midpoints of edges. It is output only if the + // second order option (-o2) is applied. One int per edge. + // 'edge2tetlist': An array of tetrahedra indices. One int per edge. + int *edgelist; + int *edgemarkerlist; + int *o2edgelist; + int *edge2tetlist; + int numberofedges; + + // 'vpointlist': An array of Voronoi vertex coordinates (like pointlist). + // 'vedgelist': An array of Voronoi edges. Each entry is a 'voroedge'. + // 'vfacetlist': An array of Voronoi facets. Each entry is a 'vorofacet'. + // 'vcelllist': An array of Voronoi cells. Each entry is an array of + // indices pointing into 'vfacetlist'. The 0th entry is used to store + // the length of this array. + REAL *vpointlist; + voroedge *vedgelist; + vorofacet *vfacetlist; + int **vcelllist; + int numberofvpoints; + int numberofvedges; + int numberofvfacets; + int numberofvcells; + + + // Variable (and callback functions) for meshing PSCs. + void *geomhandle; + GetVertexParamOnEdge getvertexparamonedge; + GetSteinerOnEdge getsteineronedge; + GetVertexParamOnFace getvertexparamonface; + GetEdgeSteinerParamOnFace getedgesteinerparamonface; + GetSteinerOnFace getsteineronface; + + // A callback function. + TetSizeFunc tetunsuitable; + + // Input & output routines. + bool load_node_call(FILE* infile, int markers, int uvflag, char*); + bool load_node(char*); + bool load_edge(char*); + bool load_face(char*); + bool load_tet(char*); + bool load_vol(char*); + bool load_var(char*); + bool load_mtr(char*); + bool load_elem(char*); + bool load_poly(char*); + bool load_off(char*); + bool load_ply(char*); + bool load_stl(char*); + bool load_vtk(char*); + bool load_medit(char*, int); + bool load_neumesh(char*, int); + bool load_plc(char*, int); + bool load_tetmesh(char*, int); + void save_nodes(const char*); + void save_elements(const char*); + void save_faces(const char*); + void save_edges(char*); + void save_neighbors(char*); + void save_poly(const char*); + void save_faces2smesh(char*); + + // Read line and parse string functions. + char *readline(char* string, FILE* infile, int *linenumber); + char *findnextfield(char* string); + char *readnumberline(char* string, FILE* infile, char* infilename); + char *findnextnumber(char* string); + + static void init(polygon* p) { + p->vertexlist = (int *) NULL; + p->numberofvertices = 0; + } + + static void init(facet* f) { + f->polygonlist = (polygon *) NULL; + f->numberofpolygons = 0; + f->holelist = (REAL *) NULL; + f->numberofholes = 0; + } + + // Initialize routine. + void initialize() + { + firstnumber = 0; + mesh_dim = 3; + useindex = 1; + + pointlist = (REAL *) NULL; + pointattributelist = (REAL *) NULL; + pointmtrlist = (REAL *) NULL; + pointmarkerlist = (int *) NULL; + point2tetlist = (int *) NULL; + pointparamlist = (pointparam *) NULL; + numberofpoints = 0; + numberofpointattributes = 0; + numberofpointmtrs = 0; + + tetrahedronlist = (int *) NULL; + tetrahedronattributelist = (REAL *) NULL; + tetrahedronvolumelist = (REAL *) NULL; + neighborlist = (int *) NULL; + tet2facelist = (int *) NULL; + tet2edgelist = (int *) NULL; + numberoftetrahedra = 0; + numberofcorners = 4; + numberoftetrahedronattributes = 0; + + trifacelist = (int *) NULL; + trifacemarkerlist = (int *) NULL; + o2facelist = (int *) NULL; + face2tetlist = (int *) NULL; + face2edgelist = (int *) NULL; + numberoftrifaces = 0; + + edgelist = (int *) NULL; + edgemarkerlist = (int *) NULL; + o2edgelist = (int *) NULL; + edge2tetlist = (int *) NULL; + numberofedges = 0; + + facetlist = (facet *) NULL; + facetmarkerlist = (int *) NULL; + numberoffacets = 0; + + holelist = (REAL *) NULL; + numberofholes = 0; + + regionlist = (REAL *) NULL; + numberofregions = 0; + + refine_elem_list = (int *) NULL; + refine_elem_vol_list = (REAL *) NULL; + numberofrefineelems = 0; + + facetconstraintlist = (REAL *) NULL; + numberoffacetconstraints = 0; + segmentconstraintlist = (REAL *) NULL; + numberofsegmentconstraints = 0; + + + vpointlist = (REAL *) NULL; + vedgelist = (voroedge *) NULL; + vfacetlist = (vorofacet *) NULL; + vcelllist = (int **) NULL; + numberofvpoints = 0; + numberofvedges = 0; + numberofvfacets = 0; + numberofvcells = 0; + + + tetunsuitable = NULL; + + geomhandle = NULL; + getvertexparamonedge = NULL; + getsteineronedge = NULL; + getvertexparamonface = NULL; + getedgesteinerparamonface = NULL; + getsteineronface = NULL; + } + + // Free the memory allocated in 'tetgenio'. Note that it assumes that the + // memory was allocated by the "new" operator (C++). + void clean_memory() + { + int i, j; + + if (pointlist != (REAL *) NULL) { + delete [] pointlist; } - - static void init(facet* f) - { - f->polygonlist = (polygon*)NULL; - f->numberofpolygons = 0; - f->holelist = (REAL*)NULL; - f->numberofholes = 0; + if (pointattributelist != (REAL *) NULL) { + delete [] pointattributelist; } - - // Initialize routine. - void initialize() - { - firstnumber = 0; - mesh_dim = 3; - useindex = 1; - - pointlist = (REAL*)NULL; - pointattributelist = (REAL*)NULL; - pointmtrlist = (REAL*)NULL; - pointmarkerlist = (int*)NULL; - point2tetlist = (int*)NULL; - pointparamlist = (pointparam*)NULL; - numberofpoints = 0; - numberofpointattributes = 0; - numberofpointmtrs = 0; - - tetrahedronlist = (int*)NULL; - tetrahedronattributelist = (REAL*)NULL; - tetrahedronvolumelist = (REAL*)NULL; - neighborlist = (int*)NULL; - tet2facelist = (int*)NULL; - tet2edgelist = (int*)NULL; - numberoftetrahedra = 0; - numberofcorners = 4; - numberoftetrahedronattributes = 0; - - trifacelist = (int*)NULL; - trifacemarkerlist = (int*)NULL; - o2facelist = (int*)NULL; - face2tetlist = (int*)NULL; - face2edgelist = (int*)NULL; - numberoftrifaces = 0; - - edgelist = (int*)NULL; - edgemarkerlist = (int*)NULL; - o2edgelist = (int*)NULL; - edge2tetlist = (int*)NULL; - numberofedges = 0; - - facetlist = (facet*)NULL; - facetmarkerlist = (int*)NULL; - numberoffacets = 0; - - holelist = (REAL*)NULL; - numberofholes = 0; - - regionlist = (REAL*)NULL; - numberofregions = 0; - - refine_elem_list = (int*)NULL; - refine_elem_vol_list = (REAL*)NULL; - numberofrefineelems = 0; - - facetconstraintlist = (REAL*)NULL; - numberoffacetconstraints = 0; - segmentconstraintlist = (REAL*)NULL; - numberofsegmentconstraints = 0; - - vpointlist = (REAL*)NULL; - vedgelist = (voroedge*)NULL; - vfacetlist = (vorofacet*)NULL; - vcelllist = (int**)NULL; - numberofvpoints = 0; - numberofvedges = 0; - numberofvfacets = 0; - numberofvcells = 0; - - tetunsuitable = NULL; - - geomhandle = NULL; - getvertexparamonedge = NULL; - getsteineronedge = NULL; - getvertexparamonface = NULL; - getedgesteinerparamonface = NULL; - getsteineronface = NULL; + if (pointmtrlist != (REAL *) NULL) { + delete [] pointmtrlist; + } + if (pointmarkerlist != (int *) NULL) { + delete [] pointmarkerlist; + } + if (point2tetlist != (int *) NULL) { + delete [] point2tetlist; + } + if (pointparamlist != (pointparam *) NULL) { + delete [] pointparamlist; } - // Free the memory allocated in 'tetgenio'. Note that it assumes that the - // memory was allocated by the "new" operator (C++). - void clean_memory() - { - int i, j; - - if (pointlist != (REAL*)NULL) - { - delete[] pointlist; - } - if (pointattributelist != (REAL*)NULL) - { - delete[] pointattributelist; - } - if (pointmtrlist != (REAL*)NULL) - { - delete[] pointmtrlist; - } - if (pointmarkerlist != (int*)NULL) - { - delete[] pointmarkerlist; - } - if (point2tetlist != (int*)NULL) - { - delete[] point2tetlist; - } - if (pointparamlist != (pointparam*)NULL) - { - delete[] pointparamlist; - } - - if (tetrahedronlist != (int*)NULL) - { - delete[] tetrahedronlist; - } - if (tetrahedronattributelist != (REAL*)NULL) - { - delete[] tetrahedronattributelist; - } - if (tetrahedronvolumelist != (REAL*)NULL) - { - delete[] tetrahedronvolumelist; - } - if (neighborlist != (int*)NULL) - { - delete[] neighborlist; - } - if (tet2facelist != (int*)NULL) - { - delete[] tet2facelist; - } - if (tet2edgelist != (int*)NULL) - { - delete[] tet2edgelist; - } - - if (trifacelist != (int*)NULL) - { - delete[] trifacelist; - } - if (trifacemarkerlist != (int*)NULL) - { - delete[] trifacemarkerlist; - } - if (o2facelist != (int*)NULL) - { - delete[] o2facelist; - } - if (face2tetlist != (int*)NULL) - { - delete[] face2tetlist; - } - if (face2edgelist != (int*)NULL) - { - delete[] face2edgelist; - } + if (tetrahedronlist != (int *) NULL) { + delete [] tetrahedronlist; + } + if (tetrahedronattributelist != (REAL *) NULL) { + delete [] tetrahedronattributelist; + } + if (tetrahedronvolumelist != (REAL *) NULL) { + delete [] tetrahedronvolumelist; + } + if (neighborlist != (int *) NULL) { + delete [] neighborlist; + } + if (tet2facelist != (int *) NULL) { + delete [] tet2facelist; + } + if (tet2edgelist != (int *) NULL) { + delete [] tet2edgelist; + } + + if (trifacelist != (int *) NULL) { + delete [] trifacelist; + } + if (trifacemarkerlist != (int *) NULL) { + delete [] trifacemarkerlist; + } + if (o2facelist != (int *) NULL) { + delete [] o2facelist; + } + if (face2tetlist != (int *) NULL) { + delete [] face2tetlist; + } + if (face2edgelist != (int *) NULL) { + delete [] face2edgelist; + } - if (edgelist != (int*)NULL) - { - delete[] edgelist; - } - if (edgemarkerlist != (int*)NULL) - { - delete[] edgemarkerlist; - } - if (o2edgelist != (int*)NULL) - { - delete[] o2edgelist; - } - if (edge2tetlist != (int*)NULL) - { - delete[] edge2tetlist; - } + if (edgelist != (int *) NULL) { + delete [] edgelist; + } + if (edgemarkerlist != (int *) NULL) { + delete [] edgemarkerlist; + } + if (o2edgelist != (int *) NULL) { + delete [] o2edgelist; + } + if (edge2tetlist != (int *) NULL) { + delete [] edge2tetlist; + } - if (facetlist != (facet*)NULL) - { - facet* f; - polygon* p; - for (i = 0; i < numberoffacets; i++) - { - f = &facetlist[i]; - for (j = 0; j < f->numberofpolygons; j++) - { - p = &f->polygonlist[j]; - delete[] p->vertexlist; - } - delete[] f->polygonlist; - if (f->holelist != (REAL*)NULL) - { - delete[] f->holelist; - } - } - delete[] facetlist; + if (facetlist != (facet *) NULL) { + facet *f; + polygon *p; + for (i = 0; i < numberoffacets; i++) { + f = &facetlist[i]; + for (j = 0; j < f->numberofpolygons; j++) { + p = &f->polygonlist[j]; + delete [] p->vertexlist; } - if (facetmarkerlist != (int*)NULL) - { - delete[] facetmarkerlist; + delete [] f->polygonlist; + if (f->holelist != (REAL *) NULL) { + delete [] f->holelist; } + } + delete [] facetlist; + } + if (facetmarkerlist != (int *) NULL) { + delete [] facetmarkerlist; + } - if (holelist != (REAL*)NULL) - { - delete[] holelist; - } - if (regionlist != (REAL*)NULL) - { - delete[] regionlist; - } - if (refine_elem_list != (int*)NULL) - { - delete[] refine_elem_list; - if (refine_elem_vol_list != (REAL*)NULL) - { - delete[] refine_elem_vol_list; - } - } - if (facetconstraintlist != (REAL*)NULL) - { - delete[] facetconstraintlist; - } - if (segmentconstraintlist != (REAL*)NULL) - { - delete[] segmentconstraintlist; - } - if (vpointlist != (REAL*)NULL) - { - delete[] vpointlist; - } - if (vedgelist != (voroedge*)NULL) - { - delete[] vedgelist; - } - if (vfacetlist != (vorofacet*)NULL) - { - for (i = 0; i < numberofvfacets; i++) - { - delete[] vfacetlist[i].elist; - } - delete[] vfacetlist; - } - if (vcelllist != (int**)NULL) - { - for (i = 0; i < numberofvcells; i++) - { - delete[] vcelllist[i]; - } - delete[] vcelllist; - } + if (holelist != (REAL *) NULL) { + delete [] holelist; + } + if (regionlist != (REAL *) NULL) { + delete [] regionlist; } + if (refine_elem_list != (int *) NULL) { + delete [] refine_elem_list; + if (refine_elem_vol_list != (REAL *) NULL) { + delete [] refine_elem_vol_list; + } + } + if (facetconstraintlist != (REAL *) NULL) { + delete [] facetconstraintlist; + } + if (segmentconstraintlist != (REAL *) NULL) { + delete [] segmentconstraintlist; + } + if (vpointlist != (REAL *) NULL) { + delete [] vpointlist; + } + if (vedgelist != (voroedge *) NULL) { + delete [] vedgelist; + } + if (vfacetlist != (vorofacet *) NULL) { + for (i = 0; i < numberofvfacets; i++) { + delete [] vfacetlist[i].elist; + } + delete [] vfacetlist; + } + if (vcelllist != (int **) NULL) { + for (i = 0; i < numberofvcells; i++) { + delete [] vcelllist[i]; + } + delete [] vcelllist; + } + } - // Constructor & destructor. - tetgenio() { initialize(); } - ~tetgenio() { clean_memory(); } + // Constructor & destructor. + tetgenio() {initialize();} + ~tetgenio() {clean_memory();} -}; // class tetgenio +}; // class tetgenio //============================================================================// // // @@ -650,226 +612,230 @@ class tetgenio // // //============================================================================// -class tetgenbehavior -{ +class tetgenbehavior { public: - // Switches of TetGen. - int plc; // '-p', 0. - int psc; // '-s', 0. - int refine; // '-r', 0. - int quality; // '-q', 0. - int nobisect; // '-Y', 0. - int cdt; // '-D', 0. - int cdtrefine; // '-D#', 7. - int coarsen; // '-R', 0. - int weighted; // '-w', 0. - int brio_hilbert; // '-b', 1. - int flipinsert; // '-L', 0. - int metric; // '-m', 0. - int varvolume; // '-a', 0. - int fixedvolume; // '-a', 0. - int regionattrib; // '-A', 0. - int insertaddpoints; // '-i', 0. - int diagnose; // '-d', 0. - int convex; // '-c', 0. - int nomergefacet; // '-M', 0. - int nomergevertex; // '-M', 0. - int noexact; // '-X', 0. - int nostaticfilter; // '-X', 0. - int zeroindex; // '-z', 0. - int facesout; // '-f', 0. - int edgesout; // '-e', 0. - int neighout; // '-n', 0. - int voroout; // '-v', 0. - int meditview; // '-g', 0. - int vtkview; // '-k', 0. - int vtksurfview; // '-k', 0. - int nobound; // '-B', 0. - int nonodewritten; // '-N', 0. - int noelewritten; // '-E', 0. - int nofacewritten; // '-F', 0. - int noiterationnum; // '-I', 0. - int nojettison; // '-J', 0. - int docheck; // '-C', 0. - int quiet; // '-Q', 0. - int nowarning; // '-W', 0. - int verbose; // '-V', 0. - - // Parameters of TetGen. - int vertexperblock; // '-x', 4092. - int tetrahedraperblock; // '-x', 8188. - int shellfaceperblock; // '-x', 2044. - int supsteiner_level; // '-Y/', 2. - int addsteiner_algo; // '-Y//', 1. - int coarsen_param; // '-R', 0. - int weighted_param; // '-w', 0. - int fliplinklevel; // -1. - int flipstarsize; // -1. - int fliplinklevelinc; // 1. - int opt_max_flip_level; // '-O', 3. - int opt_scheme; // '-O/#', 7. - int opt_iterations; // -O//#, 3. - int smooth_cirterion; // -s, 1. - int smooth_maxiter; // -s, 7. - int delmaxfliplevel; // 1. - int order; // '-o', 1. - int reversetetori; // '-o/', 0. - int steinerleft; // '-S', 0. - int unflip_queue_limit; // '-U#', 1000. - int no_sort; // 0. - int hilbert_order; // '-b///', 52. - int hilbert_limit; // '-b//' 8. - int brio_threshold; // '-b' 64. - REAL brio_ratio; // '-b/' 0.125. - REAL epsilon; // '-T', 1.0e-8. - REAL facet_separate_ang_tol; // '-p', 179.9. - REAL collinear_ang_tol; // '-p/', 179.9. - REAL facet_small_ang_tol; // '-p//', 15.0. - REAL maxvolume; // '-a', -1.0. - REAL maxvolume_length; // '-a', -1.0. - REAL minratio; // '-q', 0.0. - REAL opt_max_asp_ratio; // 1000.0. - REAL opt_max_edge_ratio; // 100.0. - REAL mindihedral; // '-q', 5.0. - REAL optmaxdihedral; // -o/# 177.0. - REAL metric_scale; // -m#, 1.0. - REAL smooth_alpha; // '-s', 0.3. - REAL coarsen_percent; // -R1/#, 1.0. - REAL elem_growth_ratio; // Growth ratio of # elements, -r#, 0.0. - REAL refine_progress_ratio; // -r/#, 0.333. - - // Strings of command line arguments and input/output file names. - char commandline[1024]; - char infilename[1024]; - char outfilename[1024]; - char addinfilename[1024]; - char bgmeshfilename[1024]; - - // Read an additional tetrahedral mesh and treat it as holes [2018-07-30]. - int hole_mesh; // '-H', 0. - char hole_mesh_filename[1024]; - - // The input object of TetGen. They are recognized by either the input - // file extensions or by the specified options. - // Currently the following objects are supported: - // - NODES, a list of nodes (.node); - // - POLY, a piecewise linear complex (.poly or .smesh); - // - OFF, a polyhedron (.off, Geomview's file format); - // - PLY, a polyhedron (.ply, file format from gatech, only ASCII); - // - STL, a surface mesh (.stl, stereolithography format); - // - MEDIT, a surface mesh (.mesh, Medit's file format); - // - MESH, a tetrahedral mesh (.ele). - // If no extension is available, the imposed command line switch - // (-p or -r) implies the object. - enum objecttype { NODES, POLY, OFF, PLY, STL, MEDIT, VTK, MESH, NEU_MESH } object; - - void syntax(); - void usage(); - - // Command line parse routine. - bool parse_commandline(int argc, char** argv); - bool parse_commandline(char* switches) { return parse_commandline(0, &switches); } - - // Initialize all variables. - tetgenbehavior() - { - plc = 0; - psc = 0; - refine = 0; - quality = 0; - nobisect = 0; - cdt = 0; // set by -D (without a number following it) - cdtrefine = 7; // default, set by -D# - coarsen = 0; - metric = 0; - weighted = 0; - brio_hilbert = 1; - flipinsert = 0; - varvolume = 0; - fixedvolume = 0; - noexact = 0; - nostaticfilter = 0; - insertaddpoints = 0; - regionattrib = 0; - diagnose = 0; - convex = 0; - zeroindex = 0; - facesout = 0; - edgesout = 0; - neighout = 0; - voroout = 0; - meditview = 0; - vtkview = 0; - vtksurfview = 0; - nobound = 0; - nonodewritten = 0; - noelewritten = 0; - nofacewritten = 0; - noiterationnum = 0; - nomergefacet = 0; - nomergevertex = 0; - nojettison = 0; - docheck = 0; - quiet = 0; - nowarning = 0; - verbose = 0; - - vertexperblock = 4092; - tetrahedraperblock = 8188; - shellfaceperblock = 4092; - supsteiner_level = 2; - addsteiner_algo = 1; - coarsen_param = 0; - weighted_param = 0; - fliplinklevel = -1; - flipstarsize = -1; - fliplinklevelinc = 1; - opt_scheme = 7; - opt_max_flip_level = 3; - opt_iterations = 3; - delmaxfliplevel = 1; - order = 1; - reversetetori = 0; - steinerleft = -1; - unflip_queue_limit = 1000; - no_sort = 0; - hilbert_order = 52; //-1; - hilbert_limit = 8; - brio_threshold = 64; - brio_ratio = 0.125; - facet_separate_ang_tol = 179.9; - collinear_ang_tol = 179.9; - facet_small_ang_tol = 15.0; - maxvolume = -1.0; - maxvolume_length = -1.0; - minratio = 2.0; - opt_max_asp_ratio = 1000.; - opt_max_edge_ratio = 100.; - mindihedral = 3.5; - optmaxdihedral = 177.00; - epsilon = 1.0e-8; - coarsen_percent = 1.0; - metric_scale = 1.0; // -m# - elem_growth_ratio = 0.0; // -r# - refine_progress_ratio = 0.333; // -r/# - object = NODES; - - smooth_cirterion = 3; // -s# default smooth surface and volume vertices. - smooth_maxiter = 7; // set by -s#/7 - smooth_alpha = 0.3; // relax parameter, set by -s#/#/0.3 - - commandline[0] = '\0'; - infilename[0] = '\0'; - outfilename[0] = '\0'; - addinfilename[0] = '\0'; - bgmeshfilename[0] = '\0'; - - hole_mesh = 0; - hole_mesh_filename[0] = '\0'; - } -}; // class tetgenbehavior + // Switches of TetGen. + int plc; // '-p', 0. + int psc; // '-s', 0. + int refine; // '-r', 0. + int quality; // '-q', 0. + int nobisect; // '-Y', 0. + int cdt; // '-D', 0. + int cdtrefine; // '-D#', 7. + int coarsen; // '-R', 0. + int weighted; // '-w', 0. + int brio_hilbert; // '-b', 1. + int flipinsert; // '-L', 0. + int metric; // '-m', 0. + int varvolume; // '-a', 0. + int fixedvolume; // '-a', 0. + int regionattrib; // '-A', 0. + int insertaddpoints; // '-i', 0. + int diagnose; // '-d', 0. + int convex; // '-c', 0. + int nomergefacet; // '-M', 0. + int nomergevertex; // '-M', 0. + int noexact; // '-X', 0. + int nostaticfilter; // '-X', 0. + int zeroindex; // '-z', 0. + int facesout; // '-f', 0. + int edgesout; // '-e', 0. + int neighout; // '-n', 0. + int voroout; // '-v', 0. + int meditview; // '-g', 0. + int vtkview; // '-k', 0. + int vtksurfview; // '-k', 0. + int nobound; // '-B', 0. + int nonodewritten; // '-N', 0. + int noelewritten; // '-E', 0. + int nofacewritten; // '-F', 0. + int noiterationnum; // '-I', 0. + int nojettison; // '-J', 0. + int docheck; // '-C', 0. + int quiet; // '-Q', 0. + int nowarning; // '-W', 0. + int verbose; // '-V', 0. + + // Parameters of TetGen. + int vertexperblock; // '-x', 4092. + int tetrahedraperblock; // '-x', 8188. + int shellfaceperblock; // '-x', 2044. + int supsteiner_level; // '-Y/', 2. + int addsteiner_algo; // '-Y//', 1. + int coarsen_param; // '-R', 0. + int weighted_param; // '-w', 0. + int fliplinklevel; // -1. + int flipstarsize; // -1. + int fliplinklevelinc; // 1. + int opt_max_flip_level; // '-O', 3. + int opt_scheme; // '-O/#', 7. + int opt_iterations; // -O//#, 3. + int smooth_cirterion; // -s, 1. + int smooth_maxiter; // -s, 7. + int delmaxfliplevel; // 1. + int order; // '-o', 1. + int reversetetori; // '-o/', 0. + int steinerleft; // '-S', 0. + int unflip_queue_limit; // '-U#', 1000. + int no_sort; // 0. + int hilbert_order; // '-b///', 52. + int hilbert_limit; // '-b//' 8. + int brio_threshold; // '-b' 64. + REAL brio_ratio; // '-b/' 0.125. + REAL epsilon; // '-T', 1.0e-8. + REAL facet_separate_ang_tol; // '-p', 179.9. + REAL collinear_ang_tol; // '-p/', 179.9. + REAL facet_small_ang_tol; // '-p//', 15.0. + REAL maxvolume; // '-a', -1.0. + REAL maxvolume_length; // '-a', -1.0. + REAL minratio; // '-q', 0.0. + REAL opt_max_asp_ratio; // 1000.0. + REAL opt_max_edge_ratio; // 100.0. + REAL mindihedral; // '-q', 5.0. + REAL optmaxdihedral; // -o/# 177.0. + REAL metric_scale; // -m#, 1.0. + REAL smooth_alpha; // '-s', 0.3. + REAL coarsen_percent; // -R1/#, 1.0. + REAL elem_growth_ratio; // Growth ratio of # elements, -r#, 0.0. + REAL refine_progress_ratio; // -r/#, 0.333. + + // Strings of command line arguments and input/output file names. + char commandline[1024]; + char infilename[1024]; + char outfilename[1024]; + char addinfilename[1024]; + char bgmeshfilename[1024]; + + // Read an additional tetrahedral mesh and treat it as holes [2018-07-30]. + int hole_mesh; // '-H', 0. + char hole_mesh_filename[1024]; + + // The input object of TetGen. They are recognized by either the input + // file extensions or by the specified options. + // Currently the following objects are supported: + // - NODES, a list of nodes (.node); + // - POLY, a piecewise linear complex (.poly or .smesh); + // - OFF, a polyhedron (.off, Geomview's file format); + // - PLY, a polyhedron (.ply, file format from gatech, only ASCII); + // - STL, a surface mesh (.stl, stereolithography format); + // - MEDIT, a surface mesh (.mesh, Medit's file format); + // - MESH, a tetrahedral mesh (.ele). + // If no extension is available, the imposed command line switch + // (-p or -r) implies the object. + enum objecttype {NODES, POLY, OFF, PLY, STL, MEDIT, VTK, MESH, NEU_MESH} object; + + + void syntax(); + void usage(); + + // Command line parse routine. + bool parse_commandline(int argc, char **argv); + bool parse_commandline(char *switches) { + return parse_commandline(0, &switches); + } + + // Initialize all variables. + tetgenbehavior() + { + plc = 0; + psc = 0; + refine = 0; + quality = 0; + nobisect = 0; + cdt = 0; // set by -D (without a number following it) + cdtrefine = 7; // default, set by -D# + coarsen = 0; + metric = 0; + weighted = 0; + brio_hilbert = 1; + flipinsert = 0; + varvolume = 0; + fixedvolume = 0; + noexact = 0; + nostaticfilter = 0; + insertaddpoints = 0; + regionattrib = 0; + diagnose = 0; + convex = 0; + zeroindex = 0; + facesout = 0; + edgesout = 0; + neighout = 0; + voroout = 0; + meditview = 0; + vtkview = 0; + vtksurfview = 0; + nobound = 0; + nonodewritten = 0; + noelewritten = 0; + nofacewritten = 0; + noiterationnum = 0; + nomergefacet = 0; + nomergevertex = 0; + nojettison = 0; + docheck = 0; + quiet = 0; + nowarning = 0; + verbose = 0; + + vertexperblock = 4092; + tetrahedraperblock = 8188; + shellfaceperblock = 4092; + supsteiner_level = 2; + addsteiner_algo = 1; + coarsen_param = 0; + weighted_param = 0; + fliplinklevel = -1; + flipstarsize = -1; + fliplinklevelinc = 1; + opt_scheme = 7; + opt_max_flip_level = 3; + opt_iterations = 3; + delmaxfliplevel = 1; + order = 1; + reversetetori = 0; + steinerleft = -1; + unflip_queue_limit = 1000; + no_sort = 0; + hilbert_order = 52; //-1; + hilbert_limit = 8; + brio_threshold = 64; + brio_ratio = 0.125; + facet_separate_ang_tol = 179.9; + collinear_ang_tol = 179.9; + facet_small_ang_tol = 15.0; + maxvolume = -1.0; + maxvolume_length = -1.0; + minratio = 2.0; + opt_max_asp_ratio = 1000.; + opt_max_edge_ratio = 100.; + mindihedral = 3.5; + optmaxdihedral = 177.00; + epsilon = 1.0e-8; + coarsen_percent = 1.0; + metric_scale = 1.0; // -m# + elem_growth_ratio = 0.0; // -r# + refine_progress_ratio = 0.333; // -r/# + object = NODES; + + smooth_cirterion = 3; // -s# default smooth surface and volume vertices. + smooth_maxiter = 7; // set by -s#/7 + smooth_alpha = 0.3; // relax parameter, set by -s#/#/0.3 + + commandline[0] = '\0'; + infilename[0] = '\0'; + outfilename[0] = '\0'; + addinfilename[0] = '\0'; + bgmeshfilename[0] = '\0'; + + hole_mesh = 0; + hole_mesh_filename[0] = '\0'; + + } + +}; // class tetgenbehavior //============================================================================// // // @@ -890,13 +856,16 @@ class tetgenbehavior void exactinit(int, int, int, REAL, REAL, REAL); -REAL orient3d(REAL* pa, REAL* pb, REAL* pc, REAL* pd); -REAL insphere(REAL* pa, REAL* pb, REAL* pc, REAL* pd, REAL* pe); -REAL orient4d(REAL* pa, REAL* pb, REAL* pc, REAL* pd, REAL* pe, REAL ah, REAL bh, REAL ch, REAL dh, REAL eh); +REAL orient3d(REAL *pa, REAL *pb, REAL *pc, REAL *pd); +REAL insphere(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL *pe); +REAL orient4d(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL *pe, + REAL ah, REAL bh, REAL ch, REAL dh, REAL eh); + +REAL orient2dexact(REAL *pa, REAL *pb, REAL *pc); +REAL orient3dexact(REAL *pa, REAL *pb, REAL *pc, REAL *pd); +REAL orient4dexact(REAL* pa, REAL* pb, REAL* pc, REAL* pd, REAL* pe, + REAL ah, REAL bh, REAL ch, REAL dh, REAL eh); -REAL orient2dexact(REAL* pa, REAL* pb, REAL* pc); -REAL orient3dexact(REAL* pa, REAL* pb, REAL* pc, REAL* pd); -REAL orient4dexact(REAL* pa, REAL* pb, REAL* pc, REAL* pd, REAL* pe, REAL ah, REAL bh, REAL ch, REAL dh, REAL eh); //============================================================================// // // @@ -910,1619 +879,1580 @@ REAL orient4dexact(REAL* pa, REAL* pb, REAL* pc, REAL* pd, REAL* pe, REAL ah, RE // // //============================================================================// -class tetgenmesh -{ +class tetgenmesh { public: - //============================================================================// - // // - // Mesh data structure // - // // - // A tetrahedral mesh T of a 3D piecewise linear complex (PLC) X is a 3D // - // simplicial complex whose underlying space is equal to the space of X. T // - // contains a 2D subcomplex S which is a triangular mesh of the boundary of // - // X. S contains a 1D subcomplex L which is a linear mesh of the boundary of // - // S. Faces and edges in S and L are respectively called subfaces and segme- // - // nts to distinguish them from others in T. // - // // - // TetGen uses a tetrahedron-based data structure. It stores tetrahedra and // - // vertices. This data structure is pointer-based. Each tetrahedron contains // - // pointers to its vertices and adjacent tetrahedra. Each vertex holds its x-,// - // y-, z-coordinates, and a pointer to one of the tetrahedra having it. Both // - // tetrahedra and vertices may contain user data. // - // // - // Let T be a tetrahedralization. Each triangular face of T belongs to either // - // two or one tetrahedron. In the latter case, it is an exterior boundary // - // face of T. TetGen attaches tetrahedra (one-to-one) to such faces. All such // - // tetrahedra contain an "infinite vertex" (which has no geometric coordinates// - // ). One can imagine such a vertex lies in 4D space and is visible by all // - // exterior boundary faces simultaneously. This extended set of tetrahedra // - // (including the infinite vertex) becomes a tetrahedralization of a 3-sphere // - // that has no boundary in 3d. It has the nice property that every triangular // - // face is shared by exactly two tetrahedra. // - // // - // The current version of TetGen stores explicitly the subfaces and segments // - // (which are in surface mesh S and the linear mesh L), respectively. Extra // - // pointers are allocated in tetrahedra and subfaces to point each other. // - // // - //============================================================================// - - // The tetrahedron data structure. It includes the following fields: - // - a list of four adjoining tetrahedra; - // - a list of four vertices; - // - a pointer to a list of four subfaces (optional, for -p switch); - // - a pointer to a list of six segments (optional, for -p switch); - // - a list of user-defined floating-point attributes (optional); - // - a volume constraint (optional, for -a switch); - // - an integer of element marker (and flags); - // The structure of a tetrahedron is an array of pointers. Its actual size - // (the length of the array) is determined at runtime. - - typedef REAL** tetrahedron; - - // The subface data structure. It includes the following fields: - // - a list of three adjoining subfaces; - // - a list of three vertices; - // - a list of three adjoining segments; - // - two adjoining tetrahedra; - // - an area constraint (optional, for -q switch); - // - an integer for boundary marker; - // - an integer for type, flags, etc. - - typedef REAL** shellface; - - // The point data structure. It includes the following fields: - // - x, y and z coordinates; - // - a list of user-defined point attributes (optional); - // - u, v coordinates (optional, for -s switch); - // - a metric tensor (optional, for -q or -m switch); - // - a pointer to an adjacent tetrahedron; - // - a pointer to a parent (or a duplicate) point; - // - a pointer to an adjacent subface or segment (optional, -p switch); - // - a pointer to a tet in background mesh (optional, for -m switch); - // - an integer for boundary marker (point index); - // - an integer for point type (and flags). - // - an integer for geometry tag (optional, for -s switch). - // The structure of a point is an array of REALs. Its acutal size is - // determined at the runtime. - - typedef REAL* point; - - //============================================================================// - // // - // Handles // - // // - // Navigation and manipulation in a tetrahedralization are accomplished by // - // operating on structures referred as ``handles". A handle is a pair (t,v), // - // where t is a pointer to a tetrahedron, and v is a 4-bit integer, in the // - // range from 0 to 11. v is called the ``version'' of a tetrahedron, it rep- // - // resents a directed edge of a specific face of the tetrahedron. // - // // - // There are 12 even permutations of the four vertices, each of them corres- // - // ponds to a directed edge (a version) of the tetrahedron. The 12 versions // - // can be grouped into 4 distinct ``edge rings'' in 4 ``oriented faces'' of // - // this tetrahedron. One can encode each version (a directed edge) into a // - // 4-bit integer such that the two upper bits encode the index (from 0 to 2) // - // of this edge in the edge ring, and the two lower bits encode the index ( // - // from 0 to 3) of the oriented face which contains this edge. // - // // - // The four vertices of a tetrahedron are indexed from 0 to 3 (according to // - // their storage in the data structure). Give each face the same index as // - // the node opposite it in the tetrahedron. Denote the edge connecting face // - // i to face j as i/j. We number the twelve versions as follows: // - // // - // | edge 0 edge 1 edge 2 // - // --------|-------------------------------- // - // face 0 | 0 (0/1) 4 (0/3) 8 (0/2) // - // face 1 | 1 (1/2) 5 (1/3) 9 (1/0) // - // face 2 | 2 (2/3) 6 (2/1) 10 (2/0) // - // face 3 | 3 (3/0) 7 (3/1) 11 (3/2) // - // // - // Similarly, navigation and manipulation in a (boundary) triangulation are // - // done by using handles of triangles. Each handle is a pair (s, v), where s // - // is a pointer to a triangle, and v is a version in the range from 0 to 5. // - // Each version corresponds to a directed edge of this triangle. // - // // - // Number the three vertices of a triangle from 0 to 2 (according to their // - // storage in the data structure). Give each edge the same index as the node // - // opposite it in the triangle. The six versions of a triangle are: // - // // - // | edge 0 edge 1 edge 2 // - // ---------------|-------------------------- // - // ccw orieation | 0 2 4 // - // cw orieation | 1 3 5 // - // // - // In the following, a 'triface' is a handle of tetrahedron, and a 'face' is // - // a handle of a triangle. // - // // - //============================================================================// - - class triface - { - public: - tetrahedron* tet; - int ver; // Range from 0 to 11. - triface() : tet(0), ver(0) {} - triface& operator=(const triface& t) - { - tet = t.tet; - ver = t.ver; - return *this; - } - }; - - class face - { - public: - shellface* sh; - int shver; // Range from 0 to 5. - face() : sh(0), shver(0) {} - face& operator=(const face& s) - { - sh = s.sh; - shver = s.shver; - return *this; - } - }; - - //============================================================================// - // // - // Arraypool // - // // - // A dynamic linear array. (It is written by J. Shewchuk) // - // // - // Each arraypool contains an array of pointers to a number of blocks. Each // - // block contains the same fixed number of objects. Each index of the array // - // addresses a particular object in the pool. The most significant bits add- // - // ress the index of the block containing the object. The less significant // - // bits address this object within the block. // - // // - // 'objectbytes' is the size of one object in blocks; 'log2objectsperblock' // - // is the base-2 logarithm of 'objectsperblock'; 'objects' counts the number // - // of allocated objects; 'totalmemory' is the total memory in bytes. // - // // - //============================================================================// - - class arraypool - { - - public: - int objectbytes; - int objectsperblock; - int log2objectsperblock; - int objectsperblockmark; - int toparraylen; - char** toparray; - long objects; - unsigned long totalmemory; - - void restart(); - void poolinit(int sizeofobject, int log2objperblk); - char* getblock(int objectindex); - void* lookup(int objectindex); - int newindex(void** newptr); - - arraypool(int sizeofobject, int log2objperblk); - ~arraypool(); - }; - - // fastlookup() -- A fast, unsafe operation. Return the pointer to the object - // with a given index. Note: The object's block must have been allocated, - // i.e., by the function newindex(). + +//============================================================================// +// // +// Mesh data structure // +// // +// A tetrahedral mesh T of a 3D piecewise linear complex (PLC) X is a 3D // +// simplicial complex whose underlying space is equal to the space of X. T // +// contains a 2D subcomplex S which is a triangular mesh of the boundary of // +// X. S contains a 1D subcomplex L which is a linear mesh of the boundary of // +// S. Faces and edges in S and L are respectively called subfaces and segme- // +// nts to distinguish them from others in T. // +// // +// TetGen uses a tetrahedron-based data structure. It stores tetrahedra and // +// vertices. This data structure is pointer-based. Each tetrahedron contains // +// pointers to its vertices and adjacent tetrahedra. Each vertex holds its x-,// +// y-, z-coordinates, and a pointer to one of the tetrahedra having it. Both // +// tetrahedra and vertices may contain user data. // +// // +// Let T be a tetrahedralization. Each triangular face of T belongs to either // +// two or one tetrahedron. In the latter case, it is an exterior boundary // +// face of T. TetGen attaches tetrahedra (one-to-one) to such faces. All such // +// tetrahedra contain an "infinite vertex" (which has no geometric coordinates// +// ). One can imagine such a vertex lies in 4D space and is visible by all // +// exterior boundary faces simultaneously. This extended set of tetrahedra // +// (including the infinite vertex) becomes a tetrahedralization of a 3-sphere // +// that has no boundary in 3d. It has the nice property that every triangular // +// face is shared by exactly two tetrahedra. // +// // +// The current version of TetGen stores explicitly the subfaces and segments // +// (which are in surface mesh S and the linear mesh L), respectively. Extra // +// pointers are allocated in tetrahedra and subfaces to point each other. // +// // +//============================================================================// + + // The tetrahedron data structure. It includes the following fields: + // - a list of four adjoining tetrahedra; + // - a list of four vertices; + // - a pointer to a list of four subfaces (optional, for -p switch); + // - a pointer to a list of six segments (optional, for -p switch); + // - a list of user-defined floating-point attributes (optional); + // - a volume constraint (optional, for -a switch); + // - an integer of element marker (and flags); + // The structure of a tetrahedron is an array of pointers. Its actual size + // (the length of the array) is determined at runtime. + + typedef REAL **tetrahedron; + + // The subface data structure. It includes the following fields: + // - a list of three adjoining subfaces; + // - a list of three vertices; + // - a list of three adjoining segments; + // - two adjoining tetrahedra; + // - an area constraint (optional, for -q switch); + // - an integer for boundary marker; + // - an integer for type, flags, etc. + + typedef REAL **shellface; + + // The point data structure. It includes the following fields: + // - x, y and z coordinates; + // - a list of user-defined point attributes (optional); + // - u, v coordinates (optional, for -s switch); + // - a metric tensor (optional, for -q or -m switch); + // - a pointer to an adjacent tetrahedron; + // - a pointer to a parent (or a duplicate) point; + // - a pointer to an adjacent subface or segment (optional, -p switch); + // - a pointer to a tet in background mesh (optional, for -m switch); + // - an integer for boundary marker (point index); + // - an integer for point type (and flags). + // - an integer for geometry tag (optional, for -s switch). + // The structure of a point is an array of REALs. Its acutal size is + // determined at the runtime. + + typedef REAL *point; + +//============================================================================// +// // +// Handles // +// // +// Navigation and manipulation in a tetrahedralization are accomplished by // +// operating on structures referred as ``handles". A handle is a pair (t,v), // +// where t is a pointer to a tetrahedron, and v is a 4-bit integer, in the // +// range from 0 to 11. v is called the ``version'' of a tetrahedron, it rep- // +// resents a directed edge of a specific face of the tetrahedron. // +// // +// There are 12 even permutations of the four vertices, each of them corres- // +// ponds to a directed edge (a version) of the tetrahedron. The 12 versions // +// can be grouped into 4 distinct ``edge rings'' in 4 ``oriented faces'' of // +// this tetrahedron. One can encode each version (a directed edge) into a // +// 4-bit integer such that the two upper bits encode the index (from 0 to 2) // +// of this edge in the edge ring, and the two lower bits encode the index ( // +// from 0 to 3) of the oriented face which contains this edge. // +// // +// The four vertices of a tetrahedron are indexed from 0 to 3 (according to // +// their storage in the data structure). Give each face the same index as // +// the node opposite it in the tetrahedron. Denote the edge connecting face // +// i to face j as i/j. We number the twelve versions as follows: // +// // +// | edge 0 edge 1 edge 2 // +// --------|-------------------------------- // +// face 0 | 0 (0/1) 4 (0/3) 8 (0/2) // +// face 1 | 1 (1/2) 5 (1/3) 9 (1/0) // +// face 2 | 2 (2/3) 6 (2/1) 10 (2/0) // +// face 3 | 3 (3/0) 7 (3/1) 11 (3/2) // +// // +// Similarly, navigation and manipulation in a (boundary) triangulation are // +// done by using handles of triangles. Each handle is a pair (s, v), where s // +// is a pointer to a triangle, and v is a version in the range from 0 to 5. // +// Each version corresponds to a directed edge of this triangle. // +// // +// Number the three vertices of a triangle from 0 to 2 (according to their // +// storage in the data structure). Give each edge the same index as the node // +// opposite it in the triangle. The six versions of a triangle are: // +// // +// | edge 0 edge 1 edge 2 // +// ---------------|-------------------------- // +// ccw orieation | 0 2 4 // +// cw orieation | 1 3 5 // +// // +// In the following, a 'triface' is a handle of tetrahedron, and a 'face' is // +// a handle of a triangle. // +// // +//============================================================================// + + class triface { + public: + tetrahedron *tet; + int ver; // Range from 0 to 11. + triface() : tet(0), ver(0) {} + triface& operator=(const triface& t) { + tet = t.tet; ver = t.ver; + return *this; + } + }; + + class face { + public: + shellface *sh; + int shver; // Range from 0 to 5. + face() : sh(0), shver(0) {} + face& operator=(const face& s) { + sh = s.sh; shver = s.shver; + return *this; + } + }; + +//============================================================================// +// // +// Arraypool // +// // +// A dynamic linear array. (It is written by J. Shewchuk) // +// // +// Each arraypool contains an array of pointers to a number of blocks. Each // +// block contains the same fixed number of objects. Each index of the array // +// addresses a particular object in the pool. The most significant bits add- // +// ress the index of the block containing the object. The less significant // +// bits address this object within the block. // +// // +// 'objectbytes' is the size of one object in blocks; 'log2objectsperblock' // +// is the base-2 logarithm of 'objectsperblock'; 'objects' counts the number // +// of allocated objects; 'totalmemory' is the total memory in bytes. // +// // +//============================================================================// + + class arraypool { + + public: + + int objectbytes; + int objectsperblock; + int log2objectsperblock; + int objectsperblockmark; + int toparraylen; + char **toparray; + long objects; + unsigned long totalmemory; + + void restart(); + void poolinit(int sizeofobject, int log2objperblk); + char* getblock(int objectindex); + void* lookup(int objectindex); + int newindex(void **newptr); + + arraypool(int sizeofobject, int log2objperblk); + ~arraypool(); + }; + +// fastlookup() -- A fast, unsafe operation. Return the pointer to the object +// with a given index. Note: The object's block must have been allocated, +// i.e., by the function newindex(). #define fastlookup(pool, index) \ - (void*)((pool)->toparray[(index) >> (pool)->log2objectsperblock] \ - + ((index) & (pool)->objectsperblockmark) * (pool)->objectbytes) - - //============================================================================// - // // - // Memorypool // - // // - // A structure for memory allocation. (It is written by J. Shewchuk) // - // // - // firstblock is the first block of items. nowblock is the block from which // - // items are currently being allocated. nextitem points to the next slab // - // of free memory for an item. deaditemstack is the head of a linked list // - // (stack) of deallocated items that can be recycled. unallocateditems is // - // the number of items that remain to be allocated from nowblock. // - // // - // Traversal is the process of walking through the entire list of items, and // - // is separate from allocation. Note that a traversal will visit items on // - // the "deaditemstack" stack as well as live items. pathblock points to // - // the block currently being traversed. pathitem points to the next item // - // to be traversed. pathitemsleft is the number of items that remain to // - // be traversed in pathblock. // - // // - //============================================================================// - - class memorypool - { - - public: - void **firstblock, **nowblock; - void* nextitem; - void* deaditemstack; - void** pathblock; - void* pathitem; - int alignbytes; - int itembytes, itemwords; - int itemsperblock; - long items, maxitems; - int unallocateditems; - int pathitemsleft; - - memorypool(); - memorypool(int, int, int, int); - ~memorypool(); - - void poolinit(int, int, int, int); - void restart(); - void* alloc(); - void dealloc(void*); - void traversalinit(); - void* traverse(); - }; - - //============================================================================// - // // - // badface // - // // - // Despite of its name, a 'badface' can be used to represent one of the // - // following objects: // - // - a face of a tetrahedron which is (possibly) non-Delaunay; // - // - an encroached subsegment or subface; // - // - a bad-quality tetrahedron, i.e, has too large radius-edge ratio; // - // - a sliver, i.e., has good radius-edge ratio but nearly zero volume; // - // - a recently flipped face (saved for undoing the flip later). // - // // - //============================================================================// - - class badface - { - public: - triface tt; - face ss; - REAL key, cent[6]; // circumcenter or cos(dihedral angles) at 6 edges. - point forg, fdest, fapex, foppo, noppo; - badface* nextitem; - badface() : key(0), forg(0), fdest(0), fapex(0), foppo(0), noppo(0), nextitem(0) {} - void init() - { - key = 0.; - for (int k = 0; k < 6; k++) cent[k] = 0.; - tt.tet = NULL; - tt.ver = 0; - ss.sh = NULL; - ss.shver = 0; - forg = fdest = fapex = foppo = noppo = NULL; - nextitem = NULL; - } - }; - - //============================================================================// - // // - // insertvertexflags // - // // - // A collection of flags that pass to the routine insertvertex(). // - // // - //============================================================================// - - class insertvertexflags - { - - public: - int iloc; // input/output. - int bowywat, lawson; - int splitbdflag, validflag, respectbdflag; - int rejflag, chkencflag, cdtflag; - int assignmeshsize; - int sloc, sbowywat; - - // Used by Delaunay refinement. - int collect_inial_cavity_flag; - int ignore_near_vertex; - int check_insert_radius; - int refineflag; // 0, 1, 2, 3 - triface refinetet; - face refinesh; - int smlenflag; // for useinsertradius. - REAL smlen; // for useinsertradius. - point parentpt; - - void init() - { - iloc = bowywat = lawson = 0; - splitbdflag = validflag = respectbdflag = 0; - rejflag = chkencflag = cdtflag = 0; - assignmeshsize = 0; - sloc = sbowywat = 0; - - collect_inial_cavity_flag = 0; - ignore_near_vertex = 0; - check_insert_radius = 0; - refineflag = 0; - refinetet.tet = NULL; - refinesh.sh = NULL; - smlenflag = 0; - smlen = 0.0; - parentpt = NULL; - } + (void *) ((pool)->toparray[(index) >> (pool)->log2objectsperblock] + \ + ((index) & (pool)->objectsperblockmark) * (pool)->objectbytes) + +//============================================================================// +// // +// Memorypool // +// // +// A structure for memory allocation. (It is written by J. Shewchuk) // +// // +// firstblock is the first block of items. nowblock is the block from which // +// items are currently being allocated. nextitem points to the next slab // +// of free memory for an item. deaditemstack is the head of a linked list // +// (stack) of deallocated items that can be recycled. unallocateditems is // +// the number of items that remain to be allocated from nowblock. // +// // +// Traversal is the process of walking through the entire list of items, and // +// is separate from allocation. Note that a traversal will visit items on // +// the "deaditemstack" stack as well as live items. pathblock points to // +// the block currently being traversed. pathitem points to the next item // +// to be traversed. pathitemsleft is the number of items that remain to // +// be traversed in pathblock. // +// // +//============================================================================// + + class memorypool { + + public: + + void **firstblock, **nowblock; + void *nextitem; + void *deaditemstack; + void **pathblock; + void *pathitem; + int alignbytes; + int itembytes, itemwords; + int itemsperblock; + long items, maxitems; + int unallocateditems; + int pathitemsleft; + + memorypool(); + memorypool(int, int, int, int); + ~memorypool(); + + void poolinit(int, int, int, int); + void restart(); + void *alloc(); + void dealloc(void*); + void traversalinit(); + void *traverse(); + }; + +//============================================================================// +// // +// badface // +// // +// Despite of its name, a 'badface' can be used to represent one of the // +// following objects: // +// - a face of a tetrahedron which is (possibly) non-Delaunay; // +// - an encroached subsegment or subface; // +// - a bad-quality tetrahedron, i.e, has too large radius-edge ratio; // +// - a sliver, i.e., has good radius-edge ratio but nearly zero volume; // +// - a recently flipped face (saved for undoing the flip later). // +// // +//============================================================================// + + class badface { + public: + triface tt; + face ss; + REAL key, cent[6]; // circumcenter or cos(dihedral angles) at 6 edges. + point forg, fdest, fapex, foppo, noppo; + badface *nextitem; + badface() : key(0), forg(0), fdest(0), fapex(0), foppo(0), noppo(0), + nextitem(0) {} + void init() { + key = 0.; + for (int k = 0; k < 6; k++) cent[k] = 0.; + tt.tet = NULL; tt.ver = 0; + ss.sh = NULL; ss.shver = 0; + forg = fdest = fapex = foppo = noppo = NULL; + nextitem = NULL; + } + }; + +//============================================================================// +// // +// insertvertexflags // +// // +// A collection of flags that pass to the routine insertvertex(). // +// // +//============================================================================// + + class insertvertexflags { + + public: + + int iloc; // input/output. + int bowywat, lawson; + int splitbdflag, validflag, respectbdflag; + int rejflag, chkencflag, cdtflag; + int assignmeshsize; + int sloc, sbowywat; + + // Used by Delaunay refinement. + int collect_inial_cavity_flag; + int ignore_near_vertex; + int check_insert_radius; + int refineflag; // 0, 1, 2, 3 + triface refinetet; + face refinesh; + int smlenflag; // for useinsertradius. + REAL smlen; // for useinsertradius. + point parentpt; + + void init() { + iloc = bowywat = lawson = 0; + splitbdflag = validflag = respectbdflag = 0; + rejflag = chkencflag = cdtflag = 0; + assignmeshsize = 0; + sloc = sbowywat = 0; + + collect_inial_cavity_flag = 0; + ignore_near_vertex = 0; + check_insert_radius = 0; + refineflag = 0; + refinetet.tet = NULL; + refinesh.sh = NULL; + smlenflag = 0; + smlen = 0.0; + parentpt = NULL; + } + + insertvertexflags() { + init(); + } + }; + +//============================================================================// +// // +// flipconstraints // +// // +// A structure of a collection of data (options and parameters) which pass // +// to the edge flip function flipnm(). // +// // +//============================================================================// + + class flipconstraints { + + public: + + // Elementary flip flags. + int enqflag; // (= flipflag) + int chkencflag; + + // Control flags + int unflip; // Undo the performed flips. + int collectnewtets; // Collect the new tets created by flips. + int collectencsegflag; + + // Optimization flags. + int noflip_in_surface; // do not flip edges (not segment) in surface. + int remove_ndelaunay_edge; // Remove a non-Delaunay edge. + REAL bak_tetprism_vol; // The value to be minimized. + REAL tetprism_vol_sum; + int remove_large_angle; // Remove a large dihedral angle at edge. + REAL cosdihed_in; // The input cosine of the dihedral angle (> 0). + REAL cosdihed_out; // The improved cosine of the dihedral angle. + REAL max_asp_out; // Max asp ratio after the improvement of dihedral angle. + + // Boundary recovery flags. + int checkflipeligibility; + point seg[2]; // A constraining edge to be recovered. + point fac[3]; // A constraining face to be recovered. + point remvert; // A vertex to be removed. + + + flipconstraints() { + enqflag = 0; + chkencflag = 0; + + unflip = 0; + collectnewtets = 0; + collectencsegflag = 0; + + noflip_in_surface = 0; + remove_ndelaunay_edge = 0; + bak_tetprism_vol = 0.0; + tetprism_vol_sum = 0.0; + remove_large_angle = 0; + cosdihed_in = 0.0; + cosdihed_out = 0.0; + max_asp_out = 0.0; + + checkflipeligibility = 0; + seg[0] = NULL; + fac[0] = NULL; + remvert = NULL; + } + }; + +//============================================================================// +// // +// optparameters // +// // +// Optimization options and parameters. // +// // +//============================================================================// + + class optparameters { + + public: + + // The one of goals of optimization. + int max_min_volume; // Maximize the minimum volume. + int min_max_aspectratio; // Minimize the maximum aspect ratio. + int min_max_dihedangle; // Minimize the maximum dihedral angle. + + // The initial and improved value. + REAL initval, imprval; + + int numofsearchdirs; + REAL searchstep; + int maxiter; // Maximum smoothing iterations (disabled by -1). + int smthiter; // Performed iterations. + + + optparameters() { + max_min_volume = 0; + min_max_aspectratio = 0; + min_max_dihedangle = 0; + + initval = imprval = 0.0; + + numofsearchdirs = 10; + searchstep = 0.01; + maxiter = -1; // Unlimited smoothing iterations. + smthiter = 0; + + } + }; + + +//============================================================================// +// // +// Labels (enumeration declarations) used by TetGen. // +// // +//============================================================================// + + // Labels that signify the type of a vertex. + enum verttype {UNUSEDVERTEX, DUPLICATEDVERTEX, RIDGEVERTEX, /*ACUTEVERTEX,*/ + FACETVERTEX, VOLVERTEX, FREESEGVERTEX, FREEFACETVERTEX, + FREEVOLVERTEX, NREGULARVERTEX, DEADVERTEX}; + + // Labels that signify the result of triangle-triangle intersection test. + enum interresult {DISJOINT, INTERSECT, SHAREVERT, SHAREEDGE, SHAREFACE, + TOUCHEDGE, TOUCHFACE, ACROSSVERT, ACROSSEDGE, ACROSSFACE, + SELF_INTERSECT}; + + // Labels that signify the result of point location. + enum locateresult {UNKNOWN, OUTSIDE, INTETRAHEDRON, ONFACE, ONEDGE, ONVERTEX, + ENCVERTEX, ENCSEGMENT, ENCSUBFACE, NEARVERTEX, NONREGULAR, + INSTAR, BADELEMENT, NULLCAVITY, SHARPCORNER, FENSEDIN, + NONCOPLANAR, SELF_ENCROACH}; + +//============================================================================// +// // +// Variables of TetGen // +// // +//============================================================================// + + // Pointer to the input data (a set of nodes, a PLC, or a mesh). + tetgenio *in, *addin; + + // Pointer to the switches and parameters. + tetgenbehavior *b; + + // Pointer to a background mesh (contains size specification map). + tetgenmesh *bgm; + + // Memorypools to store mesh elements (points, tetrahedra, subfaces, and + // segments) and extra pointers between tetrahedra, subfaces, and segments. + memorypool *tetrahedrons, *subfaces, *subsegs, *points; + memorypool *tet2subpool, *tet2segpool; + + // Memorypools to store bad-quality (or encroached) elements. + memorypool *badtetrahedrons, *badsubfacs, *badsubsegs; + memorypool *split_subfaces_pool, *split_segments_pool; + arraypool *unsplit_badtets, *unsplit_subfaces, *unsplit_segments; + arraypool *check_tets_list; + + badface *stack_enc_segments, *stack_enc_subfaces; + + // Bad quality subfaces are ordered by priority queues. + badface *queuefront[64]; + badface *queuetail[64]; + int nextnonemptyq[64]; + int firstnonemptyq, recentq; + + // Bad quality tetrahedra are ordered by priority queues. + memorypool *badqual_tets_pool; + badface *bt_queuefront[64]; + badface *bt_queuetail[64]; + int bt_nextnonemptyq[64]; + int bt_firstnonemptyq, bt_recentq; + + // A memorypool to store faces to be flipped. + memorypool *flippool; + arraypool *later_unflip_queue, *unflipqueue; + badface *flipstack, *unflip_queue_front, *unflip_queue_tail; + + // Arrays used for point insertion (the Bowyer-Watson algorithm). + arraypool *cavetetlist, *cavebdrylist, *caveoldtetlist; + arraypool *cave_oldtet_list; // only tetrahedron's + arraypool *cavetetshlist, *cavetetseglist, *cavetetvertlist; + arraypool *caveencshlist, *caveencseglist; + arraypool *caveshlist, *caveshbdlist, *cavesegshlist; + triface _bw_faces[4096]; // _bw_faces[64][64]; + + // Stacks used for CDT construction and boundary recovery. + arraypool *subsegstack, *subfacstack, *subvertstack; + arraypool *skipped_segment_list, *skipped_facet_list; + + // Arrays of encroached segments and subfaces (for mesh refinement). + arraypool *encseglist, *encshlist; + + // The map between facets to their vertices (for mesh refinement). + int number_of_facets; + int *idx2facetlist; + point *facetverticeslist; + int *idx_segment_facet_list; // segment-to-facet map. + int *segment_facet_list; + int *idx_ridge_vertex_facet_list; // vertex-to-facet map. + int *ridge_vertex_facet_list; + + // The map between segments to their endpoints (for mesh refinement). + int segmentendpointslist_length; + point *segmentendpointslist; + double *segment_info_list; + int *idx_segment_ridge_vertex_list; // are two ridge vertices form a segment? + point *segment_ridge_vertex_list; + + // The infinite vertex. + point dummypoint; + // The recently visited tetrahedron, subface. + triface recenttet; + face recentsh; + + // PI is the ratio of a circle's circumference to its diameter. + static REAL PI; + + // The list of subdomains. (-A option). + int subdomains; // Number of subdomains. + int *subdomain_markers; + + // Various variables. + int numpointattrib; // Number of point attributes. + int numelemattrib; // Number of tetrahedron attributes. + int sizeoftensor; // Number of REALs per metric tensor. + int pointmtrindex; // Index to find the metric tensor of a point. + int pointparamindex; // Index to find the u,v coordinates of a point. + int point2simindex; // Index to find a simplex adjacent to a point. + int pointmarkindex; // Index to find boundary marker of a point. + int pointinsradiusindex; // Index to find the insertion radius of a point. + int elemattribindex; // Index to find attributes of a tetrahedron. + int polarindex; // Index to find the polar plane parameters. + int volumeboundindex; // Index to find volume bound of a tetrahedron. + int elemmarkerindex; // Index to find marker of a tetrahedron. + int shmarkindex; // Index to find boundary marker of a subface. + int areaboundindex; // Index to find area bound of a subface. + int checksubsegflag; // Are there segments in the tetrahedralization yet? + int checksubfaceflag; // Are there subfaces in the tetrahedralization yet? + int boundary_recovery_flag; + int checkconstraints; // Are there variant (node, seg, facet) constraints? + int nonconvex; // Is current mesh non-convex? + int autofliplinklevel; // The increase of link levels, default is 1. + int useinsertradius; // Save the insertion radius for Steiner points. + long samples; // Number of random samples for point location. + unsigned long randomseed; // Current random number seed. + REAL cosmaxdihed, cosmindihed; // The cosine values of max/min dihedral. + REAL cossmtdihed; // The cosine value of a bad dihedral to be smoothed. + REAL cosslidihed; // The cosine value of the max dihedral of a sliver. + REAL cos_large_dihed; // The cosine value of large dihedral (135 degree). + REAL opt_max_sliver_asp_ratio; // = 10 x b->opt_max_asp_ratio. + REAL minfaceang, minfacetdihed; // The minimum input (dihedral) angles. + REAL cos_facet_separate_ang_tol; + REAL cos_collinear_ang_tol; + REAL tetprism_vol_sum; // The total volume of tetrahedral-prisms (in 4D). + REAL longest; // The longest possible edge length. + REAL minedgelength; // = longest * b->epsion. + REAL xmax, xmin, ymax, ymin, zmax, zmin; // Bounding box of points. + + // Options for mesh refinement. + REAL big_radius_edge_ratio; // calculated by qualitystatistics(). + REAL smallest_insradius; // Save the smallest insertion radius. + long elem_limit; + long insert_point_count; // number of attempted insertions. + long report_refine_progress; // the next report event. + long last_point_count; // number of points after last report event. + long last_insertion_count; // number of insertions after last report event. + + // Counters. + long insegments; // Number of input segments. + long hullsize; // Number of exterior boundary faces. + long meshedges; // Number of mesh edges. + long meshhulledges; // Number of boundary mesh edges. + long steinerleft; // Number of Steiner points not yet used. + long dupverts; // Are there duplicated vertices? + long unuverts; // Are there unused vertices? + long duplicated_facets_count; // Are there duplicated facets.? + long nonregularcount; // Are there non-regular vertices? + long st_segref_count, st_facref_count, st_volref_count; // Steiner points. + long fillregioncount, cavitycount, cavityexpcount; + long flip14count, flip26count, flipn2ncount; + long flip23count, flip32count, flip44count, flip41count; + long flip31count, flip22count; + long opt_flips_count, opt_collapse_count, opt_smooth_count; + long recover_delaunay_count; + unsigned long totalworkmemory; // Total memory used by working arrays. - insertvertexflags() { init(); } - }; - - //============================================================================// - // // - // flipconstraints // - // // - // A structure of a collection of data (options and parameters) which pass // - // to the edge flip function flipnm(). // - // // - //============================================================================// - - class flipconstraints - { - - public: - // Elementary flip flags. - int enqflag; // (= flipflag) - int chkencflag; - - // Control flags - int unflip; // Undo the performed flips. - int collectnewtets; // Collect the new tets created by flips. - int collectencsegflag; - - // Optimization flags. - int noflip_in_surface; // do not flip edges (not segment) in surface. - int remove_ndelaunay_edge; // Remove a non-Delaunay edge. - REAL bak_tetprism_vol; // The value to be minimized. - REAL tetprism_vol_sum; - int remove_large_angle; // Remove a large dihedral angle at edge. - REAL cosdihed_in; // The input cosine of the dihedral angle (> 0). - REAL cosdihed_out; // The improved cosine of the dihedral angle. - REAL max_asp_out; // Max asp ratio after the improvement of dihedral angle. - - // Boundary recovery flags. - int checkflipeligibility; - point seg[2]; // A constraining edge to be recovered. - point fac[3]; // A constraining face to be recovered. - point remvert; // A vertex to be removed. - - flipconstraints() - { - enqflag = 0; - chkencflag = 0; - - unflip = 0; - collectnewtets = 0; - collectencsegflag = 0; - - noflip_in_surface = 0; - remove_ndelaunay_edge = 0; - bak_tetprism_vol = 0.0; - tetprism_vol_sum = 0.0; - remove_large_angle = 0; - cosdihed_in = 0.0; - cosdihed_out = 0.0; - max_asp_out = 0.0; - - checkflipeligibility = 0; - seg[0] = NULL; - fac[0] = NULL; - remvert = NULL; - } - }; - - //============================================================================// - // // - // optparameters // - // // - // Optimization options and parameters. // - // // - //============================================================================// - - class optparameters - { - - public: - // The one of goals of optimization. - int max_min_volume; // Maximize the minimum volume. - int min_max_aspectratio; // Minimize the maximum aspect ratio. - int min_max_dihedangle; // Minimize the maximum dihedral angle. - - // The initial and improved value. - REAL initval, imprval; - - int numofsearchdirs; - REAL searchstep; - int maxiter; // Maximum smoothing iterations (disabled by -1). - int smthiter; // Performed iterations. - - optparameters() - { - max_min_volume = 0; - min_max_aspectratio = 0; - min_max_dihedangle = 0; - - initval = imprval = 0.0; - - numofsearchdirs = 10; - searchstep = 0.01; - maxiter = -1; // Unlimited smoothing iterations. - smthiter = 0; - } - }; - - //============================================================================// - // // - // Labels (enumeration declarations) used by TetGen. // - // // - //============================================================================// - - // Labels that signify the type of a vertex. - enum verttype { - UNUSEDVERTEX, - DUPLICATEDVERTEX, - RIDGEVERTEX, /*ACUTEVERTEX,*/ - FACETVERTEX, - VOLVERTEX, - FREESEGVERTEX, - FREEFACETVERTEX, - FREEVOLVERTEX, - NREGULARVERTEX, - DEADVERTEX - }; - - // Labels that signify the result of triangle-triangle intersection test. - enum interresult { - DISJOINT, - INTERSECT, - SHAREVERT, - SHAREEDGE, - SHAREFACE, - TOUCHEDGE, - TOUCHFACE, - ACROSSVERT, - ACROSSEDGE, - ACROSSFACE, - SELF_INTERSECT - }; - - // Labels that signify the result of point location. - enum locateresult { - UNKNOWN, - OUTSIDE, - INTETRAHEDRON, - ONFACE, - ONEDGE, - ONVERTEX, - ENCVERTEX, - ENCSEGMENT, - ENCSUBFACE, - NEARVERTEX, - NONREGULAR, - INSTAR, - BADELEMENT, - NULLCAVITY, - SHARPCORNER, - FENSEDIN, - NONCOPLANAR, - SELF_ENCROACH - }; - - //============================================================================// - // // - // Variables of TetGen // - // // - //============================================================================// - - // Pointer to the input data (a set of nodes, a PLC, or a mesh). - tetgenio *in, *addin; - - // Pointer to the switches and parameters. - tetgenbehavior* b; - - // Pointer to a background mesh (contains size specification map). - tetgenmesh* bgm; - - // Memorypools to store mesh elements (points, tetrahedra, subfaces, and - // segments) and extra pointers between tetrahedra, subfaces, and segments. - memorypool *tetrahedrons, *subfaces, *subsegs, *points; - memorypool *tet2subpool, *tet2segpool; - - // Memorypools to store bad-quality (or encroached) elements. - memorypool *badtetrahedrons, *badsubfacs, *badsubsegs; - memorypool *split_subfaces_pool, *split_segments_pool; - arraypool *unsplit_badtets, *unsplit_subfaces, *unsplit_segments; - arraypool* check_tets_list; - - badface *stack_enc_segments, *stack_enc_subfaces; - - // Bad quality subfaces are ordered by priority queues. - badface* queuefront[64]; - badface* queuetail[64]; - int nextnonemptyq[64]; - int firstnonemptyq, recentq; - - // Bad quality tetrahedra are ordered by priority queues. - memorypool* badqual_tets_pool; - badface* bt_queuefront[64]; - badface* bt_queuetail[64]; - int bt_nextnonemptyq[64]; - int bt_firstnonemptyq, bt_recentq; - - // A memorypool to store faces to be flipped. - memorypool* flippool; - arraypool *later_unflip_queue, *unflipqueue; - badface *flipstack, *unflip_queue_front, *unflip_queue_tail; - - // Arrays used for point insertion (the Bowyer-Watson algorithm). - arraypool *cavetetlist, *cavebdrylist, *caveoldtetlist; - arraypool* cave_oldtet_list; // only tetrahedron's - arraypool *cavetetshlist, *cavetetseglist, *cavetetvertlist; - arraypool *caveencshlist, *caveencseglist; - arraypool *caveshlist, *caveshbdlist, *cavesegshlist; - triface _bw_faces[4096]; // _bw_faces[64][64]; - - // Stacks used for CDT construction and boundary recovery. - arraypool *subsegstack, *subfacstack, *subvertstack; - arraypool *skipped_segment_list, *skipped_facet_list; - - // Arrays of encroached segments and subfaces (for mesh refinement). - arraypool *encseglist, *encshlist; - - // The map between facets to their vertices (for mesh refinement). - int number_of_facets; - int* idx2facetlist; - point* facetverticeslist; - int* idx_segment_facet_list; // segment-to-facet map. - int* segment_facet_list; - int* idx_ridge_vertex_facet_list; // vertex-to-facet map. - int* ridge_vertex_facet_list; - - // The map between segments to their endpoints (for mesh refinement). - int segmentendpointslist_length; - point* segmentendpointslist; - double* segment_info_list; - int* idx_segment_ridge_vertex_list; // are two ridge vertices form a segment? - point* segment_ridge_vertex_list; - - // The infinite vertex. - point dummypoint; - // The recently visited tetrahedron, subface. - triface recenttet; - face recentsh; - - // PI is the ratio of a circle's circumference to its diameter. - static REAL PI; - - // The list of subdomains. (-A option). - int subdomains; // Number of subdomains. - int* subdomain_markers; - - // Various variables. - int numpointattrib; // Number of point attributes. - int numelemattrib; // Number of tetrahedron attributes. - int sizeoftensor; // Number of REALs per metric tensor. - int pointmtrindex; // Index to find the metric tensor of a point. - int pointparamindex; // Index to find the u,v coordinates of a point. - int point2simindex; // Index to find a simplex adjacent to a point. - int pointmarkindex; // Index to find boundary marker of a point. - int pointinsradiusindex; // Index to find the insertion radius of a point. - int elemattribindex; // Index to find attributes of a tetrahedron. - int polarindex; // Index to find the polar plane parameters. - int volumeboundindex; // Index to find volume bound of a tetrahedron. - int elemmarkerindex; // Index to find marker of a tetrahedron. - int shmarkindex; // Index to find boundary marker of a subface. - int areaboundindex; // Index to find area bound of a subface. - int checksubsegflag; // Are there segments in the tetrahedralization yet? - int checksubfaceflag; // Are there subfaces in the tetrahedralization yet? - int boundary_recovery_flag; - int checkconstraints; // Are there variant (node, seg, facet) constraints? - int nonconvex; // Is current mesh non-convex? - int autofliplinklevel; // The increase of link levels, default is 1. - int useinsertradius; // Save the insertion radius for Steiner points. - long samples; // Number of random samples for point location. - unsigned long randomseed; // Current random number seed. - REAL cosmaxdihed, cosmindihed; // The cosine values of max/min dihedral. - REAL cossmtdihed; // The cosine value of a bad dihedral to be smoothed. - REAL cosslidihed; // The cosine value of the max dihedral of a sliver. - REAL cos_large_dihed; // The cosine value of large dihedral (135 degree). - REAL opt_max_sliver_asp_ratio; // = 10 x b->opt_max_asp_ratio. - REAL minfaceang, minfacetdihed; // The minimum input (dihedral) angles. - REAL cos_facet_separate_ang_tol; - REAL cos_collinear_ang_tol; - REAL tetprism_vol_sum; // The total volume of tetrahedral-prisms (in 4D). - REAL longest; // The longest possible edge length. - REAL minedgelength; // = longest * b->epsion. - REAL xmax, xmin, ymax, ymin, zmax, zmin; // Bounding box of points. - - // Options for mesh refinement. - REAL big_radius_edge_ratio; // calculated by qualitystatistics(). - REAL smallest_insradius; // Save the smallest insertion radius. - long elem_limit; - long insert_point_count; // number of attempted insertions. - long report_refine_progress; // the next report event. - long last_point_count; // number of points after last report event. - long last_insertion_count; // number of insertions after last report event. - - // Counters. - long insegments; // Number of input segments. - long hullsize; // Number of exterior boundary faces. - long meshedges; // Number of mesh edges. - long meshhulledges; // Number of boundary mesh edges. - long steinerleft; // Number of Steiner points not yet used. - long dupverts; // Are there duplicated vertices? - long unuverts; // Are there unused vertices? - long duplicated_facets_count; // Are there duplicated facets.? - long nonregularcount; // Are there non-regular vertices? - long st_segref_count, st_facref_count, st_volref_count; // Steiner points. - long fillregioncount, cavitycount, cavityexpcount; - long flip14count, flip26count, flipn2ncount; - long flip23count, flip32count, flip44count, flip41count; - long flip31count, flip22count; - long opt_flips_count, opt_collapse_count, opt_smooth_count; - long recover_delaunay_count; - unsigned long totalworkmemory; // Total memory used by working arrays. - - //============================================================================// - // // - // Mesh manipulation primitives // - // // - //============================================================================// - - // Fast lookup tables for mesh manipulation primitives. - static int bondtbl[12][12], fsymtbl[12][12]; - static int esymtbl[12], enexttbl[12], eprevtbl[12]; - static int enextesymtbl[12], eprevesymtbl[12]; - static int eorgoppotbl[12], edestoppotbl[12]; - static int facepivot1[12], facepivot2[12][12]; - static int orgpivot[12], destpivot[12], apexpivot[12], oppopivot[12]; - static int tsbondtbl[12][6], stbondtbl[12][6]; - static int tspivottbl[12][6], stpivottbl[12][6]; - static int ver2edge[12], edge2ver[6], epivot[12]; - static int sorgpivot[6], sdestpivot[6], sapexpivot[6]; - static int snextpivot[6]; - - void inittables(); - - // Primitives for tetrahedra. - inline tetrahedron encode(triface& t); - inline tetrahedron encode2(tetrahedron* ptr, int ver); - inline void decode(tetrahedron ptr, triface& t); - inline tetrahedron* decode_tet_only(tetrahedron ptr); - inline int decode_ver_only(tetrahedron ptr); - inline void bond(triface& t1, triface& t2); - inline void dissolve(triface& t); - inline void esym(triface& t1, triface& t2); - inline void esymself(triface& t); - inline void enext(triface& t1, triface& t2); - inline void enextself(triface& t); - inline void eprev(triface& t1, triface& t2); - inline void eprevself(triface& t); - inline void enextesym(triface& t1, triface& t2); - inline void enextesymself(triface& t); - inline void eprevesym(triface& t1, triface& t2); - inline void eprevesymself(triface& t); - inline void eorgoppo(triface& t1, triface& t2); - inline void eorgoppoself(triface& t); - inline void edestoppo(triface& t1, triface& t2); - inline void edestoppoself(triface& t); - inline void fsym(triface& t1, triface& t2); - inline void fsymself(triface& t); - inline void fnext(triface& t1, triface& t2); - inline void fnextself(triface& t); - inline point org(triface& t); - inline point dest(triface& t); - inline point apex(triface& t); - inline point oppo(triface& t); - inline void setorg(triface& t, point p); - inline void setdest(triface& t, point p); - inline void setapex(triface& t, point p); - inline void setoppo(triface& t, point p); - inline REAL elemattribute(tetrahedron* ptr, int attnum); - inline void setelemattribute(tetrahedron* ptr, int attnum, REAL value); - inline REAL* get_polar(tetrahedron* ptr); - inline REAL get_volume(tetrahedron* ptr); - inline REAL volumebound(tetrahedron* ptr); - inline void setvolumebound(tetrahedron* ptr, REAL value); - inline int elemindex(tetrahedron* ptr); - inline void setelemindex(tetrahedron* ptr, int value); - inline int elemmarker(tetrahedron* ptr); - inline void setelemmarker(tetrahedron* ptr, int value); - inline void infect(triface& t); - inline void uninfect(triface& t); - inline bool infected(triface& t); - inline void marktest(triface& t); - inline void unmarktest(triface& t); - inline bool marktested(triface& t); - inline void markface(triface& t); - inline void unmarkface(triface& t); - inline bool facemarked(triface& t); - inline void markedge(triface& t); - inline void unmarkedge(triface& t); - inline bool edgemarked(triface& t); - inline void marktest2(triface& t); - inline void unmarktest2(triface& t); - inline bool marktest2ed(triface& t); - inline int elemcounter(triface& t); - inline void setelemcounter(triface& t, int value); - inline void increaseelemcounter(triface& t); - inline void decreaseelemcounter(triface& t); - inline bool ishulltet(triface& t); - inline bool isdeadtet(triface& t); - - // Primitives for subfaces and subsegments. - inline void sdecode(shellface sptr, face& s); - inline shellface sencode(face& s); - inline shellface sencode2(shellface* sh, int shver); - inline void spivot(face& s1, face& s2); - inline void spivotself(face& s); - inline void sbond(face& s1, face& s2); - inline void sbond1(face& s1, face& s2); - inline void sdissolve(face& s); - inline point sorg(face& s); - inline point sdest(face& s); - inline point sapex(face& s); - inline void setsorg(face& s, point pointptr); - inline void setsdest(face& s, point pointptr); - inline void setsapex(face& s, point pointptr); - inline void sesym(face& s1, face& s2); - inline void sesymself(face& s); - inline void senext(face& s1, face& s2); - inline void senextself(face& s); - inline void senext2(face& s1, face& s2); - inline void senext2self(face& s); - inline REAL areabound(face& s); - inline void setareabound(face& s, REAL value); - inline int shellmark(face& s); - inline void setshellmark(face& s, int value); - inline void sinfect(face& s); - inline void suninfect(face& s); - inline bool sinfected(face& s); - inline void smarktest(face& s); - inline void sunmarktest(face& s); - inline bool smarktested(face& s); - inline void smarktest2(face& s); - inline void sunmarktest2(face& s); - inline bool smarktest2ed(face& s); - inline void smarktest3(face& s); - inline void sunmarktest3(face& s); - inline bool smarktest3ed(face& s); - inline void setfacetindex(face& f, int value); - inline int getfacetindex(face& f); - inline bool isdeadsh(face& s); - - // Primitives for interacting tetrahedra and subfaces. - inline void tsbond(triface& t, face& s); - inline void tsdissolve(triface& t); - inline void stdissolve(face& s); - inline void tspivot(triface& t, face& s); - inline void stpivot(face& s, triface& t); - - // Primitives for interacting tetrahedra and segments. - inline void tssbond1(triface& t, face& seg); - inline void sstbond1(face& s, triface& t); - inline void tssdissolve1(triface& t); - inline void sstdissolve1(face& s); - inline void tsspivot1(triface& t, face& s); - inline void sstpivot1(face& s, triface& t); - - // Primitives for interacting subfaces and segments. - inline void ssbond(face& s, face& edge); - inline void ssbond1(face& s, face& edge); - inline void ssdissolve(face& s); - inline void sspivot(face& s, face& edge); - - // Primitives for points. - inline int pointmark(point pt); - inline void setpointmark(point pt, int value); - inline enum verttype pointtype(point pt); - inline void setpointtype(point pt, enum verttype value); - inline int pointgeomtag(point pt); - inline void setpointgeomtag(point pt, int value); - inline REAL pointgeomuv(point pt, int i); - inline void setpointgeomuv(point pt, int i, REAL value); - inline void pinfect(point pt); - inline void puninfect(point pt); - inline bool pinfected(point pt); - inline void pmarktest(point pt); - inline void punmarktest(point pt); - inline bool pmarktested(point pt); - inline void pmarktest2(point pt); - inline void punmarktest2(point pt); - inline bool pmarktest2ed(point pt); - inline void pmarktest3(point pt); - inline void punmarktest3(point pt); - inline bool pmarktest3ed(point pt); - inline tetrahedron point2tet(point pt); - inline void setpoint2tet(point pt, tetrahedron value); - inline shellface point2sh(point pt); - inline void setpoint2sh(point pt, shellface value); - inline point point2ppt(point pt); - inline void setpoint2ppt(point pt, point value); - inline tetrahedron point2bgmtet(point pt); - inline void setpoint2bgmtet(point pt, tetrahedron value); - inline void setpointinsradius(point pt, REAL value); - inline REAL getpointinsradius(point pt); - inline bool issteinerpoint(point pt); - - // Advanced primitives. - inline void point2tetorg(point pt, triface& t); - inline void point2shorg(point pa, face& s); - inline point farsorg(face& seg); - inline point farsdest(face& seg); - - //============================================================================// - // // - // Memory managment // - // // - //============================================================================// - - void tetrahedrondealloc(tetrahedron*); - tetrahedron* tetrahedrontraverse(); - tetrahedron* alltetrahedrontraverse(); - void shellfacedealloc(memorypool*, shellface*); - shellface* shellfacetraverse(memorypool*); - void pointdealloc(point); - point pointtraverse(); - - void makeindex2pointmap(point*&); - void makepoint2submap(memorypool*, int*&, face*&); - void maketetrahedron(triface*); - void maketetrahedron2(triface*, point, point, point, point); - void makeshellface(memorypool*, face*); - void makepoint(point*, enum verttype); - - void initializepools(); - - //============================================================================// - // // - // Advanced geometric predicates and calculations // - // // - // the routine insphere_s() implements a simplified symbolic perturbation // - // scheme from Edelsbrunner, et al [*]. Hence the point-in-sphere test never // - // returns a zero. The idea is to perturb the weights of vertices in 4D. // - // // - // The routine tri_edge_test() determines whether or not a triangle and an // - // edge intersect in 3D. If they do cross, their intersection type is also // - // reported. This test is a combination of n 3D orientation tests (3 < n < 9).// - // It uses the robust orient3d() test to make the branch decisions. // - // // - // There are several routines to calculate geometrical quantities, e.g., // - // circumcenters, angles, dihedral angles, face normals, face areas, etc. // - // They are implemented using floating-point arithmetics. // - // // - //============================================================================// - - // Symbolic perturbations (robust) - REAL insphere_s(REAL*, REAL*, REAL*, REAL*, REAL*); - REAL orient4d_s(REAL*, REAL*, REAL*, REAL*, REAL*, REAL, REAL, REAL, REAL, REAL); - - // An embedded 2-dimensional geometric predicate (non-robust) - REAL incircle3d(point pa, point pb, point pc, point pd); - - // Triangle-edge intersection test (robust) - int tri_edge_2d(point, point, point, point, point, point, int, int*, int*); - int tri_edge_tail(point, point, point, point, point, point, REAL, REAL, int, int*, int*); - int tri_edge_test(point, point, point, point, point, point, int, int*, int*); + +//============================================================================// +// // +// Mesh manipulation primitives // +// // +//============================================================================// + + // Fast lookup tables for mesh manipulation primitives. + static int bondtbl[12][12], fsymtbl[12][12]; + static int esymtbl[12], enexttbl[12], eprevtbl[12]; + static int enextesymtbl[12], eprevesymtbl[12]; + static int eorgoppotbl[12], edestoppotbl[12]; + static int facepivot1[12], facepivot2[12][12]; + static int orgpivot[12], destpivot[12], apexpivot[12], oppopivot[12]; + static int tsbondtbl[12][6], stbondtbl[12][6]; + static int tspivottbl[12][6], stpivottbl[12][6]; + static int ver2edge[12], edge2ver[6], epivot[12]; + static int sorgpivot [6], sdestpivot[6], sapexpivot[6]; + static int snextpivot[6]; + + void inittables(); + + // Primitives for tetrahedra. + inline tetrahedron encode(triface& t); + inline tetrahedron encode2(tetrahedron* ptr, int ver); + inline void decode(tetrahedron ptr, triface& t); + inline tetrahedron* decode_tet_only(tetrahedron ptr); + inline int decode_ver_only(tetrahedron ptr); + inline void bond(triface& t1, triface& t2); + inline void dissolve(triface& t); + inline void esym(triface& t1, triface& t2); + inline void esymself(triface& t); + inline void enext(triface& t1, triface& t2); + inline void enextself(triface& t); + inline void eprev(triface& t1, triface& t2); + inline void eprevself(triface& t); + inline void enextesym(triface& t1, triface& t2); + inline void enextesymself(triface& t); + inline void eprevesym(triface& t1, triface& t2); + inline void eprevesymself(triface& t); + inline void eorgoppo(triface& t1, triface& t2); + inline void eorgoppoself(triface& t); + inline void edestoppo(triface& t1, triface& t2); + inline void edestoppoself(triface& t); + inline void fsym(triface& t1, triface& t2); + inline void fsymself(triface& t); + inline void fnext(triface& t1, triface& t2); + inline void fnextself(triface& t); + inline point org (triface& t); + inline point dest(triface& t); + inline point apex(triface& t); + inline point oppo(triface& t); + inline void setorg (triface& t, point p); + inline void setdest(triface& t, point p); + inline void setapex(triface& t, point p); + inline void setoppo(triface& t, point p); + inline REAL elemattribute(tetrahedron* ptr, int attnum); + inline void setelemattribute(tetrahedron* ptr, int attnum, REAL value); + inline REAL* get_polar(tetrahedron* ptr); + inline REAL get_volume(tetrahedron* ptr); + inline REAL volumebound(tetrahedron* ptr); + inline void setvolumebound(tetrahedron* ptr, REAL value); + inline int elemindex(tetrahedron* ptr); + inline void setelemindex(tetrahedron* ptr, int value); + inline int elemmarker(tetrahedron* ptr); + inline void setelemmarker(tetrahedron* ptr, int value); + inline void infect(triface& t); + inline void uninfect(triface& t); + inline bool infected(triface& t); + inline void marktest(triface& t); + inline void unmarktest(triface& t); + inline bool marktested(triface& t); + inline void markface(triface& t); + inline void unmarkface(triface& t); + inline bool facemarked(triface& t); + inline void markedge(triface& t); + inline void unmarkedge(triface& t); + inline bool edgemarked(triface& t); + inline void marktest2(triface& t); + inline void unmarktest2(triface& t); + inline bool marktest2ed(triface& t); + inline int elemcounter(triface& t); + inline void setelemcounter(triface& t, int value); + inline void increaseelemcounter(triface& t); + inline void decreaseelemcounter(triface& t); + inline bool ishulltet(triface& t); + inline bool isdeadtet(triface& t); + + // Primitives for subfaces and subsegments. + inline void sdecode(shellface sptr, face& s); + inline shellface sencode(face& s); + inline shellface sencode2(shellface *sh, int shver); + inline void spivot(face& s1, face& s2); + inline void spivotself(face& s); + inline void sbond(face& s1, face& s2); + inline void sbond1(face& s1, face& s2); + inline void sdissolve(face& s); + inline point sorg(face& s); + inline point sdest(face& s); + inline point sapex(face& s); + inline void setsorg(face& s, point pointptr); + inline void setsdest(face& s, point pointptr); + inline void setsapex(face& s, point pointptr); + inline void sesym(face& s1, face& s2); + inline void sesymself(face& s); + inline void senext(face& s1, face& s2); + inline void senextself(face& s); + inline void senext2(face& s1, face& s2); + inline void senext2self(face& s); + inline REAL areabound(face& s); + inline void setareabound(face& s, REAL value); + inline int shellmark(face& s); + inline void setshellmark(face& s, int value); + inline void sinfect(face& s); + inline void suninfect(face& s); + inline bool sinfected(face& s); + inline void smarktest(face& s); + inline void sunmarktest(face& s); + inline bool smarktested(face& s); + inline void smarktest2(face& s); + inline void sunmarktest2(face& s); + inline bool smarktest2ed(face& s); + inline void smarktest3(face& s); + inline void sunmarktest3(face& s); + inline bool smarktest3ed(face& s); + inline void setfacetindex(face& f, int value); + inline int getfacetindex(face& f); + inline bool isdeadsh(face& s); + + // Primitives for interacting tetrahedra and subfaces. + inline void tsbond(triface& t, face& s); + inline void tsdissolve(triface& t); + inline void stdissolve(face& s); + inline void tspivot(triface& t, face& s); + inline void stpivot(face& s, triface& t); + + // Primitives for interacting tetrahedra and segments. + inline void tssbond1(triface& t, face& seg); + inline void sstbond1(face& s, triface& t); + inline void tssdissolve1(triface& t); + inline void sstdissolve1(face& s); + inline void tsspivot1(triface& t, face& s); + inline void sstpivot1(face& s, triface& t); + + // Primitives for interacting subfaces and segments. + inline void ssbond(face& s, face& edge); + inline void ssbond1(face& s, face& edge); + inline void ssdissolve(face& s); + inline void sspivot(face& s, face& edge); + + // Primitives for points. + inline int pointmark(point pt); + inline void setpointmark(point pt, int value); + inline enum verttype pointtype(point pt); + inline void setpointtype(point pt, enum verttype value); + inline int pointgeomtag(point pt); + inline void setpointgeomtag(point pt, int value); + inline REAL pointgeomuv(point pt, int i); + inline void setpointgeomuv(point pt, int i, REAL value); + inline void pinfect(point pt); + inline void puninfect(point pt); + inline bool pinfected(point pt); + inline void pmarktest(point pt); + inline void punmarktest(point pt); + inline bool pmarktested(point pt); + inline void pmarktest2(point pt); + inline void punmarktest2(point pt); + inline bool pmarktest2ed(point pt); + inline void pmarktest3(point pt); + inline void punmarktest3(point pt); + inline bool pmarktest3ed(point pt); + inline tetrahedron point2tet(point pt); + inline void setpoint2tet(point pt, tetrahedron value); + inline shellface point2sh(point pt); + inline void setpoint2sh(point pt, shellface value); + inline point point2ppt(point pt); + inline void setpoint2ppt(point pt, point value); + inline tetrahedron point2bgmtet(point pt); + inline void setpoint2bgmtet(point pt, tetrahedron value); + inline void setpointinsradius(point pt, REAL value); + inline REAL getpointinsradius(point pt); + inline bool issteinerpoint(point pt); + + // Advanced primitives. + inline void point2tetorg(point pt, triface& t); + inline void point2shorg(point pa, face& s); + inline point farsorg(face& seg); + inline point farsdest(face& seg); + +//============================================================================// +// // +// Memory managment // +// // +//============================================================================// + + void tetrahedrondealloc(tetrahedron*); + tetrahedron *tetrahedrontraverse(); + tetrahedron *alltetrahedrontraverse(); + void shellfacedealloc(memorypool*, shellface*); + shellface *shellfacetraverse(memorypool*); + void pointdealloc(point); + point pointtraverse(); + + void makeindex2pointmap(point*&); + void makepoint2submap(memorypool*, int*&, face*&); + void maketetrahedron(triface*); + void maketetrahedron2(triface*, point, point, point, point); + void makeshellface(memorypool*, face*); + void makepoint(point*, enum verttype); + + void initializepools(); + +//============================================================================// +// // +// Advanced geometric predicates and calculations // +// // +// the routine insphere_s() implements a simplified symbolic perturbation // +// scheme from Edelsbrunner, et al [*]. Hence the point-in-sphere test never // +// returns a zero. The idea is to perturb the weights of vertices in 4D. // +// // +// The routine tri_edge_test() determines whether or not a triangle and an // +// edge intersect in 3D. If they do cross, their intersection type is also // +// reported. This test is a combination of n 3D orientation tests (3 < n < 9).// +// It uses the robust orient3d() test to make the branch decisions. // +// // +// There are several routines to calculate geometrical quantities, e.g., // +// circumcenters, angles, dihedral angles, face normals, face areas, etc. // +// They are implemented using floating-point arithmetics. // +// // +//============================================================================// + + // Symbolic perturbations (robust) + REAL insphere_s(REAL*, REAL*, REAL*, REAL*, REAL*); + REAL orient4d_s(REAL*, REAL*, REAL*, REAL*, REAL*, + REAL, REAL, REAL, REAL, REAL); + + // An embedded 2-dimensional geometric predicate (non-robust) + REAL incircle3d(point pa, point pb, point pc, point pd); + + // Triangle-edge intersection test (robust) + int tri_edge_2d(point, point, point, point, point, point, int, int*, int*); + int tri_edge_tail(point,point,point,point,point,point,REAL,REAL,int,int*,int*); + int tri_edge_test(point, point, point, point, point, point, int, int*, int*); // Triangle-triangle intersection test (robust) - int tri_edge_inter_tail(point, point, point, point, point, REAL, REAL); - int tri_tri_inter(point, point, point, point, point, point); - - // Linear algebra functions - inline REAL dot(REAL* v1, REAL* v2); - inline void cross(REAL* v1, REAL* v2, REAL* n); - bool lu_decmp(REAL lu[4][4], int n, int* ps, REAL* d, int N); - void lu_solve(REAL lu[4][4], int n, int* ps, REAL* b, int N); - - // Geometric calculations (non-robust) - REAL orient3dfast(REAL* pa, REAL* pb, REAL* pc, REAL* pd); - inline REAL norm2(REAL x, REAL y, REAL z); - inline REAL distance(REAL* p1, REAL* p2); - inline REAL distance2(REAL* p1, REAL* p2); - void facenormal(point pa, point pb, point pc, REAL* n, int pivot, REAL* lav); - REAL facedihedral(REAL* pa, REAL* pb, REAL* pc1, REAL* pc2); - REAL triarea(REAL* pa, REAL* pb, REAL* pc); - REAL interiorangle(REAL* o, REAL* p1, REAL* p2, REAL* n); - REAL cos_interiorangle(REAL* o, REAL* p1, REAL* p2); - void projpt2edge(REAL* p, REAL* e1, REAL* e2, REAL* prj); - void projpt2face(REAL* p, REAL* f1, REAL* f2, REAL* f3, REAL* prj); - bool circumsphere(REAL*, REAL*, REAL*, REAL*, REAL* cent, REAL* radius); - bool orthosphere(REAL*, REAL*, REAL*, REAL*, REAL, REAL, REAL, REAL, REAL*, REAL*); - void planelineint(REAL*, REAL*, REAL*, REAL*, REAL*, REAL*, REAL*); - int linelineint(REAL*, REAL*, REAL*, REAL*, REAL*, REAL*, REAL*, REAL*); - REAL tetprismvol(REAL* pa, REAL* pb, REAL* pc, REAL* pd); - bool calculateabovepoint(arraypool*, point*, point*, point*); - void calculateabovepoint4(point, point, point, point); - - //============================================================================// - // // - // Local mesh transformations // - // // - // A local transformation replaces a set of tetrahedra with another set that // - // partitions the same space and boundaries. // - // // - // In 3D, the most straightforward local transformations are the elementary // - // flips performed within the convex hull of five vertices: 2-to-3, 3-to-2, // - // 1-to-4, and 4-to-1 flips. The numbers indicate the number of tetrahedra // - // before and after each flip. The 1-to-4 and 4-to-1 flip involve inserting // - // or deleting a vertex, respectively. // - // // - // There are complex local transformations that are a combination of element- // - // ary flips. For example, a 4-to-4 flip, which replaces two coplanar edges, // - // combines a 2-to-3 flip and a 3-to-2 flip. Note that the first 2-to-3 flip // - // will temporarily create a degenerate tetrahedron removed immediately by // - // the followed 3-to-2 flip. More generally, an n-to-m flip, where n > 3, // - // m = (n - 2) * 2, which removes an edge, can be done by first performing a // - // sequence of (n - 3) 2-to-3 flips followed by a 3-to-2 flip. // - // // - // The routines flip23(), flip32(), and flip41() perform the three elementray // - // flips. The flip14() is available inside the routine insertpoint(). // - // // - // The routines flipnm() and flipnm_post() implement a generalized edge flip // - // algorithm that uses elementary flips. // - // // - // The routine insertpoint() implements the Bowyer-Watson's cavity algorithm // - // to insert a vertex. It works for arbitrary tetrahedralization, either // - // Delaunay, or constrained Delaunay, or non-Delaunay. // - // // - //============================================================================// - - void flippush(badface*&, triface*); - - // The elementary flips. - void flip23(triface*, int, flipconstraints* fc); - void flip32(triface*, int, flipconstraints* fc); - void flip41(triface*, int, flipconstraints* fc); - - // A generalized edge flip. - int flipnm(triface*, int n, int level, int, flipconstraints* fc); - int flipnm_post(triface*, int n, int nn, int, flipconstraints* fc); - - // Point insertion. - int insertpoint(point, triface*, face*, face*, insertvertexflags*); - void insertpoint_abort(face*, insertvertexflags*); - - //============================================================================// - // // - // Delaunay tetrahedralization // - // // - // The routine incrementaldelaunay() implemented two incremental algorithms // - // for constructing Delaunay tetrahedralizations (DTs): the Bowyer-Watson // - // (B-W) algorithm and the incremental flip algorithm of Edelsbrunner and // - // Shah, "Incremental topological flipping works for regular triangulation," // - // Algorithmica, 15:233-241, 1996. // - // // - // The routine incrementalflip() implements the flip algorithm of [Edelsbrun- // - // ner and Shah, 1996]. It flips a queue of locally non-Delaunay faces (in // - // arbitrary order). The success is guaranteed when the Delaunay tetrahedra- // - // lization is constructed incrementally by adding one vertex at a time. // - // // - // The routine locate() finds a tetrahedron contains a new point in current // - // DT. It uses a simple stochastic walk algorithm: starting from an arbitrary // - // tetrahedron in DT, it finds the destination by visit one tetrahedron at a // - // time, randomly chooses a tetrahedron if there are more than one choices. // - // This algorithm terminates due to Edelsbrunner's acyclic theorem. // - // // - // Choose a good starting tetrahedron is crucial to the speed of the walk. // - // TetGen initially uses the "jump-and-walk" algorithm of Muecke, E.P., Saias,// - // I., and Zhu, B. "Fast Randomized Point Location Without Preprocessing." In // - // Proceedings of the 12th ACM Symposium on Computational Geometry, 274-283, // - // 1996. It first randomly samples several tetrahedra in the DT and then // - // choosing the closet one to start walking. // - // // - // The above algorithm slows download dramatically as the number of points // - // grows -- reported in Amenta, N., Choi, S. and Rote, G., "Incremental // - // construction con {BRIO}," In Proceedings of 19th ACM Symposium on Computa- // - // tional Geometry, 211-219, 2003. On the other hand, Liu and Snoeyink showed // - // that the point location could be made in constant time if the points are // - // pre-sorted so that the nearby points in space have nearby indices, then // - // adding the points in this order. They sorted the points along the 3D // - // Hilbert curve. // - // // - // The routine hilbert_sort3() sorts a set of 3D points along the 3D Hilbert // - // curve. It recursively splits a point set according to the Hilbert indices // - // mapped to the subboxes of the bounding box of the point set. The Hilbert // - // indices is calculated by Butz's algorithm in 1971. An excellent exposition // - // of this algorithm can be found in the paper of Hamilton, C., "Compact // - // Hilbert Indices", Technical Report CS-2006-07, Computer Science, Dalhousie // - // University, 2006 (the Section 2). My implementation also referenced Steven // - // Witham's performance of "Hilbert walk" (hopefully, it is still available // - // at http://www.tiac.net/~sw/2008/10/Hilbert/). // - // // - // TetGen sorts the points using the method in the paper of Boissonnat,J.-D., // - // Devillers, O. and Hornus, S. "Incremental Construction of the Delaunay // - // Triangulation and the Delaunay Graph in Medium Dimension," In Proceedings // - // of the 25th ACM Symposium on Computational Geometry, 2009. It first // - // randomly sorts the points into subgroups using the Biased Randomized // - // Insertion Ordering (BRIO) of Amenta et al 2003, then sorts the points in // - // each subgroup along the 3D Hilbert curve. Inserting points in this order // - // ensure a randomized "sprinkling" of the points over the domain, while // - // sorting of each subset provides locality. // - // // - //============================================================================// - - void transfernodes(); - - // Point sorting. - int transgc[8][3][8], tsb1mod3[8]; - void hilbert_init(int n); - int hilbert_split(point* vertexarray, int arraysize, int gc0, int gc1, REAL, REAL, REAL, REAL, REAL, REAL); - void hilbert_sort3(point* vertexarray, int arraysize, int e, int d, REAL, REAL, REAL, REAL, REAL, REAL, int depth); - void brio_multiscale_sort(point*, int, int threshold, REAL ratio, int* depth); - - // Point location. - unsigned long randomnation(unsigned int choices); - void randomsample(point searchpt, triface* searchtet); - enum locateresult locate(point searchpt, triface* searchtet, int chkencflag = 0); - - // Incremental Delaunay construction. - enum locateresult locate_dt(point searchpt, triface* searchtet); - int insert_vertex_bw(point, triface*, insertvertexflags*); - void initialdelaunay(point pa, point pb, point pc, point pd); - void incrementaldelaunay(clock_t&); - - //============================================================================// - // // - // Surface triangulation // - // // - //============================================================================// - - void flipshpush(face*); - void flip22(face*, int, int); - void flip31(face*, int); - long lawsonflip(); - int sinsertvertex(point newpt, face*, face*, int iloc, int bowywat, int); - int sremovevertex(point delpt, face*, face*, int lawson); - - enum locateresult slocate(point, face*, int, int, int); - enum interresult sscoutsegment(face*, point, int, int, int); - void scarveholes(int, REAL*); - int triangulate(int, arraypool*, arraypool*, int, REAL*); - - void unifysegments(); - void identifyinputedges(point*); - void mergefacets(); - void meshsurface(); - - //============================================================================// - // // - // Constrained Delaunay tetrahedralization // - // // - // A constrained Delaunay tetrahedralization (CDT) is a variation of a Delau- // - // nay tetrahedralization (DT) that respects the boundary of a 3D PLC (mesh // - // domain). A crucial difference between a CDT and a DT is that triangles in // - // the PLC's polygons are not required to be locally Delaunay, which frees // - // the CDT to respect the PLC's polygons better. CDTs have optimal properties // - // similar to those of DTs. // - // // - // Steiner Points and Steiner CDTs. It is well-known that even a simple 3D // - // polyhedron may not have a tetrahedralization which only uses its vertices. // - // Some extra points, so-called "Steiner points" are needed to form a tetrah- // - // edralization of such polyhedron. A Steiner CDT of a 3D PLC is a CDT // - // containing Steiner points. TetGen generates Steiner CDTs. // - // // - // The routine constraineddelaunay() creates a (Steiner) CDT of the PLC // - // (including Steiner points). It has two steps, (1) segment recovery and (2) // - // facet (polygon) recovery. // - // // - // The routine delaunizesegments() implements the segment recovery algorithm // - // of Si, H., and Gaertner, K. "Meshing Piecewise Linear Complexes by // - // Constrained Delaunay Tetrahedralizations," In Proceedings of the 14th // - // International Meshing Roundtable, 147--163, 2005. It adds Steiner points // - // into non-Delaunay segments until all subsegments appear together in a DT. // - // The running time of this algorithm is proportional to the number of // - // Steiner points. // - // // - // There are two incremental facet recovery algorithms: the cavity re- // - // triangulation algorithm of Si, H., and Gaertner, K. "3D Boundary Recovery // - // by Constrained Delaunay Tetrahedralization," International Journal for // - // Numerical Methods in Engineering, 85:1341-1364, 2011, and the flip // - // algorithm of Shewchuk, J. "Updating and Constructing Constrained Delaunay // - // and Constrained Regular Triangulations by Flips." In Proceedings of the // - // 19th ACM Symposium on Computational Geometry, 86-95, 2003. // - // // - // Although no Steiner point is needed in step (2), a facet with non-coplanar // - // vertices might need Steiner points. It is discussed in the paper of Si, H.,// - // and Shewchuk, J., "Incrementally Constructing and Updating Constrained // - // Delaunay Tetrahedralizations with Finite Precision Coordinates." In // - // Proceedings of the 21th International Meshing Roundtable, 2012. // - // // - // Our implementation of the facet recovery algorithms recovers a "missing // - // region" at a time. Each missing region is a subset of connected interiors // - // of a polygon. The routine formcavity() creates the cavity of crossing // - // tetrahedra of the missing region. The cavity re-triangulation algorithm is // - // implemented by three subroutines, delaunizecavity(), fillcavity(), and // - // carvecavity(). Since it may fail due to non-coplanar vertices, the // - // subroutine restorecavity() is used to restore the original cavity. // - // // - // The routine flipinsertfacet() implements the flip algorithm. The sub- // - // routine flipcertify() is used to maintain the priority queue of flips. // - // The routine refineregion() is called when the facet recovery algorithm // - // fails to recover a missing region. It inserts Steiner points to refine the // - // missing region. To avoid inserting Steiner points very close to existing // - // segments. The classical encroachment rules of the Delaunay refinement // - // algorithm are used to choose the Steiner points. The routine // - // constrainedfacets() does the facet recovery by using either the cavity re- // - // triangulation algorithm (default) or the flip algorithm. It results in a // - // CDT of the (modified) PLC (including Steiner points). // - // // - //============================================================================// - - enum interresult finddirection(triface* searchtet, point endpt); - enum interresult scoutsegment(point, point, face*, triface*, point*, arraypool*); - int getsteinerptonsegment(face* seg, point refpt, point steinpt); - void delaunizesegments(); - - int scoutsubface(face* searchsh, triface* searchtet, int shflag); - void formregion(face*, arraypool*, arraypool*, arraypool*); - int scoutcrossedge(triface& crosstet, arraypool*, arraypool*); - bool formcavity(triface*, arraypool*, arraypool*, arraypool*, arraypool*, arraypool*, arraypool*); - // Facet recovery by cavity re-triangulation [Si and Gaertner 2011]. - void delaunizecavity(arraypool*, arraypool*, arraypool*, arraypool*, arraypool*, arraypool*); - bool fillcavity(arraypool*, arraypool*, arraypool*, arraypool*, arraypool*, arraypool*, triface* crossedge); - void carvecavity(arraypool*, arraypool*, arraypool*); - void restorecavity(arraypool*, arraypool*, arraypool*, arraypool*); - // Facet recovery by flips [Shewchuk 2003]. - void flipcertify(triface* chkface, badface** pqueue, point, point, point); - void flipinsertfacet(arraypool*, arraypool*, arraypool*, arraypool*); - - int insertpoint_cdt(point, triface*, face*, face*, insertvertexflags*, arraypool*, arraypool*, arraypool*, - arraypool*, arraypool*, arraypool*); - void refineregion(face&, arraypool*, arraypool*, arraypool*, arraypool*, arraypool*, arraypool*); - void constrainedfacets(); - - void constraineddelaunay(clock_t&); - - //============================================================================// - // // - // Constrained tetrahedralizations. // - // // - //============================================================================// - - void sort_2pts(point p1, point p2, point ppt[2]); - void sort_3pts(point p1, point p2, point p3, point ppt[3]); - - bool is_collinear_at(point mid, point left, point right); - bool is_segment(point p1, point p2); - bool valid_constrained_f23(triface&, point pd, point pe); - bool valid_constrained_f32(triface*, point pa, point pb); - - int checkflipeligibility(int fliptype, point, point, point, point, point, int level, int edgepivot, - flipconstraints* fc); - - int removeedgebyflips(triface*, flipconstraints*); - int removefacebyflips(triface*, flipconstraints*); - - int recoveredgebyflips(point, point, face*, triface*, int fullsearch, int& idir); - int add_steinerpt_in_schoenhardtpoly(triface*, int, int, int chkencflag); - int add_steinerpt_in_segment(face*, int searchlevel, int& idir); - int add_steinerpt_to_recover_edge(point, point, face*, int, int, int& idir); - int recoversegments(arraypool*, int fullsearch, int steinerflag); - - int recoverfacebyflips(point, point, point, face*, triface*, int&, point*, point*); - int recoversubfaces(arraypool*, int steinerflag); - - int getvertexstar(int, point searchpt, arraypool*, arraypool*, arraypool*); - int getedge(point, point, triface*); - int reduceedgesatvertex(point startpt, arraypool* endptlist); - int removevertexbyflips(point steinerpt); - - int smoothpoint(point smtpt, arraypool*, int ccw, optparameters* opm); - int suppressbdrysteinerpoint(point steinerpt); - int suppresssteinerpoints(); - - void recoverboundary(clock_t&); - - //============================================================================// - // // - // Mesh reconstruction // - // // - //============================================================================// - - void carveholes(); - - void reconstructmesh(); - - int search_face(point p0, point p1, point p2, triface& tetloop); - int search_edge(point p0, point p1, triface& tetloop); - int scout_point(point, triface*, int randflag); - REAL getpointmeshsize(point, triface*, int iloc); - void interpolatemeshsize(); - - void insertconstrainedpoints(point* insertarray, int arylen, int rejflag); - void insertconstrainedpoints(tetgenio* addio); - - void collectremovepoints(arraypool* remptlist); - void meshcoarsening(); - - //============================================================================// - // // - // Mesh refinement // - // // - // The purpose of mesh refinement is to obtain a tetrahedral mesh with well- // - // -shaped tetrahedra and appropriate mesh size. It is necessary to insert // - // new Steiner points to achieve this property. The questions are (1) how to // - // choose the Steiner points? and (2) how to insert them? // - // // - // Delaunay refinement is a technique first developed by Chew [1989] and // - // Ruppert [1993, 1995] to generate quality triangular meshes in the plane. // - // It provides guarantee on the smallest angle of the triangles. Rupper's // - // algorithm guarantees that the mesh is size-optimal (to within a constant // - // factor) among all meshes with the same quality. // - // Shewchuk generalized Ruppert's algorithm into 3D in his PhD thesis // - // [Shewchuk 1997]. A short version of his algorithm appears in "Tetrahedral // - // Mesh Generation by Delaunay Refinement," In Proceedings of the 14th ACM // - // Symposium on Computational Geometry, 86-95, 1998. It guarantees that all // - // tetrahedra of the output mesh have a "radius-edge ratio" (equivalent to // - // the minimal face angle) bounded. However, it does not remove slivers, a // - // type of very flat tetrahedra which can have no small face angles but have // - // very small (and large) dihedral angles. Moreover, it may not terminate if // - // the input PLC contains "sharp features", e.g., two edges (or two facets) // - // meet at an acute angle (or dihedral angle). // - // // - // TetGen uses the basic Delaunay refinement scheme to insert Steiner points. // - // While it always maintains a constrained Delaunay mesh. The algorithm is // - // described in Si, H., "Adaptive Constrained Delaunay Mesh Generation," // - // International Journal for Numerical Methods in Engineering, 75:856-880. // - // This algorithm always terminates and sharp features are easily preserved. // - // The mesh has good quality (same as Shewchuk's Delaunay refinement algori- // - // thm) in the bulk of the mesh domain. Moreover, it supports the generation // - // of adaptive mesh according to a (isotropic) mesh sizing function. // - // // - //============================================================================// - - void makesegmentendpointsmap(); - REAL set_ridge_vertex_protecting_ball(point); - REAL get_min_angle_at_ridge_vertex(face* seg); - REAL get_min_diahedral_angle(face* seg); - void create_segment_info_list(); - - void makefacetverticesmap(); - void create_segment_facet_map(); - - int ridge_vertices_adjacent(point, point); - int facet_ridge_vertex_adjacent(face*, point); - int segsegadjacent(face*, face*); - int segfacetadjacent(face* checkseg, face* checksh); - int facetfacetadjacent(face*, face*); - bool is_sharp_segment(face* seg); - bool does_seg_contain_acute_vertex(face* seg); - bool create_a_shorter_edge(point steinerpt, point nearpt); - - void enqueuesubface(memorypool*, face*); - void enqueuetetrahedron(triface*); - - bool check_encroachment(point pa, point pb, point checkpt); - bool check_enc_segment(face* chkseg, point* pencpt); - bool get_steiner_on_segment(face* seg, point encpt, point newpt); - bool split_segment(face* splitseg, point encpt, REAL* param, int qflag, int, int*); - void repairencsegs(REAL* param, int qflag, int chkencflag); - - bool get_subface_ccent(face* chkfac, REAL* ccent); - bool check_enc_subface(face* chkfac, point* pencpt, REAL* ccent, REAL* radius); - bool check_subface(face* chkfac, REAL* ccent, REAL radius, REAL* param); - void enqueue_subface(face* bface, point encpt, REAL* ccent, REAL* param); - badface* top_subface(); - void dequeue_subface(); - void parallel_shift(point pa, point pb, point pc, point pt, REAL* ppt); - enum locateresult locate_on_surface(point searchpt, face* searchsh); - bool split_subface(face* splitfac, point encpt, REAL* ccent, REAL*, int, int, int*); - void repairencfacs(REAL* param, int qflag, int chkencflag); - - bool check_tetrahedron(triface* chktet, REAL* param, int& qflag); - bool checktet4split(triface* chktet, REAL* param, int& qflag); - enum locateresult locate_point_walk(point searchpt, triface*, int chkencflag); - bool split_tetrahedron(triface*, REAL*, int, int, insertvertexflags& ivf); - void repairbadtets(REAL queratio, int chkencflag); - - void delaunayrefinement(); - - //============================================================================// - // // - // Mesh optimization // - // // - //============================================================================// - - long lawsonflip3d(flipconstraints* fc); - void recoverdelaunay(); - - int get_seg_laplacian_center(point mesh_vert, REAL target[3]); - int get_surf_laplacian_center(point mesh_vert, REAL target[3]); - int get_laplacian_center(point mesh_vert, REAL target[3]); - bool move_vertex(point mesh_vert, REAL target[3]); - void smooth_vertices(); - - bool get_tet(point, point, point, point, triface*); - bool get_tetqual(triface* chktet, point oppo_pt, badface* bf); - bool get_tetqual(point, point, point, point, badface* bf); - void enqueue_badtet(badface* bf); - badface* top_badtet(); - void dequeue_badtet(); - - bool add_steinerpt_to_repair(badface* bf, bool bSmooth); - bool flip_edge_to_improve(triface* sliver_edge, REAL& improved_cosmaxd); - bool repair_tet(badface* bf, bool bFlips, bool bSmooth, bool bSteiners); - long repair_badqual_tets(bool bFlips, bool bSmooth, bool bSteiners); - void improve_mesh(); - - //============================================================================// - // // - // Mesh check and statistics // - // // - //============================================================================// - - // Mesh validations. - int check_mesh(int topoflag); - int check_shells(); - int check_segments(); - int check_delaunay(int perturb = 1); - int check_regular(int); - int check_conforming(int); - - // Mesh statistics. - void printfcomma(unsigned long n); - void qualitystatistics(); - void memorystatistics(); - void statistics(); - - //============================================================================// - // // - // Mesh output // - // // - //============================================================================// - - void jettisonnodes(); - void highorder(); - void indexelements(); - void numberedges(); - void outnodes(tetgenio*); - void outmetrics(tetgenio*); - void outelements(tetgenio*); - void outfaces(tetgenio*); - void outhullfaces(tetgenio*); - void outsubfaces(tetgenio*); - void outedges(tetgenio*); - void outsubsegments(tetgenio*); - void outneighbors(tetgenio*); - void outvoronoi(tetgenio*); - void outsmesh(char*); - void outmesh2medit(char*); - void outmesh2vtk(char*, int); - void out_surfmesh_vtk(char*, int); - void out_intersected_facets(); - - //============================================================================// - // // - // Constructor & destructor // - // // - //============================================================================// - - void initializetetgenmesh() - { - in = addin = NULL; - b = NULL; - bgm = NULL; - - tetrahedrons = subfaces = subsegs = points = NULL; - tet2segpool = tet2subpool = NULL; - dummypoint = NULL; - - badtetrahedrons = badsubfacs = badsubsegs = NULL; - split_segments_pool = split_subfaces_pool = NULL; - unsplit_badtets = unsplit_subfaces = unsplit_segments = NULL; - check_tets_list = NULL; - badqual_tets_pool = NULL; - - stack_enc_segments = stack_enc_subfaces = NULL; - - flippool = NULL; - flipstack = unflip_queue_front = unflip_queue_tail = NULL; - later_unflip_queue = unflipqueue = NULL; - - cavetetlist = cavebdrylist = caveoldtetlist = NULL; - cave_oldtet_list = NULL; - cavetetshlist = cavetetseglist = cavetetvertlist = NULL; - caveencshlist = caveencseglist = NULL; - caveshlist = caveshbdlist = cavesegshlist = NULL; - - subsegstack = subfacstack = subvertstack = NULL; - skipped_segment_list = skipped_facet_list = NULL; - - encseglist = encshlist = NULL; - - number_of_facets = 0; - idx2facetlist = NULL; - facetverticeslist = NULL; - idx_segment_facet_list = NULL; - segment_facet_list = NULL; - idx_ridge_vertex_facet_list = NULL; - ridge_vertex_facet_list = NULL; - - segmentendpointslist_length = 0; - segmentendpointslist = NULL; - segment_info_list = NULL; - idx_segment_ridge_vertex_list = NULL; - segment_ridge_vertex_list = NULL; - - subdomains = 0; - subdomain_markers = NULL; - - numpointattrib = numelemattrib = 0; - sizeoftensor = 0; - pointmtrindex = 0; - pointparamindex = 0; - pointmarkindex = 0; - point2simindex = 0; - pointinsradiusindex = 0; - elemattribindex = 0; - polarindex = 0; - volumeboundindex = 0; - shmarkindex = 0; - areaboundindex = 0; - checksubsegflag = 0; - checksubfaceflag = 0; - boundary_recovery_flag = 0; - checkconstraints = 0; - nonconvex = 0; - autofliplinklevel = 1; - useinsertradius = 0; - samples = 0l; - randomseed = 1l; - minfaceang = minfacetdihed = PI; - cos_facet_separate_ang_tol = cos(179.9 / 180. * PI); - cos_collinear_ang_tol = cos(179.9 / 180. * PI); - tetprism_vol_sum = 0.0; - longest = minedgelength = 0.0; - xmax = xmin = ymax = ymin = zmax = zmin = 0.0; - - smallest_insradius = 1.e+30; - big_radius_edge_ratio = 100.0; - elem_limit = 0; - insert_point_count = 0l; - report_refine_progress = 0l; - last_point_count = 0l; - last_insertion_count = 0l; - - insegments = 0l; - hullsize = 0l; - meshedges = meshhulledges = 0l; - steinerleft = -1; - dupverts = 0l; - unuverts = 0l; - duplicated_facets_count = 0l; - nonregularcount = 0l; - st_segref_count = st_facref_count = st_volref_count = 0l; - fillregioncount = cavitycount = cavityexpcount = 0l; - flip14count = flip26count = flipn2ncount = 0l; - flip23count = flip32count = flip44count = flip41count = 0l; - flip22count = flip31count = 0l; - recover_delaunay_count = 0l; - opt_flips_count = opt_collapse_count = opt_smooth_count = 0l; - totalworkmemory = 0l; - - } // tetgenmesh() - - void freememory() - { - if (bgm != NULL) - { - delete bgm; - } + int tri_edge_inter_tail(point, point, point, point, point, REAL, REAL); + int tri_tri_inter(point, point, point, point, point, point); + + // Linear algebra functions + inline REAL dot(REAL* v1, REAL* v2); + inline void cross(REAL* v1, REAL* v2, REAL* n); + bool lu_decmp(REAL lu[4][4], int n, int* ps, REAL* d, int N); + void lu_solve(REAL lu[4][4], int n, int* ps, REAL* b, int N); + + // Geometric calculations (non-robust) + REAL orient3dfast(REAL *pa, REAL *pb, REAL *pc, REAL *pd); + inline REAL norm2(REAL x, REAL y, REAL z); + inline REAL distance(REAL* p1, REAL* p2); + inline REAL distance2(REAL* p1, REAL* p2); + void facenormal(point pa, point pb, point pc, REAL *n, int pivot, REAL *lav); + REAL facedihedral(REAL* pa, REAL* pb, REAL* pc1, REAL* pc2); + REAL triarea(REAL* pa, REAL* pb, REAL* pc); + REAL interiorangle(REAL* o, REAL* p1, REAL* p2, REAL* n); + REAL cos_interiorangle(REAL* o, REAL* p1, REAL* p2); + void projpt2edge(REAL* p, REAL* e1, REAL* e2, REAL* prj); + void projpt2face(REAL* p, REAL* f1, REAL* f2, REAL* f3, REAL* prj); + bool circumsphere(REAL*, REAL*, REAL*, REAL*, REAL* cent, REAL* radius); + bool orthosphere(REAL*,REAL*,REAL*,REAL*,REAL,REAL,REAL,REAL,REAL*,REAL*); + void planelineint(REAL*, REAL*, REAL*, REAL*, REAL*, REAL*, REAL*); + int linelineint(REAL*, REAL*, REAL*, REAL*, REAL*, REAL*, REAL*, REAL*); + REAL tetprismvol(REAL* pa, REAL* pb, REAL* pc, REAL* pd); + bool calculateabovepoint(arraypool*, point*, point*, point*); + void calculateabovepoint4(point, point, point, point); - if (points != (memorypool*)NULL) - { - delete points; - delete[] dummypoint; - } - if (tetrahedrons != (memorypool*)NULL) - { - delete tetrahedrons; - } - if (subfaces != (memorypool*)NULL) - { - delete subfaces; - delete subsegs; - } - if (tet2segpool != NULL) - { - delete tet2segpool; - delete tet2subpool; - } +//============================================================================// +// // +// Local mesh transformations // +// // +// A local transformation replaces a set of tetrahedra with another set that // +// partitions the same space and boundaries. // +// // +// In 3D, the most straightforward local transformations are the elementary // +// flips performed within the convex hull of five vertices: 2-to-3, 3-to-2, // +// 1-to-4, and 4-to-1 flips. The numbers indicate the number of tetrahedra // +// before and after each flip. The 1-to-4 and 4-to-1 flip involve inserting // +// or deleting a vertex, respectively. // +// // +// There are complex local transformations that are a combination of element- // +// ary flips. For example, a 4-to-4 flip, which replaces two coplanar edges, // +// combines a 2-to-3 flip and a 3-to-2 flip. Note that the first 2-to-3 flip // +// will temporarily create a degenerate tetrahedron removed immediately by // +// the followed 3-to-2 flip. More generally, an n-to-m flip, where n > 3, // +// m = (n - 2) * 2, which removes an edge, can be done by first performing a // +// sequence of (n - 3) 2-to-3 flips followed by a 3-to-2 flip. // +// // +// The routines flip23(), flip32(), and flip41() perform the three elementray // +// flips. The flip14() is available inside the routine insertpoint(). // +// // +// The routines flipnm() and flipnm_post() implement a generalized edge flip // +// algorithm that uses elementary flips. // +// // +// The routine insertpoint() implements the Bowyer-Watson's cavity algorithm // +// to insert a vertex. It works for arbitrary tetrahedralization, either // +// Delaunay, or constrained Delaunay, or non-Delaunay. // +// // +//============================================================================// - if (badtetrahedrons) - { - delete badtetrahedrons; - } - if (badsubfacs) - { - delete badsubfacs; - } - if (badsubsegs) - { - delete badsubsegs; - } - if (unsplit_badtets) - { - delete unsplit_badtets; - } - if (check_tets_list) - { - delete check_tets_list; - } + void flippush(badface*&, triface*); - if (flippool != NULL) - { - delete flippool; - delete later_unflip_queue; - delete unflipqueue; - } + // The elementary flips. + void flip23(triface*, int, flipconstraints* fc); + void flip32(triface*, int, flipconstraints* fc); + void flip41(triface*, int, flipconstraints* fc); - if (cavetetlist != NULL) - { - delete cavetetlist; - delete cavebdrylist; - delete caveoldtetlist; - delete cavetetvertlist; - delete cave_oldtet_list; - } + // A generalized edge flip. + int flipnm(triface*, int n, int level, int, flipconstraints* fc); + int flipnm_post(triface*, int n, int nn, int, flipconstraints* fc); - if (caveshlist != NULL) - { - delete caveshlist; - delete caveshbdlist; - delete cavesegshlist; - delete cavetetshlist; - delete cavetetseglist; - delete caveencshlist; - delete caveencseglist; - } + // Point insertion. + int insertpoint(point, triface*, face*, face*, insertvertexflags*); + void insertpoint_abort(face*, insertvertexflags*); - if (subsegstack != NULL) - { - delete subsegstack; - delete subfacstack; - delete subvertstack; - } +//============================================================================// +// // +// Delaunay tetrahedralization // +// // +// The routine incrementaldelaunay() implemented two incremental algorithms // +// for constructing Delaunay tetrahedralizations (DTs): the Bowyer-Watson // +// (B-W) algorithm and the incremental flip algorithm of Edelsbrunner and // +// Shah, "Incremental topological flipping works for regular triangulation," // +// Algorithmica, 15:233-241, 1996. // +// // +// The routine incrementalflip() implements the flip algorithm of [Edelsbrun- // +// ner and Shah, 1996]. It flips a queue of locally non-Delaunay faces (in // +// arbitrary order). The success is guaranteed when the Delaunay tetrahedra- // +// lization is constructed incrementally by adding one vertex at a time. // +// // +// The routine locate() finds a tetrahedron contains a new point in current // +// DT. It uses a simple stochastic walk algorithm: starting from an arbitrary // +// tetrahedron in DT, it finds the destination by visit one tetrahedron at a // +// time, randomly chooses a tetrahedron if there are more than one choices. // +// This algorithm terminates due to Edelsbrunner's acyclic theorem. // +// // +// Choose a good starting tetrahedron is crucial to the speed of the walk. // +// TetGen initially uses the "jump-and-walk" algorithm of Muecke, E.P., Saias,// +// I., and Zhu, B. "Fast Randomized Point Location Without Preprocessing." In // +// Proceedings of the 12th ACM Symposium on Computational Geometry, 274-283, // +// 1996. It first randomly samples several tetrahedra in the DT and then // +// choosing the closet one to start walking. // +// // +// The above algorithm slows download dramatically as the number of points // +// grows -- reported in Amenta, N., Choi, S. and Rote, G., "Incremental // +// construction con {BRIO}," In Proceedings of 19th ACM Symposium on Computa- // +// tional Geometry, 211-219, 2003. On the other hand, Liu and Snoeyink showed // +// that the point location could be made in constant time if the points are // +// pre-sorted so that the nearby points in space have nearby indices, then // +// adding the points in this order. They sorted the points along the 3D // +// Hilbert curve. // +// // +// The routine hilbert_sort3() sorts a set of 3D points along the 3D Hilbert // +// curve. It recursively splits a point set according to the Hilbert indices // +// mapped to the subboxes of the bounding box of the point set. The Hilbert // +// indices is calculated by Butz's algorithm in 1971. An excellent exposition // +// of this algorithm can be found in the paper of Hamilton, C., "Compact // +// Hilbert Indices", Technical Report CS-2006-07, Computer Science, Dalhousie // +// University, 2006 (the Section 2). My implementation also referenced Steven // +// Witham's performance of "Hilbert walk" (hopefully, it is still available // +// at http://www.tiac.net/~sw/2008/10/Hilbert/). // +// // +// TetGen sorts the points using the method in the paper of Boissonnat,J.-D., // +// Devillers, O. and Hornus, S. "Incremental Construction of the Delaunay // +// Triangulation and the Delaunay Graph in Medium Dimension," In Proceedings // +// of the 25th ACM Symposium on Computational Geometry, 2009. It first // +// randomly sorts the points into subgroups using the Biased Randomized // +// Insertion Ordering (BRIO) of Amenta et al 2003, then sorts the points in // +// each subgroup along the 3D Hilbert curve. Inserting points in this order // +// ensure a randomized "sprinkling" of the points over the domain, while // +// sorting of each subset provides locality. // +// // +//============================================================================// - if (idx2facetlist != NULL) - { - delete[] idx2facetlist; - delete[] facetverticeslist; - delete[] idx_segment_facet_list; - delete[] segment_facet_list; - delete[] idx_ridge_vertex_facet_list; - delete[] ridge_vertex_facet_list; - } + void transfernodes(); - if (segmentendpointslist != NULL) - { - delete[] segmentendpointslist; - delete[] idx_segment_ridge_vertex_list; - delete[] segment_ridge_vertex_list; - } + // Point sorting. + int transgc[8][3][8], tsb1mod3[8]; + void hilbert_init(int n); + int hilbert_split(point* vertexarray, int arraysize, int gc0, int gc1, + REAL, REAL, REAL, REAL, REAL, REAL); + void hilbert_sort3(point* vertexarray, int arraysize, int e, int d, + REAL, REAL, REAL, REAL, REAL, REAL, int depth); + void brio_multiscale_sort(point*,int,int threshold,REAL ratio,int* depth); - if (segment_info_list != NULL) - { - delete[] segment_info_list; - } + // Point location. + unsigned long randomnation(unsigned int choices); + void randomsample(point searchpt, triface *searchtet); + enum locateresult locate(point searchpt, triface *searchtet, int chkencflag = 0); - if (subdomain_markers != NULL) - { - delete[] subdomain_markers; - } + // Incremental Delaunay construction. + enum locateresult locate_dt(point searchpt, triface *searchtet); + int insert_vertex_bw(point, triface*, insertvertexflags*); + void initialdelaunay(point pa, point pb, point pc, point pd); + void incrementaldelaunay(clock_t&); + +//============================================================================// +// // +// Surface triangulation // +// // +//============================================================================// + + void flipshpush(face*); + void flip22(face*, int, int); + void flip31(face*, int); + long lawsonflip(); + int sinsertvertex(point newpt, face*, face*, int iloc, int bowywat, int); + int sremovevertex(point delpt, face*, face*, int lawson); + + enum locateresult slocate(point, face*, int, int, int); + enum interresult sscoutsegment(face*, point, int, int, int); + void scarveholes(int, REAL*); + int triangulate(int, arraypool*, arraypool*, int, REAL*); + + void unifysegments(); + void identifyinputedges(point*); + void mergefacets(); + void meshsurface(); + + +//============================================================================// +// // +// Constrained Delaunay tetrahedralization // +// // +// A constrained Delaunay tetrahedralization (CDT) is a variation of a Delau- // +// nay tetrahedralization (DT) that respects the boundary of a 3D PLC (mesh // +// domain). A crucial difference between a CDT and a DT is that triangles in // +// the PLC's polygons are not required to be locally Delaunay, which frees // +// the CDT to respect the PLC's polygons better. CDTs have optimal properties // +// similar to those of DTs. // +// // +// Steiner Points and Steiner CDTs. It is well-known that even a simple 3D // +// polyhedron may not have a tetrahedralization which only uses its vertices. // +// Some extra points, so-called "Steiner points" are needed to form a tetrah- // +// edralization of such polyhedron. A Steiner CDT of a 3D PLC is a CDT // +// containing Steiner points. TetGen generates Steiner CDTs. // +// // +// The routine constraineddelaunay() creates a (Steiner) CDT of the PLC // +// (including Steiner points). It has two steps, (1) segment recovery and (2) // +// facet (polygon) recovery. // +// // +// The routine delaunizesegments() implements the segment recovery algorithm // +// of Si, H., and Gaertner, K. "Meshing Piecewise Linear Complexes by // +// Constrained Delaunay Tetrahedralizations," In Proceedings of the 14th // +// International Meshing Roundtable, 147--163, 2005. It adds Steiner points // +// into non-Delaunay segments until all subsegments appear together in a DT. // +// The running time of this algorithm is proportional to the number of // +// Steiner points. // +// // +// There are two incremental facet recovery algorithms: the cavity re- // +// triangulation algorithm of Si, H., and Gaertner, K. "3D Boundary Recovery // +// by Constrained Delaunay Tetrahedralization," International Journal for // +// Numerical Methods in Engineering, 85:1341-1364, 2011, and the flip // +// algorithm of Shewchuk, J. "Updating and Constructing Constrained Delaunay // +// and Constrained Regular Triangulations by Flips." In Proceedings of the // +// 19th ACM Symposium on Computational Geometry, 86-95, 2003. // +// // +// Although no Steiner point is needed in step (2), a facet with non-coplanar // +// vertices might need Steiner points. It is discussed in the paper of Si, H.,// +// and Shewchuk, J., "Incrementally Constructing and Updating Constrained // +// Delaunay Tetrahedralizations with Finite Precision Coordinates." In // +// Proceedings of the 21th International Meshing Roundtable, 2012. // +// // +// Our implementation of the facet recovery algorithms recovers a "missing // +// region" at a time. Each missing region is a subset of connected interiors // +// of a polygon. The routine formcavity() creates the cavity of crossing // +// tetrahedra of the missing region. The cavity re-triangulation algorithm is // +// implemented by three subroutines, delaunizecavity(), fillcavity(), and // +// carvecavity(). Since it may fail due to non-coplanar vertices, the // +// subroutine restorecavity() is used to restore the original cavity. // +// // +// The routine flipinsertfacet() implements the flip algorithm. The sub- // +// routine flipcertify() is used to maintain the priority queue of flips. // +// The routine refineregion() is called when the facet recovery algorithm // +// fails to recover a missing region. It inserts Steiner points to refine the // +// missing region. To avoid inserting Steiner points very close to existing // +// segments. The classical encroachment rules of the Delaunay refinement // +// algorithm are used to choose the Steiner points. The routine // +// constrainedfacets() does the facet recovery by using either the cavity re- // +// triangulation algorithm (default) or the flip algorithm. It results in a // +// CDT of the (modified) PLC (including Steiner points). // +// // +//============================================================================// + + enum interresult finddirection(triface* searchtet, point endpt); + enum interresult scoutsegment(point, point, face*, triface*, point*, + arraypool*); + int getsteinerptonsegment(face* seg, point refpt, point steinpt); + void delaunizesegments(); + + int scoutsubface(face* searchsh,triface* searchtet,int shflag); + void formregion(face*, arraypool*, arraypool*, arraypool*); + int scoutcrossedge(triface& crosstet, arraypool*, arraypool*); + bool formcavity(triface*, arraypool*, arraypool*, arraypool*, arraypool*, + arraypool*, arraypool*); + // Facet recovery by cavity re-triangulation [Si and Gaertner 2011]. + void delaunizecavity(arraypool*, arraypool*, arraypool*, arraypool*, + arraypool*, arraypool*); + bool fillcavity(arraypool*, arraypool*, arraypool*, arraypool*, + arraypool*, arraypool*, triface* crossedge); + void carvecavity(arraypool*, arraypool*, arraypool*); + void restorecavity(arraypool*, arraypool*, arraypool*, arraypool*); + // Facet recovery by flips [Shewchuk 2003]. + void flipcertify(triface *chkface, badface **pqueue, point, point, point); + void flipinsertfacet(arraypool*, arraypool*, arraypool*, arraypool*); + + int insertpoint_cdt(point, triface*, face*, face*, insertvertexflags*, + arraypool*, arraypool*, arraypool*, arraypool*, + arraypool*, arraypool*); + void refineregion(face&, arraypool*, arraypool*, arraypool*, arraypool*, + arraypool*, arraypool*); + void constrainedfacets(); + + void constraineddelaunay(clock_t&); + +//============================================================================// +// // +// Constrained tetrahedralizations. // +// // +//============================================================================// + + void sort_2pts(point p1, point p2, point ppt[2]); + void sort_3pts(point p1, point p2, point p3, point ppt[3]); + + bool is_collinear_at(point mid, point left, point right); + bool is_segment(point p1, point p2); + bool valid_constrained_f23(triface&, point pd, point pe); + bool valid_constrained_f32(triface*, point pa, point pb); + + int checkflipeligibility(int fliptype, point, point, point, point, point, + int level, int edgepivot, flipconstraints* fc); + + int removeedgebyflips(triface*, flipconstraints*); + int removefacebyflips(triface*, flipconstraints*); + + int recoveredgebyflips(point, point, face*, triface*, int fullsearch, int& idir); + int add_steinerpt_in_schoenhardtpoly(triface*, int, int, int chkencflag); + int add_steinerpt_in_segment(face*, int searchlevel, int& idir); + int add_steinerpt_to_recover_edge(point, point, face*, int, int, int& idir); + int recoversegments(arraypool*, int fullsearch, int steinerflag); + + int recoverfacebyflips(point,point,point,face*,triface*,int&,point*,point*); + int recoversubfaces(arraypool*, int steinerflag); + + int getvertexstar(int, point searchpt, arraypool*, arraypool*, arraypool*); + int getedge(point, point, triface*); + int reduceedgesatvertex(point startpt, arraypool* endptlist); + int removevertexbyflips(point steinerpt); + + int smoothpoint(point smtpt, arraypool*, int ccw, optparameters *opm); + int suppressbdrysteinerpoint(point steinerpt); + int suppresssteinerpoints(); + + void recoverboundary(clock_t&); + +//============================================================================// +// // +// Mesh reconstruction // +// // +//============================================================================// + + void carveholes(); + + void reconstructmesh(); + + int search_face(point p0, point p1, point p2, triface &tetloop); + int search_edge(point p0, point p1, triface &tetloop); + int scout_point(point, triface*, int randflag); + REAL getpointmeshsize(point, triface*, int iloc); + void interpolatemeshsize(); + + void insertconstrainedpoints(point *insertarray, int arylen, int rejflag); + void insertconstrainedpoints(tetgenio *addio); + + void collectremovepoints(arraypool *remptlist); + void meshcoarsening(); + +//============================================================================// +// // +// Mesh refinement // +// // +// The purpose of mesh refinement is to obtain a tetrahedral mesh with well- // +// -shaped tetrahedra and appropriate mesh size. It is necessary to insert // +// new Steiner points to achieve this property. The questions are (1) how to // +// choose the Steiner points? and (2) how to insert them? // +// // +// Delaunay refinement is a technique first developed by Chew [1989] and // +// Ruppert [1993, 1995] to generate quality triangular meshes in the plane. // +// It provides guarantee on the smallest angle of the triangles. Rupper's // +// algorithm guarantees that the mesh is size-optimal (to within a constant // +// factor) among all meshes with the same quality. // +// Shewchuk generalized Ruppert's algorithm into 3D in his PhD thesis // +// [Shewchuk 1997]. A short version of his algorithm appears in "Tetrahedral // +// Mesh Generation by Delaunay Refinement," In Proceedings of the 14th ACM // +// Symposium on Computational Geometry, 86-95, 1998. It guarantees that all // +// tetrahedra of the output mesh have a "radius-edge ratio" (equivalent to // +// the minimal face angle) bounded. However, it does not remove slivers, a // +// type of very flat tetrahedra which can have no small face angles but have // +// very small (and large) dihedral angles. Moreover, it may not terminate if // +// the input PLC contains "sharp features", e.g., two edges (or two facets) // +// meet at an acute angle (or dihedral angle). // +// // +// TetGen uses the basic Delaunay refinement scheme to insert Steiner points. // +// While it always maintains a constrained Delaunay mesh. The algorithm is // +// described in Si, H., "Adaptive Constrained Delaunay Mesh Generation," // +// International Journal for Numerical Methods in Engineering, 75:856-880. // +// This algorithm always terminates and sharp features are easily preserved. // +// The mesh has good quality (same as Shewchuk's Delaunay refinement algori- // +// thm) in the bulk of the mesh domain. Moreover, it supports the generation // +// of adaptive mesh according to a (isotropic) mesh sizing function. // +// // +//============================================================================// + + void makesegmentendpointsmap(); + REAL set_ridge_vertex_protecting_ball(point); + REAL get_min_angle_at_ridge_vertex(face* seg); + REAL get_min_diahedral_angle(face* seg); + void create_segment_info_list(); + + void makefacetverticesmap(); + void create_segment_facet_map(); + + int ridge_vertices_adjacent(point, point); + int facet_ridge_vertex_adjacent(face *, point); + int segsegadjacent(face *, face *); + int segfacetadjacent(face *checkseg, face *checksh); + int facetfacetadjacent(face *, face *); + bool is_sharp_segment(face* seg); + bool does_seg_contain_acute_vertex(face* seg); + bool create_a_shorter_edge(point steinerpt, point nearpt); + + void enqueuesubface(memorypool*, face*); + void enqueuetetrahedron(triface*); + + bool check_encroachment(point pa, point pb, point checkpt); + bool check_enc_segment(face *chkseg, point *pencpt); + bool get_steiner_on_segment(face* seg, point encpt, point newpt); + bool split_segment(face *splitseg, point encpt, REAL *param, int qflag, int, int*); + void repairencsegs(REAL *param, int qflag, int chkencflag); + + bool get_subface_ccent(face *chkfac, REAL *ccent); + bool check_enc_subface(face *chkfac, point *pencpt, REAL *ccent, REAL *radius); + bool check_subface(face *chkfac, REAL *ccent, REAL radius, REAL *param); + void enqueue_subface(face *bface, point encpt, REAL *ccent, REAL *param); + badface* top_subface(); + void dequeue_subface(); + void parallel_shift(point pa, point pb, point pc, point pt, REAL* ppt); + enum locateresult locate_on_surface(point searchpt, face* searchsh); + bool split_subface(face *splitfac, point encpt, REAL *ccent, REAL*, int, int, int*); + void repairencfacs(REAL *param, int qflag, int chkencflag); + + bool check_tetrahedron(triface *chktet, REAL* param, int& qflag); + bool checktet4split(triface *chktet, REAL* param, int& qflag); + enum locateresult locate_point_walk(point searchpt, triface*, int chkencflag); + bool split_tetrahedron(triface*, REAL*, int, int, insertvertexflags &ivf); + void repairbadtets(REAL queratio, int chkencflag); + + void delaunayrefinement(); + +//============================================================================// +// // +// Mesh optimization // +// // +//============================================================================// + + long lawsonflip3d(flipconstraints *fc); + void recoverdelaunay(); + + int get_seg_laplacian_center(point mesh_vert, REAL target[3]); + int get_surf_laplacian_center(point mesh_vert, REAL target[3]); + int get_laplacian_center(point mesh_vert, REAL target[3]); + bool move_vertex(point mesh_vert, REAL target[3]); + void smooth_vertices(); + + bool get_tet(point, point, point, point, triface *); + bool get_tetqual(triface *chktet, point oppo_pt, badface *bf); + bool get_tetqual(point, point, point, point, badface *bf); + void enqueue_badtet(badface *bf); + badface* top_badtet(); + void dequeue_badtet(); + + bool add_steinerpt_to_repair(badface *bf, bool bSmooth); + bool flip_edge_to_improve(triface *sliver_edge, REAL& improved_cosmaxd); + bool repair_tet(badface *bf, bool bFlips, bool bSmooth, bool bSteiners); + long repair_badqual_tets(bool bFlips, bool bSmooth, bool bSteiners); + void improve_mesh(); + +//============================================================================// +// // +// Mesh check and statistics // +// // +//============================================================================// + + // Mesh validations. + int check_mesh(int topoflag); + int check_shells(); + int check_segments(); + int check_delaunay(int perturb = 1); + int check_regular(int); + int check_conforming(int); - initializetetgenmesh(); + // Mesh statistics. + void printfcomma(unsigned long n); + void qualitystatistics(); + void memorystatistics(); + void statistics(); + +//============================================================================// +// // +// Mesh output // +// // +//============================================================================// + + void jettisonnodes(); + void highorder(); + void indexelements(); + void numberedges(); + void outnodes(tetgenio*); + void outmetrics(tetgenio*); + void outelements(tetgenio*); + void outfaces(tetgenio*); + void outhullfaces(tetgenio*); + void outsubfaces(tetgenio*); + void outedges(tetgenio*); + void outsubsegments(tetgenio*); + void outneighbors(tetgenio*); + void outvoronoi(tetgenio*); + void outsmesh(char*); + void outmesh2medit(char*); + void outmesh2vtk(char*, int); + void out_surfmesh_vtk(char*, int); + void out_intersected_facets(); + + + + +//============================================================================// +// // +// Constructor & destructor // +// // +//============================================================================// + + void initializetetgenmesh() + { + in = addin = NULL; + b = NULL; + bgm = NULL; + + tetrahedrons = subfaces = subsegs = points = NULL; + tet2segpool = tet2subpool = NULL; + dummypoint = NULL; + + badtetrahedrons = badsubfacs = badsubsegs = NULL; + split_segments_pool = split_subfaces_pool = NULL; + unsplit_badtets = unsplit_subfaces = unsplit_segments = NULL; + check_tets_list = NULL; + badqual_tets_pool = NULL; + + stack_enc_segments = stack_enc_subfaces = NULL; + + flippool = NULL; + flipstack = unflip_queue_front = unflip_queue_tail = NULL; + later_unflip_queue = unflipqueue = NULL; + + cavetetlist = cavebdrylist = caveoldtetlist = NULL; + cave_oldtet_list = NULL; + cavetetshlist = cavetetseglist = cavetetvertlist = NULL; + caveencshlist = caveencseglist = NULL; + caveshlist = caveshbdlist = cavesegshlist = NULL; + + subsegstack = subfacstack = subvertstack = NULL; + skipped_segment_list = skipped_facet_list = NULL; + + encseglist = encshlist = NULL; + + number_of_facets = 0; + idx2facetlist = NULL; + facetverticeslist = NULL; + idx_segment_facet_list = NULL; + segment_facet_list = NULL; + idx_ridge_vertex_facet_list = NULL; + ridge_vertex_facet_list = NULL; + + segmentendpointslist_length = 0; + segmentendpointslist = NULL; + segment_info_list = NULL; + idx_segment_ridge_vertex_list = NULL; + segment_ridge_vertex_list = NULL; + + subdomains = 0; + subdomain_markers = NULL; + + numpointattrib = numelemattrib = 0; + sizeoftensor = 0; + pointmtrindex = 0; + pointparamindex = 0; + pointmarkindex = 0; + point2simindex = 0; + pointinsradiusindex = 0; + elemattribindex = 0; + polarindex = 0; + volumeboundindex = 0; + shmarkindex = 0; + areaboundindex = 0; + checksubsegflag = 0; + checksubfaceflag = 0; + boundary_recovery_flag = 0; + checkconstraints = 0; + nonconvex = 0; + autofliplinklevel = 1; + useinsertradius = 0; + samples = 0l; + randomseed = 1l; + minfaceang = minfacetdihed = PI; + cos_facet_separate_ang_tol = cos(179.9/180.*PI); + cos_collinear_ang_tol = cos(179.9/180.*PI); + tetprism_vol_sum = 0.0; + longest = minedgelength = 0.0; + xmax = xmin = ymax = ymin = zmax = zmin = 0.0; + + smallest_insradius = 1.e+30; + big_radius_edge_ratio = 100.0; + elem_limit = 0; + insert_point_count = 0l; + report_refine_progress = 0l; + last_point_count = 0l; + last_insertion_count = 0l; + + insegments = 0l; + hullsize = 0l; + meshedges = meshhulledges = 0l; + steinerleft = -1; + dupverts = 0l; + unuverts = 0l; + duplicated_facets_count = 0l; + nonregularcount = 0l; + st_segref_count = st_facref_count = st_volref_count = 0l; + fillregioncount = cavitycount = cavityexpcount = 0l; + flip14count = flip26count = flipn2ncount = 0l; + flip23count = flip32count = flip44count = flip41count = 0l; + flip22count = flip31count = 0l; + recover_delaunay_count = 0l; + opt_flips_count = opt_collapse_count = opt_smooth_count = 0l; + totalworkmemory = 0l; + + } // tetgenmesh() + + void freememory() + { + if (bgm != NULL) { + delete bgm; } - tetgenmesh() { initializetetgenmesh(); } + if (points != (memorypool *) NULL) { + delete points; + delete [] dummypoint; + } + if (tetrahedrons != (memorypool *) NULL) { + delete tetrahedrons; + } + if (subfaces != (memorypool *) NULL) { + delete subfaces; + delete subsegs; + } + if (tet2segpool != NULL) { + delete tet2segpool; + delete tet2subpool; + } - ~tetgenmesh() { freememory(); } // ~tetgenmesh() + if (badtetrahedrons) { + delete badtetrahedrons; + } + if (badsubfacs) { + delete badsubfacs; + } + if (badsubsegs) { + delete badsubsegs; + } + if (unsplit_badtets) { + delete unsplit_badtets; + } + if (check_tets_list) { + delete check_tets_list; + } + + if (flippool != NULL) { + delete flippool; + delete later_unflip_queue; + delete unflipqueue; + } + + if (cavetetlist != NULL) { + delete cavetetlist; + delete cavebdrylist; + delete caveoldtetlist; + delete cavetetvertlist; + delete cave_oldtet_list; + } + + if (caveshlist != NULL) { + delete caveshlist; + delete caveshbdlist; + delete cavesegshlist; + delete cavetetshlist; + delete cavetetseglist; + delete caveencshlist; + delete caveencseglist; + } + + if (subsegstack != NULL) { + delete subsegstack; + delete subfacstack; + delete subvertstack; + } + + if (idx2facetlist != NULL) { + delete [] idx2facetlist; + delete [] facetverticeslist; + delete [] idx_segment_facet_list; + delete [] segment_facet_list; + delete [] idx_ridge_vertex_facet_list; + delete [] ridge_vertex_facet_list; + } -}; // End of class tetgenmesh. + if (segmentendpointslist != NULL) { + delete [] segmentendpointslist; + delete [] idx_segment_ridge_vertex_list; + delete [] segment_ridge_vertex_list; + } + + if (segment_info_list != NULL) { + delete [] segment_info_list; + } + + if (subdomain_markers != NULL) { + delete [] subdomain_markers; + } + + initializetetgenmesh(); + } + + tetgenmesh() + { + initializetetgenmesh(); + } + + ~tetgenmesh() + { + freememory(); + } // ~tetgenmesh() + +}; // End of class tetgenmesh. //============================================================================// // // @@ -2538,12 +2468,14 @@ class tetgenmesh // // //============================================================================// -void tetrahedralize(tetgenbehavior* b, tetgenio* in, tetgenio* out, tetgenio* addin = NULL, tetgenio* bgmin = NULL); +void tetrahedralize(tetgenbehavior *b, tetgenio *in, tetgenio *out, + tetgenio *addin = NULL, tetgenio *bgmin = NULL); #ifdef TETLIBRARY -void tetrahedralize(char* switches, tetgenio* in, tetgenio* out, tetgenio* addin = NULL, tetgenio* bgmin = NULL); +void tetrahedralize(char *switches, tetgenio *in, tetgenio *out, + tetgenio *addin = NULL, tetgenio *bgmin = NULL); -#endif // #ifdef TETLIBRARY +#endif // #ifdef TETLIBRARY //============================================================================// // // @@ -2551,41 +2483,45 @@ void tetrahedralize(char* switches, tetgenio* in, tetgenio* out, tetgenio* addin // // //============================================================================// -inline void terminatetetgen(tetgenmesh* m, int x) + +inline void terminatetetgen(tetgenmesh *m, int x) { #ifdef TETLIBRARY - throw x; + throw x; #else - switch (x) - { - case 1: // Out of memory. - printf("Error: Out of memory.\n"); - break; - case 2: // Encounter an internal error. - printf("Please report this bug to Hang.Si@wias-berlin.de. Include\n"); - printf(" the message above, your input data set, and the exact\n"); - printf(" command line you used to run this program, thank you.\n"); - break; - case 3: - printf("The input surface mesh contain self-intersections. Program stopped.\n"); - //printf("Hint: use -d option to detect all self-intersections.\n"); - break; - case 4: - printf("A very small input feature size was detected. Program stopped.\n"); - if (m) - { - printf("Hint: use -T option to set a smaller tolerance. Current is %g\n", m->b->epsilon); - } - break; - case 5: - printf("Two very close input facets were detected. Program stopped.\n"); - printf("Hint: use -Y option to avoid adding Steiner points in boundary.\n"); - break; - case 10: printf("An input error was detected. Program stopped.\n"); break; - case 200: printf("Boundary contains Steiner points (-YY option). Program stopped.\n"); break; - } // switch (x) - exit(x); -#endif // #ifdef TETLIBRARY + switch (x) { + case 1: // Out of memory. + printf("Error: Out of memory.\n"); + break; + case 2: // Encounter an internal error. + printf("Please report this bug to Hang.Si@wias-berlin.de. Include\n"); + printf(" the message above, your input data set, and the exact\n"); + printf(" command line you used to run this program, thank you.\n"); + break; + case 3: + printf("The input surface mesh contain self-intersections. Program stopped.\n"); + //printf("Hint: use -d option to detect all self-intersections.\n"); + break; + case 4: + printf("A very small input feature size was detected. Program stopped.\n"); + if (m) { + printf("Hint: use -T option to set a smaller tolerance. Current is %g\n", + m->b->epsilon); + } + break; + case 5: + printf("Two very close input facets were detected. Program stopped.\n"); + printf("Hint: use -Y option to avoid adding Steiner points in boundary.\n"); + break; + case 10: + printf("An input error was detected. Program stopped.\n"); + break; + case 200: + printf("Boundary contains Steiner points (-YY option). Program stopped.\n"); + break; + } // switch (x) + exit(x); +#endif // #ifdef TETLIBRARY } //============================================================================// @@ -2594,424 +2530,373 @@ inline void terminatetetgen(tetgenmesh* m, int x) // // //============================================================================// -// encode() compress a handle into a single pointer. It relies on the +// encode() compress a handle into a single pointer. It relies on the // assumption that all addresses of tetrahedra are aligned to sixteen- // byte boundaries, so that the last four significant bits are zero. -inline tetgenmesh::tetrahedron tetgenmesh::encode(triface& t) -{ - return (tetrahedron)((uintptr_t)(t).tet | (uintptr_t)(t).ver); +inline tetgenmesh::tetrahedron tetgenmesh::encode(triface& t) { + return (tetrahedron) ((uintptr_t) (t).tet | (uintptr_t) (t).ver); } -inline tetgenmesh::tetrahedron tetgenmesh::encode2(tetrahedron* ptr, int ver) -{ - return (tetrahedron)((uintptr_t)(ptr) | (uintptr_t)(ver)); +inline tetgenmesh::tetrahedron tetgenmesh::encode2(tetrahedron* ptr, int ver) { + return (tetrahedron) ((uintptr_t) (ptr) | (uintptr_t) (ver)); } // decode() converts a pointer to a handle. The version is extracted from // the four least significant bits of the pointer. -inline void tetgenmesh::decode(tetrahedron ptr, triface& t) -{ - (t).ver = (int)((uintptr_t)(ptr) & (uintptr_t)15); - (t).tet = (tetrahedron*)((uintptr_t)(ptr) ^ (uintptr_t)(t).ver); +inline void tetgenmesh::decode(tetrahedron ptr, triface& t) { + (t).ver = (int) ((uintptr_t) (ptr) & (uintptr_t) 15); + (t).tet = (tetrahedron *) ((uintptr_t) (ptr) ^ (uintptr_t) (t).ver); } inline tetgenmesh::tetrahedron* tetgenmesh::decode_tet_only(tetrahedron ptr) { - return (tetrahedron*)((((uintptr_t)ptr) >> 4) << 4); + return (tetrahedron *) ((((uintptr_t) ptr) >> 4) << 4); } inline int tetgenmesh::decode_ver_only(tetrahedron ptr) { - return (int)((uintptr_t)(ptr) & (uintptr_t)15); + return (int) ((uintptr_t) (ptr) & (uintptr_t) 15); } -// bond() connects two tetrahedra together. (t1,v1) and (t2,v2) must -// refer to the same face and the same edge. +// bond() connects two tetrahedra together. (t1,v1) and (t2,v2) must +// refer to the same face and the same edge. -inline void tetgenmesh::bond(triface& t1, triface& t2) -{ - t1.tet[t1.ver & 3] = encode2(t2.tet, bondtbl[t1.ver][t2.ver]); - t2.tet[t2.ver & 3] = encode2(t1.tet, bondtbl[t2.ver][t1.ver]); +inline void tetgenmesh::bond(triface& t1, triface& t2) { + t1.tet[t1.ver & 3] = encode2(t2.tet, bondtbl[t1.ver][t2.ver]); + t2.tet[t2.ver & 3] = encode2(t1.tet, bondtbl[t2.ver][t1.ver]); } + // dissolve() a bond (from one side). -inline void tetgenmesh::dissolve(triface& t) -{ - t.tet[t.ver & 3] = NULL; +inline void tetgenmesh::dissolve(triface& t) { + t.tet[t.ver & 3] = NULL; } // enext() finds the next edge (counterclockwise) in the same face. -inline void tetgenmesh::enext(triface& t1, triface& t2) -{ - t2.tet = t1.tet; - t2.ver = enexttbl[t1.ver]; // (t1.ver + 4) % 12; +inline void tetgenmesh::enext(triface& t1, triface& t2) { + t2.tet = t1.tet; + t2.ver = enexttbl[t1.ver]; // (t1.ver + 4) % 12; } -inline void tetgenmesh::enextself(triface& t) -{ - t.ver = enexttbl[t.ver]; // (t.ver + 4) % 12; +inline void tetgenmesh::enextself(triface& t) { + t.ver = enexttbl[t.ver]; // (t.ver + 4) % 12; } // eprev() finds the next edge (clockwise) in the same face. -inline void tetgenmesh::eprev(triface& t1, triface& t2) -{ - t2.tet = t1.tet; - t2.ver = eprevtbl[t1.ver]; // (t1.ver + 8) % 12; +inline void tetgenmesh::eprev(triface& t1, triface& t2) { + t2.tet = t1.tet; + t2.ver = eprevtbl[t1.ver]; // (t1.ver + 8) % 12; } -inline void tetgenmesh::eprevself(triface& t) -{ - t.ver = eprevtbl[t.ver]; // (t.ver + 8) % 12; +inline void tetgenmesh::eprevself(triface& t) { + t.ver = eprevtbl[t.ver]; // (t.ver + 8) % 12; } // esym() finds the reversed edge. It is in the other face of the // same tetrahedron. -inline void tetgenmesh::esym(triface& t1, triface& t2) -{ - (t2).tet = (t1).tet; - (t2).ver = esymtbl[(t1).ver]; +inline void tetgenmesh::esym(triface& t1, triface& t2) { + (t2).tet = (t1).tet; + (t2).ver = esymtbl[(t1).ver]; } -inline void tetgenmesh::esymself(triface& t) -{ - (t).ver = esymtbl[(t).ver]; +inline void tetgenmesh::esymself(triface& t) { + (t).ver = esymtbl[(t).ver]; } // enextesym() finds the reversed edge of the next edge. It is in the other -// face of the same tetrahedron. It is the combination esym() * enext(). +// face of the same tetrahedron. It is the combination esym() * enext(). -inline void tetgenmesh::enextesym(triface& t1, triface& t2) -{ - t2.tet = t1.tet; - t2.ver = enextesymtbl[t1.ver]; +inline void tetgenmesh::enextesym(triface& t1, triface& t2) { + t2.tet = t1.tet; + t2.ver = enextesymtbl[t1.ver]; } -inline void tetgenmesh::enextesymself(triface& t) -{ - t.ver = enextesymtbl[t.ver]; +inline void tetgenmesh::enextesymself(triface& t) { + t.ver = enextesymtbl[t.ver]; } // eprevesym() finds the reversed edge of the previous edge. -inline void tetgenmesh::eprevesym(triface& t1, triface& t2) -{ - t2.tet = t1.tet; - t2.ver = eprevesymtbl[t1.ver]; +inline void tetgenmesh::eprevesym(triface& t1, triface& t2) { + t2.tet = t1.tet; + t2.ver = eprevesymtbl[t1.ver]; } -inline void tetgenmesh::eprevesymself(triface& t) -{ - t.ver = eprevesymtbl[t.ver]; +inline void tetgenmesh::eprevesymself(triface& t) { + t.ver = eprevesymtbl[t.ver]; } // eorgoppo() Finds the opposite face of the origin of the current edge. // Return the opposite edge of the current edge. -inline void tetgenmesh::eorgoppo(triface& t1, triface& t2) -{ - t2.tet = t1.tet; - t2.ver = eorgoppotbl[t1.ver]; +inline void tetgenmesh::eorgoppo(triface& t1, triface& t2) { + t2.tet = t1.tet; + t2.ver = eorgoppotbl[t1.ver]; } -inline void tetgenmesh::eorgoppoself(triface& t) -{ - t.ver = eorgoppotbl[t.ver]; +inline void tetgenmesh::eorgoppoself(triface& t) { + t.ver = eorgoppotbl[t.ver]; } -// edestoppo() Finds the opposite face of the destination of the current +// edestoppo() Finds the opposite face of the destination of the current // edge. Return the opposite edge of the current edge. -inline void tetgenmesh::edestoppo(triface& t1, triface& t2) -{ - t2.tet = t1.tet; - t2.ver = edestoppotbl[t1.ver]; +inline void tetgenmesh::edestoppo(triface& t1, triface& t2) { + t2.tet = t1.tet; + t2.ver = edestoppotbl[t1.ver]; } -inline void tetgenmesh::edestoppoself(triface& t) -{ - t.ver = edestoppotbl[t.ver]; +inline void tetgenmesh::edestoppoself(triface& t) { + t.ver = edestoppotbl[t.ver]; } // fsym() finds the adjacent tetrahedron at the same face and the same edge. -inline void tetgenmesh::fsym(triface& t1, triface& t2) -{ - decode((t1).tet[(t1).ver & 3], t2); - t2.ver = fsymtbl[t1.ver][t2.ver]; +inline void tetgenmesh::fsym(triface& t1, triface& t2) { + decode((t1).tet[(t1).ver & 3], t2); + t2.ver = fsymtbl[t1.ver][t2.ver]; } + #define fsymself(t) \ - t1ver = (t).ver; \ - decode((t).tet[(t).ver & 3], (t)); \ - (t).ver = fsymtbl[t1ver][(t).ver] + t1ver = (t).ver; \ + decode((t).tet[(t).ver & 3], (t));\ + (t).ver = fsymtbl[t1ver][(t).ver] // fnext() finds the next face while rotating about an edge according to // a right-hand rule. The face is in the adjacent tetrahedron. It is // the combination: fsym() * esym(). -inline void tetgenmesh::fnext(triface& t1, triface& t2) -{ - decode(t1.tet[facepivot1[t1.ver]], t2); - t2.ver = facepivot2[t1.ver][t2.ver]; +inline void tetgenmesh::fnext(triface& t1, triface& t2) { + decode(t1.tet[facepivot1[t1.ver]], t2); + t2.ver = facepivot2[t1.ver][t2.ver]; } + #define fnextself(t) \ - t1ver = (t).ver; \ - decode((t).tet[facepivot1[(t).ver]], (t)); \ - (t).ver = facepivot2[t1ver][(t).ver] + t1ver = (t).ver; \ + decode((t).tet[facepivot1[(t).ver]], (t)); \ + (t).ver = facepivot2[t1ver][(t).ver] + // The following primtives get or set the origin, destination, face apex, // or face opposite of an ordered tetrahedron. -inline tetgenmesh::point tetgenmesh::org(triface& t) -{ - return (point)(t).tet[orgpivot[(t).ver]]; +inline tetgenmesh::point tetgenmesh::org(triface& t) { + return (point) (t).tet[orgpivot[(t).ver]]; } -inline tetgenmesh::point tetgenmesh::dest(triface& t) -{ - return (point)(t).tet[destpivot[(t).ver]]; +inline tetgenmesh::point tetgenmesh:: dest(triface& t) { + return (point) (t).tet[destpivot[(t).ver]]; } -inline tetgenmesh::point tetgenmesh::apex(triface& t) -{ - return (point)(t).tet[apexpivot[(t).ver]]; +inline tetgenmesh::point tetgenmesh:: apex(triface& t) { + return (point) (t).tet[apexpivot[(t).ver]]; } -inline tetgenmesh::point tetgenmesh::oppo(triface& t) -{ - return (point)(t).tet[oppopivot[(t).ver]]; +inline tetgenmesh::point tetgenmesh:: oppo(triface& t) { + return (point) (t).tet[oppopivot[(t).ver]]; } -inline void tetgenmesh::setorg(triface& t, point p) -{ - (t).tet[orgpivot[(t).ver]] = (tetrahedron)(p); +inline void tetgenmesh:: setorg(triface& t, point p) { + (t).tet[orgpivot[(t).ver]] = (tetrahedron) (p); } -inline void tetgenmesh::setdest(triface& t, point p) -{ - (t).tet[destpivot[(t).ver]] = (tetrahedron)(p); +inline void tetgenmesh:: setdest(triface& t, point p) { + (t).tet[destpivot[(t).ver]] = (tetrahedron) (p); } -inline void tetgenmesh::setapex(triface& t, point p) -{ - (t).tet[apexpivot[(t).ver]] = (tetrahedron)(p); +inline void tetgenmesh:: setapex(triface& t, point p) { + (t).tet[apexpivot[(t).ver]] = (tetrahedron) (p); } -inline void tetgenmesh::setoppo(triface& t, point p) -{ - (t).tet[oppopivot[(t).ver]] = (tetrahedron)(p); +inline void tetgenmesh:: setoppo(triface& t, point p) { + (t).tet[oppopivot[(t).ver]] = (tetrahedron) (p); } #define setvertices(t, torg, tdest, tapex, toppo) \ - (t).tet[orgpivot[(t).ver]] = (tetrahedron)(torg); \ - (t).tet[destpivot[(t).ver]] = (tetrahedron)(tdest); \ - (t).tet[apexpivot[(t).ver]] = (tetrahedron)(tapex); \ - (t).tet[oppopivot[(t).ver]] = (tetrahedron)(toppo) + (t).tet[orgpivot[(t).ver]] = (tetrahedron) (torg);\ + (t).tet[destpivot[(t).ver]] = (tetrahedron) (tdest); \ + (t).tet[apexpivot[(t).ver]] = (tetrahedron) (tapex); \ + (t).tet[oppopivot[(t).ver]] = (tetrahedron) (toppo) + inline REAL* tetgenmesh::get_polar(tetrahedron* ptr) { - return &(((REAL*)(ptr))[polarindex]); + return &(((REAL *) (ptr))[polarindex]); } inline REAL tetgenmesh::get_volume(tetrahedron* ptr) { - return ((REAL*)(ptr))[polarindex + 4]; + return ((REAL *) (ptr))[polarindex + 4]; } // Check or set a tetrahedron's attributes. -inline REAL tetgenmesh::elemattribute(tetrahedron* ptr, int attnum) -{ - return ((REAL*)(ptr))[elemattribindex + attnum]; +inline REAL tetgenmesh::elemattribute(tetrahedron* ptr, int attnum) { + return ((REAL *) (ptr))[elemattribindex + attnum]; } -inline void tetgenmesh::setelemattribute(tetrahedron* ptr, int attnum, REAL value) -{ - ((REAL*)(ptr))[elemattribindex + attnum] = value; +inline void tetgenmesh::setelemattribute(tetrahedron* ptr, int attnum, + REAL value) { + ((REAL *) (ptr))[elemattribindex + attnum] = value; } // Check or set a tetrahedron's maximum volume bound. -inline REAL tetgenmesh::volumebound(tetrahedron* ptr) -{ - return ((REAL*)(ptr))[volumeboundindex]; +inline REAL tetgenmesh::volumebound(tetrahedron* ptr) { + return ((REAL *) (ptr))[volumeboundindex]; } -inline void tetgenmesh::setvolumebound(tetrahedron* ptr, REAL value) -{ - ((REAL*)(ptr))[volumeboundindex] = value; +inline void tetgenmesh::setvolumebound(tetrahedron* ptr, REAL value) { + ((REAL *) (ptr))[volumeboundindex] = value; } // Get or set a tetrahedron's index (only used for output). // These two routines use the reserved slot ptr[10]. -inline int tetgenmesh::elemindex(tetrahedron* ptr) -{ - int* iptr = (int*)&(ptr[10]); - return iptr[0]; +inline int tetgenmesh::elemindex(tetrahedron* ptr) { + int *iptr = (int *) &(ptr[10]); + return iptr[0]; } -inline void tetgenmesh::setelemindex(tetrahedron* ptr, int value) -{ - int* iptr = (int*)&(ptr[10]); - iptr[0] = value; +inline void tetgenmesh::setelemindex(tetrahedron* ptr, int value) { + int *iptr = (int *) &(ptr[10]); + iptr[0] = value; } -// Get or set a tetrahedron's marker. +// Get or set a tetrahedron's marker. // Set 'value = 0' cleans all the face/edge flags. -inline int tetgenmesh::elemmarker(tetrahedron* ptr) -{ - return ((int*)(ptr))[elemmarkerindex]; +inline int tetgenmesh::elemmarker(tetrahedron* ptr) { + return ((int *) (ptr))[elemmarkerindex]; } -inline void tetgenmesh::setelemmarker(tetrahedron* ptr, int value) -{ - ((int*)(ptr))[elemmarkerindex] = value; +inline void tetgenmesh::setelemmarker(tetrahedron* ptr, int value) { + ((int *) (ptr))[elemmarkerindex] = value; } // infect(), infected(), uninfect() -- primitives to flag or unflag a // tetrahedron. The last bit of the element marker is flagged (1) // or unflagged (0). -inline void tetgenmesh::infect(triface& t) -{ - ((int*)(t.tet))[elemmarkerindex] |= 1; +inline void tetgenmesh::infect(triface& t) { + ((int *) (t.tet))[elemmarkerindex] |= 1; } -inline void tetgenmesh::uninfect(triface& t) -{ - ((int*)(t.tet))[elemmarkerindex] &= ~1; +inline void tetgenmesh::uninfect(triface& t) { + ((int *) (t.tet))[elemmarkerindex] &= ~1; } -inline bool tetgenmesh::infected(triface& t) -{ - return (((int*)(t.tet))[elemmarkerindex] & 1) != 0; +inline bool tetgenmesh::infected(triface& t) { + return (((int *) (t.tet))[elemmarkerindex] & 1) != 0; } // marktest(), marktested(), unmarktest() -- primitives to flag or unflag a // tetrahedron. Use the second lowerest bit of the element marker. -inline void tetgenmesh::marktest(triface& t) -{ - ((int*)(t.tet))[elemmarkerindex] |= 2; +inline void tetgenmesh::marktest(triface& t) { + ((int *) (t.tet))[elemmarkerindex] |= 2; } -inline void tetgenmesh::unmarktest(triface& t) -{ - ((int*)(t.tet))[elemmarkerindex] &= ~2; +inline void tetgenmesh::unmarktest(triface& t) { + ((int *) (t.tet))[elemmarkerindex] &= ~2; } - -inline bool tetgenmesh::marktested(triface& t) -{ - return (((int*)(t.tet))[elemmarkerindex] & 2) != 0; + +inline bool tetgenmesh::marktested(triface& t) { + return (((int *) (t.tet))[elemmarkerindex] & 2) != 0; } // markface(), unmarkface(), facemarked() -- primitives to flag or unflag a // face of a tetrahedron. From the last 3rd to 6th bits are used for -// face markers, e.g., the last third bit corresponds to loc = 0. +// face markers, e.g., the last third bit corresponds to loc = 0. -inline void tetgenmesh::markface(triface& t) -{ - ((int*)(t.tet))[elemmarkerindex] |= (4 << (t.ver & 3)); +inline void tetgenmesh::markface(triface& t) { + ((int *) (t.tet))[elemmarkerindex] |= (4 << (t.ver & 3)); } -inline void tetgenmesh::unmarkface(triface& t) -{ - ((int*)(t.tet))[elemmarkerindex] &= ~(4 << (t.ver & 3)); +inline void tetgenmesh::unmarkface(triface& t) { + ((int *) (t.tet))[elemmarkerindex] &= ~(4 << (t.ver & 3)); } -inline bool tetgenmesh::facemarked(triface& t) -{ - return (((int*)(t.tet))[elemmarkerindex] & (4 << (t.ver & 3))) != 0; +inline bool tetgenmesh::facemarked(triface& t) { + return (((int *) (t.tet))[elemmarkerindex] & (4 << (t.ver & 3))) != 0; } // markedge(), unmarkedge(), edgemarked() -- primitives to flag or unflag an // edge of a tetrahedron. From the last 7th to 12th bits are used for -// edge markers, e.g., the last 7th bit corresponds to the 0th edge, etc. +// edge markers, e.g., the last 7th bit corresponds to the 0th edge, etc. // Remark: The last 7th bit is marked by 2^6 = 64. -inline void tetgenmesh::markedge(triface& t) -{ - ((int*)(t.tet))[elemmarkerindex] |= (int)(64 << ver2edge[(t).ver]); +inline void tetgenmesh::markedge(triface& t) { + ((int *) (t.tet))[elemmarkerindex] |= (int) (64 << ver2edge[(t).ver]); } -inline void tetgenmesh::unmarkedge(triface& t) -{ - ((int*)(t.tet))[elemmarkerindex] &= ~(int)(64 << ver2edge[(t).ver]); +inline void tetgenmesh::unmarkedge(triface& t) { + ((int *) (t.tet))[elemmarkerindex] &= ~(int) (64 << ver2edge[(t).ver]); } -inline bool tetgenmesh::edgemarked(triface& t) -{ - return (((int*)(t.tet))[elemmarkerindex] & (int)(64 << ver2edge[(t).ver])) != 0; +inline bool tetgenmesh::edgemarked(triface& t) { + return (((int *) (t.tet))[elemmarkerindex] & + (int) (64 << ver2edge[(t).ver])) != 0; } // marktest2(), unmarktest2(), marktest2ed() -- primitives to flag and unflag // a tetrahedron. The 13th bit (2^12 = 4096) is used for this flag. -inline void tetgenmesh::marktest2(triface& t) -{ - ((int*)(t.tet))[elemmarkerindex] |= (int)(4096); +inline void tetgenmesh::marktest2(triface& t) { + ((int *) (t.tet))[elemmarkerindex] |= (int) (4096); } -inline void tetgenmesh::unmarktest2(triface& t) -{ - ((int*)(t.tet))[elemmarkerindex] &= ~(int)(4096); +inline void tetgenmesh::unmarktest2(triface& t) { + ((int *) (t.tet))[elemmarkerindex] &= ~(int) (4096); } -inline bool tetgenmesh::marktest2ed(triface& t) -{ - return (((int*)(t.tet))[elemmarkerindex] & (int)(4096)) != 0; +inline bool tetgenmesh::marktest2ed(triface& t) { + return (((int *) (t.tet))[elemmarkerindex] & (int) (4096)) != 0; } // elemcounter(), setelemcounter() -- primitives to read or ser a (small) // integer counter in this tet. It is saved from the 16th bit. On 32 bit -// system, the range of the counter is [0, 2^15 = 32768]. +// system, the range of the counter is [0, 2^15 = 32768]. -inline int tetgenmesh::elemcounter(triface& t) -{ - return (((int*)(t.tet))[elemmarkerindex]) >> 16; +inline int tetgenmesh::elemcounter(triface& t) { + return (((int *) (t.tet))[elemmarkerindex]) >> 16; } -inline void tetgenmesh::setelemcounter(triface& t, int value) -{ - int c = ((int*)(t.tet))[elemmarkerindex]; - // Clear the old counter while keep the other flags. - c &= 65535; // sum_{i=0^15} 2^i - c |= (value << 16); - ((int*)(t.tet))[elemmarkerindex] = c; +inline void tetgenmesh::setelemcounter(triface& t, int value) { + int c = ((int *) (t.tet))[elemmarkerindex]; + // Clear the old counter while keep the other flags. + c &= 65535; // sum_{i=0^15} 2^i + c |= (value << 16); + ((int *) (t.tet))[elemmarkerindex] = c; } -inline void tetgenmesh::increaseelemcounter(triface& t) -{ - int c = elemcounter(t); - setelemcounter(t, c + 1); +inline void tetgenmesh::increaseelemcounter(triface& t) { + int c = elemcounter(t); + setelemcounter(t, c + 1); } -inline void tetgenmesh::decreaseelemcounter(triface& t) -{ - int c = elemcounter(t); - setelemcounter(t, c - 1); +inline void tetgenmesh::decreaseelemcounter(triface& t) { + int c = elemcounter(t); + setelemcounter(t, c - 1); } // ishulltet() tests if t is a hull tetrahedron. -inline bool tetgenmesh::ishulltet(triface& t) -{ - return (point)(t).tet[7] == dummypoint; +inline bool tetgenmesh::ishulltet(triface& t) { + return (point) (t).tet[7] == dummypoint; } // isdeadtet() tests if t is a tetrahedron is dead. -inline bool tetgenmesh::isdeadtet(triface& t) -{ - return ((t.tet == NULL) || (t.tet[4] == NULL)); +inline bool tetgenmesh::isdeadtet(triface& t) { + return ((t.tet == NULL) || (t.tet[4] == NULL)); } //============================================================================// @@ -3028,38 +2913,35 @@ inline bool tetgenmesh::isdeadtet(triface& t) // bits of each pointer by 'sencode()'. 'sdecode()' decodes a pointer, // extracting an edge version and a pointer to the beginning of a subface. -inline void tetgenmesh::sdecode(shellface sptr, face& s) -{ - s.shver = (int)((uintptr_t)(sptr) & (uintptr_t)7); - s.sh = (shellface*)((uintptr_t)(sptr) ^ (uintptr_t)(s.shver)); +inline void tetgenmesh::sdecode(shellface sptr, face& s) { + s.shver = (int) ((uintptr_t) (sptr) & (uintptr_t) 7); + s.sh = (shellface *) ((uintptr_t) (sptr) ^ (uintptr_t) (s.shver)); } -inline tetgenmesh::shellface tetgenmesh::sencode(face& s) -{ - return (shellface)((uintptr_t)s.sh | (uintptr_t)s.shver); +inline tetgenmesh::shellface tetgenmesh::sencode(face& s) { + return (shellface) ((uintptr_t) s.sh | (uintptr_t) s.shver); } -inline tetgenmesh::shellface tetgenmesh::sencode2(shellface* sh, int shver) -{ - return (shellface)((uintptr_t)sh | (uintptr_t)shver); +inline tetgenmesh::shellface tetgenmesh::sencode2(shellface *sh, int shver) { + return (shellface) ((uintptr_t) sh | (uintptr_t) shver); } // sbond() bonds two subfaces (s1) and (s2) together. s1 and s2 must refer // to the same edge. No requirement is needed on their orientations. -inline void tetgenmesh::sbond(face& s1, face& s2) +inline void tetgenmesh::sbond(face& s1, face& s2) { - s1.sh[s1.shver >> 1] = sencode(s2); - s2.sh[s2.shver >> 1] = sencode(s1); + s1.sh[s1.shver >> 1] = sencode(s2); + s2.sh[s2.shver >> 1] = sencode(s1); } // sbond1() bonds s1 <== s2, i.e., after bonding, s1 is pointing to s2, // but s2 is not pointing to s1. s1 and s2 must refer to the same edge. // No requirement is needed on their orientations. -inline void tetgenmesh::sbond1(face& s1, face& s2) +inline void tetgenmesh::sbond1(face& s1, face& s2) { - s1.sh[s1.shver >> 1] = sencode(s2); + s1.sh[s1.shver >> 1] = sencode(s2); } // Dissolve a subface bond (from one side). Note that the other subface @@ -3067,216 +2949,227 @@ inline void tetgenmesh::sbond1(face& s1, face& s2) inline void tetgenmesh::sdissolve(face& s) { - s.sh[s.shver >> 1] = NULL; + s.sh[s.shver >> 1] = NULL; } // spivot() finds the adjacent subface (s2) for a given subface (s1). // s1 and s2 share at the same edge. -inline void tetgenmesh::spivot(face& s1, face& s2) +inline void tetgenmesh::spivot(face& s1, face& s2) { - shellface sptr = s1.sh[s1.shver >> 1]; - sdecode(sptr, s2); + shellface sptr = s1.sh[s1.shver >> 1]; + sdecode(sptr, s2); } -inline void tetgenmesh::spivotself(face& s) +inline void tetgenmesh::spivotself(face& s) { - shellface sptr = s.sh[s.shver >> 1]; - sdecode(sptr, s); + shellface sptr = s.sh[s.shver >> 1]; + sdecode(sptr, s); } // These primitives determine or set the origin, destination, or apex // of a subface with respect to the edge version. -inline tetgenmesh::point tetgenmesh::sorg(face& s) +inline tetgenmesh::point tetgenmesh::sorg(face& s) { - return (point)s.sh[sorgpivot[s.shver]]; + return (point) s.sh[sorgpivot[s.shver]]; } -inline tetgenmesh::point tetgenmesh::sdest(face& s) +inline tetgenmesh::point tetgenmesh::sdest(face& s) { - return (point)s.sh[sdestpivot[s.shver]]; + return (point) s.sh[sdestpivot[s.shver]]; } -inline tetgenmesh::point tetgenmesh::sapex(face& s) +inline tetgenmesh::point tetgenmesh::sapex(face& s) { - return (point)s.sh[sapexpivot[s.shver]]; + return (point) s.sh[sapexpivot[s.shver]]; } -inline void tetgenmesh::setsorg(face& s, point pointptr) +inline void tetgenmesh::setsorg(face& s, point pointptr) { - s.sh[sorgpivot[s.shver]] = (shellface)pointptr; + s.sh[sorgpivot[s.shver]] = (shellface) pointptr; } -inline void tetgenmesh::setsdest(face& s, point pointptr) +inline void tetgenmesh::setsdest(face& s, point pointptr) { - s.sh[sdestpivot[s.shver]] = (shellface)pointptr; + s.sh[sdestpivot[s.shver]] = (shellface) pointptr; } -inline void tetgenmesh::setsapex(face& s, point pointptr) +inline void tetgenmesh::setsapex(face& s, point pointptr) { - s.sh[sapexpivot[s.shver]] = (shellface)pointptr; + s.sh[sapexpivot[s.shver]] = (shellface) pointptr; } -#define setshvertices(s, pa, pb, pc) \ - setsorg(s, pa); \ - setsdest(s, pb); \ - setsapex(s, pc) +#define setshvertices(s, pa, pb, pc)\ + setsorg(s, pa);\ + setsdest(s, pb);\ + setsapex(s, pc) // sesym() reserves the direction of the lead edge. -inline void tetgenmesh::sesym(face& s1, face& s2) +inline void tetgenmesh::sesym(face& s1, face& s2) { - s2.sh = s1.sh; - s2.shver = (s1.shver ^ 1); // Inverse the last bit. + s2.sh = s1.sh; + s2.shver = (s1.shver ^ 1); // Inverse the last bit. } -inline void tetgenmesh::sesymself(face& s) +inline void tetgenmesh::sesymself(face& s) { - s.shver ^= 1; + s.shver ^= 1; } // senext() finds the next edge (counterclockwise) in the same orientation // of this face. -inline void tetgenmesh::senext(face& s1, face& s2) +inline void tetgenmesh::senext(face& s1, face& s2) { - s2.sh = s1.sh; - s2.shver = snextpivot[s1.shver]; + s2.sh = s1.sh; + s2.shver = snextpivot[s1.shver]; } -inline void tetgenmesh::senextself(face& s) +inline void tetgenmesh::senextself(face& s) { - s.shver = snextpivot[s.shver]; + s.shver = snextpivot[s.shver]; } -inline void tetgenmesh::senext2(face& s1, face& s2) +inline void tetgenmesh::senext2(face& s1, face& s2) { - s2.sh = s1.sh; - s2.shver = snextpivot[snextpivot[s1.shver]]; + s2.sh = s1.sh; + s2.shver = snextpivot[snextpivot[s1.shver]]; } -inline void tetgenmesh::senext2self(face& s) +inline void tetgenmesh::senext2self(face& s) { - s.shver = snextpivot[snextpivot[s.shver]]; + s.shver = snextpivot[snextpivot[s.shver]]; } + // Check or set a subface's maximum area bound. -inline REAL tetgenmesh::areabound(face& s) +inline REAL tetgenmesh::areabound(face& s) { - return ((REAL*)(s.sh))[areaboundindex]; + return ((REAL *) (s.sh))[areaboundindex]; } -inline void tetgenmesh::setareabound(face& s, REAL value) +inline void tetgenmesh::setareabound(face& s, REAL value) { - ((REAL*)(s.sh))[areaboundindex] = value; + ((REAL *) (s.sh))[areaboundindex] = value; } // These two primitives read or set a shell marker. Shell markers are used // to hold user boundary information. -inline int tetgenmesh::shellmark(face& s) +inline int tetgenmesh::shellmark(face& s) { - return ((int*)(s.sh))[shmarkindex]; + return ((int *) (s.sh))[shmarkindex]; } -inline void tetgenmesh::setshellmark(face& s, int value) +inline void tetgenmesh::setshellmark(face& s, int value) { - ((int*)(s.sh))[shmarkindex] = value; + ((int *) (s.sh))[shmarkindex] = value; } + + // sinfect(), sinfected(), suninfect() -- primitives to flag or unflag a // subface. The last bit of ((int *) ((s).sh))[shmarkindex+1] is flagged. -inline void tetgenmesh::sinfect(face& s) +inline void tetgenmesh::sinfect(face& s) { - ((int*)((s).sh))[shmarkindex + 1] = (((int*)((s).sh))[shmarkindex + 1] | (int)1); + ((int *) ((s).sh))[shmarkindex+1] = + (((int *) ((s).sh))[shmarkindex+1] | (int) 1); } -inline void tetgenmesh::suninfect(face& s) +inline void tetgenmesh::suninfect(face& s) { - ((int*)((s).sh))[shmarkindex + 1] = (((int*)((s).sh))[shmarkindex + 1] & ~(int)1); + ((int *) ((s).sh))[shmarkindex+1] = + (((int *) ((s).sh))[shmarkindex+1] & ~(int) 1); } // Test a subface for viral infection. -inline bool tetgenmesh::sinfected(face& s) +inline bool tetgenmesh::sinfected(face& s) { - return (((int*)((s).sh))[shmarkindex + 1] & (int)1) != 0; + return (((int *) ((s).sh))[shmarkindex+1] & (int) 1) != 0; } // smarktest(), smarktested(), sunmarktest() -- primitives to flag or unflag // a subface. The last 2nd bit of the integer is flagged. -inline void tetgenmesh::smarktest(face& s) +inline void tetgenmesh::smarktest(face& s) { - ((int*)((s).sh))[shmarkindex + 1] = (((int*)((s).sh))[shmarkindex + 1] | (int)2); + ((int *) ((s).sh))[shmarkindex+1] = + (((int *)((s).sh))[shmarkindex+1] | (int) 2); } -inline void tetgenmesh::sunmarktest(face& s) +inline void tetgenmesh::sunmarktest(face& s) { - ((int*)((s).sh))[shmarkindex + 1] = (((int*)((s).sh))[shmarkindex + 1] & ~(int)2); + ((int *) ((s).sh))[shmarkindex+1] = + (((int *)((s).sh))[shmarkindex+1] & ~(int)2); } -inline bool tetgenmesh::smarktested(face& s) +inline bool tetgenmesh::smarktested(face& s) { - return ((((int*)((s).sh))[shmarkindex + 1] & (int)2) != 0); + return ((((int *) ((s).sh))[shmarkindex+1] & (int) 2) != 0); } -// smarktest2(), smarktest2ed(), sunmarktest2() -- primitives to flag or +// smarktest2(), smarktest2ed(), sunmarktest2() -- primitives to flag or // unflag a subface. The last 3rd bit of the integer is flagged. -inline void tetgenmesh::smarktest2(face& s) +inline void tetgenmesh::smarktest2(face& s) { - ((int*)((s).sh))[shmarkindex + 1] = (((int*)((s).sh))[shmarkindex + 1] | (int)4); + ((int *) ((s).sh))[shmarkindex+1] = + (((int *)((s).sh))[shmarkindex+1] | (int) 4); } -inline void tetgenmesh::sunmarktest2(face& s) +inline void tetgenmesh::sunmarktest2(face& s) { - ((int*)((s).sh))[shmarkindex + 1] = (((int*)((s).sh))[shmarkindex + 1] & ~(int)4); + ((int *) ((s).sh))[shmarkindex+1] = + (((int *)((s).sh))[shmarkindex+1] & ~(int)4); } -inline bool tetgenmesh::smarktest2ed(face& s) +inline bool tetgenmesh::smarktest2ed(face& s) { - return ((((int*)((s).sh))[shmarkindex + 1] & (int)4) != 0); + return ((((int *) ((s).sh))[shmarkindex+1] & (int) 4) != 0); } // The last 4th bit of ((int *) ((s).sh))[shmarkindex+1] is flagged. -inline void tetgenmesh::smarktest3(face& s) +inline void tetgenmesh::smarktest3(face& s) { - ((int*)((s).sh))[shmarkindex + 1] = (((int*)((s).sh))[shmarkindex + 1] | (int)8); + ((int *) ((s).sh))[shmarkindex+1] = + (((int *)((s).sh))[shmarkindex+1] | (int) 8); } -inline void tetgenmesh::sunmarktest3(face& s) +inline void tetgenmesh::sunmarktest3(face& s) { - ((int*)((s).sh))[shmarkindex + 1] = (((int*)((s).sh))[shmarkindex + 1] & ~(int)8); + ((int *) ((s).sh))[shmarkindex+1] = + (((int *)((s).sh))[shmarkindex+1] & ~(int)8); } -inline bool tetgenmesh::smarktest3ed(face& s) +inline bool tetgenmesh::smarktest3ed(face& s) { - return ((((int*)((s).sh))[shmarkindex + 1] & (int)8) != 0); + return ((((int *) ((s).sh))[shmarkindex+1] & (int) 8) != 0); } + // Each facet has a unique index (automatically indexed). Starting from '0'. -// We save this index in the same field of the shell type. +// We save this index in the same field of the shell type. inline void tetgenmesh::setfacetindex(face& s, int value) { - ((int*)(s.sh))[shmarkindex + 2] = value; + ((int *) (s.sh))[shmarkindex + 2] = value; } inline int tetgenmesh::getfacetindex(face& s) { - return ((int*)(s.sh))[shmarkindex + 2]; + return ((int *) (s.sh))[shmarkindex + 2]; } // Tests if the subface (subsegment) s is dead. -inline bool tetgenmesh::isdeadsh(face& s) -{ - return ((s.sh == NULL) || (s.sh[3] == NULL)); +inline bool tetgenmesh::isdeadsh(face& s) { + return ((s.sh == NULL) || (s.sh[3] == NULL)); } //============================================================================// @@ -3287,26 +3180,26 @@ inline bool tetgenmesh::isdeadsh(face& s) // tsbond() bond a tetrahedron (t) and a subface (s) together. // Note that t and s must be the same face and the same edge. Moreover, -// t and s have the same orientation. +// t and s have the same orientation. // Since the edge number in t and in s can be any number in {0,1,2}. We bond // the edge in s which corresponds to t's 0th edge, and vice versa. inline void tetgenmesh::tsbond(triface& t, face& s) { - if ((t).tet[9] == NULL) - { - // Allocate space for this tet. - (t).tet[9] = (tetrahedron)tet2subpool->alloc(); - // Initialize. - for (int i = 0; i < 4; i++) - { - ((shellface*)(t).tet[9])[i] = NULL; - } + if ((t).tet[9] == NULL) { + // Allocate space for this tet. + (t).tet[9] = (tetrahedron) tet2subpool->alloc(); + // Initialize. + for (int i = 0; i < 4; i++) { + ((shellface *) (t).tet[9])[i] = NULL; } - // Bond t <== s. - ((shellface*)(t).tet[9])[(t).ver & 3] = sencode2((s).sh, tsbondtbl[t.ver][s.shver]); - // Bond s <== t. - s.sh[9 + ((s).shver & 1)] = (shellface)encode2((t).tet, stbondtbl[t.ver][s.shver]); + } + // Bond t <== s. + ((shellface *) (t).tet[9])[(t).ver & 3] = + sencode2((s).sh, tsbondtbl[t.ver][s.shver]); + // Bond s <== t. + s.sh[9 + ((s).shver & 1)] = + (shellface) encode2((t).tet, stbondtbl[t.ver][s.shver]); } // tspivot() finds a subface (s) abutting on the given tetrahdera (t). @@ -3314,55 +3207,54 @@ inline void tetgenmesh::tsbond(triface& t, face& s) // the subface s, and s and t must be at the same edge wth the same // orientation. -inline void tetgenmesh::tspivot(triface& t, face& s) +inline void tetgenmesh::tspivot(triface& t, face& s) { - if ((t).tet[9] == NULL) - { - (s).sh = NULL; - return; - } - // Get the attached subface s. - sdecode(((shellface*)(t).tet[9])[(t).ver & 3], (s)); - (s).shver = tspivottbl[t.ver][s.shver]; + if ((t).tet[9] == NULL) { + (s).sh = NULL; + return; + } + // Get the attached subface s. + sdecode(((shellface *) (t).tet[9])[(t).ver & 3], (s)); + (s).shver = tspivottbl[t.ver][s.shver]; } // Quickly check if the handle (t, v) is a subface. -#define issubface(t) ((t).tet[9] && ((t).tet[9])[(t).ver & 3]) +#define issubface(t) \ + ((t).tet[9] && ((t).tet[9])[(t).ver & 3]) // stpivot() finds a tetrahedron (t) abutting a given subface (s). // Return the t (if it exists) with the same edge and the same // orientation of s. -inline void tetgenmesh::stpivot(face& s, triface& t) +inline void tetgenmesh::stpivot(face& s, triface& t) { - decode((tetrahedron)s.sh[9 + (s.shver & 1)], t); - if ((t).tet == NULL) - { - return; - } - (t).ver = stpivottbl[t.ver][s.shver]; + decode((tetrahedron) s.sh[9 + (s.shver & 1)], t); + if ((t).tet == NULL) { + return; + } + (t).ver = stpivottbl[t.ver][s.shver]; } // Quickly check if this subface is attached to a tetrahedron. -#define isshtet(s) ((s).sh[9 + ((s).shver & 1)]) +#define isshtet(s) \ + ((s).sh[9 + ((s).shver & 1)]) // tsdissolve() dissolve a bond (from the tetrahedron side). -inline void tetgenmesh::tsdissolve(triface& t) +inline void tetgenmesh::tsdissolve(triface& t) { - if ((t).tet[9] != NULL) - { - ((shellface*)(t).tet[9])[(t).ver & 3] = NULL; - } + if ((t).tet[9] != NULL) { + ((shellface *) (t).tet[9])[(t).ver & 3] = NULL; + } } // stdissolve() dissolve a bond (from the subface side). -inline void tetgenmesh::stdissolve(face& s) +inline void tetgenmesh::stdissolve(face& s) { - (s).sh[9] = NULL; - (s).sh[10] = NULL; + (s).sh[9] = NULL; + (s).sh[10] = NULL; } //============================================================================// @@ -3373,35 +3265,36 @@ inline void tetgenmesh::stdissolve(face& s) // ssbond() bond a subface to a subsegment. -inline void tetgenmesh::ssbond(face& s, face& edge) +inline void tetgenmesh::ssbond(face& s, face& edge) { - s.sh[6 + (s.shver >> 1)] = sencode(edge); - edge.sh[0] = sencode(s); + s.sh[6 + (s.shver >> 1)] = sencode(edge); + edge.sh[0] = sencode(s); } -inline void tetgenmesh::ssbond1(face& s, face& edge) +inline void tetgenmesh::ssbond1(face& s, face& edge) { - s.sh[6 + (s.shver >> 1)] = sencode(edge); - //edge.sh[0] = sencode(s); + s.sh[6 + (s.shver >> 1)] = sencode(edge); + //edge.sh[0] = sencode(s); } // ssdisolve() dissolve a bond (from the subface side) -inline void tetgenmesh::ssdissolve(face& s) +inline void tetgenmesh::ssdissolve(face& s) { - s.sh[6 + (s.shver >> 1)] = NULL; + s.sh[6 + (s.shver >> 1)] = NULL; } // sspivot() finds a subsegment abutting a subface. -inline void tetgenmesh::sspivot(face& s, face& edge) +inline void tetgenmesh::sspivot(face& s, face& edge) { - sdecode((shellface)s.sh[6 + (s.shver >> 1)], edge); + sdecode((shellface) s.sh[6 + (s.shver >> 1)], edge); } // Quickly check if the edge is a subsegment. -#define isshsubseg(s) ((s).sh[6 + ((s).shver >> 1)]) +#define isshsubseg(s) \ + ((s).sh[6 + ((s).shver >> 1)]) //============================================================================// // // @@ -3411,56 +3304,51 @@ inline void tetgenmesh::sspivot(face& s, face& edge) inline void tetgenmesh::tssbond1(triface& t, face& s) { - if ((t).tet[8] == NULL) - { - // Allocate space for this tet. - (t).tet[8] = (tetrahedron)tet2segpool->alloc(); - // Initialization. - for (int i = 0; i < 6; i++) - { - ((shellface*)(t).tet[8])[i] = NULL; - } + if ((t).tet[8] == NULL) { + // Allocate space for this tet. + (t).tet[8] = (tetrahedron) tet2segpool->alloc(); + // Initialization. + for (int i = 0; i < 6; i++) { + ((shellface *) (t).tet[8])[i] = NULL; } - ((shellface*)(t).tet[8])[ver2edge[(t).ver]] = sencode((s)); + } + ((shellface *) (t).tet[8])[ver2edge[(t).ver]] = sencode((s)); } -inline void tetgenmesh::sstbond1(face& s, triface& t) +inline void tetgenmesh::sstbond1(face& s, triface& t) { - ((tetrahedron*)(s).sh)[9] = encode(t); + ((tetrahedron *) (s).sh)[9] = encode(t); } inline void tetgenmesh::tssdissolve1(triface& t) { - if ((t).tet[8] != NULL) - { - ((shellface*)(t).tet[8])[ver2edge[(t).ver]] = NULL; - } + if ((t).tet[8] != NULL) { + ((shellface *) (t).tet[8])[ver2edge[(t).ver]] = NULL; + } } -inline void tetgenmesh::sstdissolve1(face& s) +inline void tetgenmesh::sstdissolve1(face& s) { - ((tetrahedron*)(s).sh)[9] = NULL; + ((tetrahedron *) (s).sh)[9] = NULL; } inline void tetgenmesh::tsspivot1(triface& t, face& s) { - if ((t).tet[8] != NULL) - { - sdecode(((shellface*)(t).tet[8])[ver2edge[(t).ver]], s); - } - else - { - (s).sh = NULL; - } + if ((t).tet[8] != NULL) { + sdecode(((shellface *) (t).tet[8])[ver2edge[(t).ver]], s); + } else { + (s).sh = NULL; + } } // Quickly check whether 't' is a segment or not. -#define issubseg(t) ((t).tet[8] && ((t).tet[8])[ver2edge[(t).ver]]) +#define issubseg(t) \ + ((t).tet[8] && ((t).tet[8])[ver2edge[(t).ver]]) -inline void tetgenmesh::sstpivot1(face& s, triface& t) +inline void tetgenmesh::sstpivot1(face& s, triface& t) { - decode((tetrahedron)s.sh[9], t); + decode((tetrahedron) s.sh[9], t); } //============================================================================// @@ -3469,217 +3357,183 @@ inline void tetgenmesh::sstpivot1(face& s, triface& t) // // //============================================================================// -inline int tetgenmesh::pointmark(point pt) -{ - return ((int*)(pt))[pointmarkindex]; +inline int tetgenmesh::pointmark(point pt) { + return ((int *) (pt))[pointmarkindex]; } -inline void tetgenmesh::setpointmark(point pt, int value) -{ - ((int*)(pt))[pointmarkindex] = value; +inline void tetgenmesh::setpointmark(point pt, int value) { + ((int *) (pt))[pointmarkindex] = value; } + // These two primitives set and read the type of the point. -inline enum tetgenmesh::verttype tetgenmesh::pointtype(point pt) -{ - return (enum verttype)(((int*)(pt))[pointmarkindex + 1] >> (int)8); +inline enum tetgenmesh::verttype tetgenmesh::pointtype(point pt) { + return (enum verttype) (((int *) (pt))[pointmarkindex + 1] >> (int) 8); } -inline void tetgenmesh::setpointtype(point pt, enum verttype value) -{ - ((int*)(pt))[pointmarkindex + 1] = ((int)value << 8) + (((int*)(pt))[pointmarkindex + 1] & (int)255); +inline void tetgenmesh::setpointtype(point pt, enum verttype value) { + ((int *) (pt))[pointmarkindex + 1] = + ((int) value << 8) + (((int *) (pt))[pointmarkindex + 1] & (int) 255); } // pinfect(), puninfect(), pinfected() -- primitives to flag or unflag // a point. The last bit of the integer '[pointindex+1]' is flagged. -inline void tetgenmesh::pinfect(point pt) -{ - ((int*)(pt))[pointmarkindex + 1] |= (int)1; +inline void tetgenmesh::pinfect(point pt) { + ((int *) (pt))[pointmarkindex + 1] |= (int) 1; } -inline void tetgenmesh::puninfect(point pt) -{ - ((int*)(pt))[pointmarkindex + 1] &= ~(int)1; +inline void tetgenmesh::puninfect(point pt) { + ((int *) (pt))[pointmarkindex + 1] &= ~(int) 1; } -inline bool tetgenmesh::pinfected(point pt) -{ - return (((int*)(pt))[pointmarkindex + 1] & (int)1) != 0; +inline bool tetgenmesh::pinfected(point pt) { + return (((int *) (pt))[pointmarkindex + 1] & (int) 1) != 0; } -// pmarktest(), punmarktest(), pmarktested() -- more primitives to -// flag or unflag a point. +// pmarktest(), punmarktest(), pmarktested() -- more primitives to +// flag or unflag a point. -inline void tetgenmesh::pmarktest(point pt) -{ - ((int*)(pt))[pointmarkindex + 1] |= (int)2; +inline void tetgenmesh::pmarktest(point pt) { + ((int *) (pt))[pointmarkindex + 1] |= (int) 2; } -inline void tetgenmesh::punmarktest(point pt) -{ - ((int*)(pt))[pointmarkindex + 1] &= ~(int)2; +inline void tetgenmesh::punmarktest(point pt) { + ((int *) (pt))[pointmarkindex + 1] &= ~(int) 2; } -inline bool tetgenmesh::pmarktested(point pt) -{ - return (((int*)(pt))[pointmarkindex + 1] & (int)2) != 0; +inline bool tetgenmesh::pmarktested(point pt) { + return (((int *) (pt))[pointmarkindex + 1] & (int) 2) != 0; } -inline void tetgenmesh::pmarktest2(point pt) -{ - ((int*)(pt))[pointmarkindex + 1] |= (int)4; +inline void tetgenmesh::pmarktest2(point pt) { + ((int *) (pt))[pointmarkindex + 1] |= (int) 4; } -inline void tetgenmesh::punmarktest2(point pt) -{ - ((int*)(pt))[pointmarkindex + 1] &= ~(int)4; +inline void tetgenmesh::punmarktest2(point pt) { + ((int *) (pt))[pointmarkindex + 1] &= ~(int) 4; } -inline bool tetgenmesh::pmarktest2ed(point pt) -{ - return (((int*)(pt))[pointmarkindex + 1] & (int)4) != 0; +inline bool tetgenmesh::pmarktest2ed(point pt) { + return (((int *) (pt))[pointmarkindex + 1] & (int) 4) != 0; } -inline void tetgenmesh::pmarktest3(point pt) -{ - ((int*)(pt))[pointmarkindex + 1] |= (int)8; +inline void tetgenmesh::pmarktest3(point pt) { + ((int *) (pt))[pointmarkindex + 1] |= (int) 8; } -inline void tetgenmesh::punmarktest3(point pt) -{ - ((int*)(pt))[pointmarkindex + 1] &= ~(int)8; +inline void tetgenmesh::punmarktest3(point pt) { + ((int *) (pt))[pointmarkindex + 1] &= ~(int) 8; } -inline bool tetgenmesh::pmarktest3ed(point pt) -{ - return (((int*)(pt))[pointmarkindex + 1] & (int)8) != 0; +inline bool tetgenmesh::pmarktest3ed(point pt) { + return (((int *) (pt))[pointmarkindex + 1] & (int) 8) != 0; } // Read and set the geometry tag of the point (used by -s option). -inline int tetgenmesh::pointgeomtag(point pt) -{ - return ((int*)(pt))[pointmarkindex + 2]; +inline int tetgenmesh::pointgeomtag(point pt) { + return ((int *) (pt))[pointmarkindex + 2]; } -inline void tetgenmesh::setpointgeomtag(point pt, int value) -{ - ((int*)(pt))[pointmarkindex + 2] = value; +inline void tetgenmesh::setpointgeomtag(point pt, int value) { + ((int *) (pt))[pointmarkindex + 2] = value; } // Read and set the u,v coordinates of the point (used by -s option). -inline REAL tetgenmesh::pointgeomuv(point pt, int i) -{ - return pt[pointparamindex + i]; +inline REAL tetgenmesh::pointgeomuv(point pt, int i) { + return pt[pointparamindex + i]; } -inline void tetgenmesh::setpointgeomuv(point pt, int i, REAL value) -{ - pt[pointparamindex + i] = value; +inline void tetgenmesh::setpointgeomuv(point pt, int i, REAL value) { + pt[pointparamindex + i] = value; } + + // These following primitives set and read a pointer to a tetrahedron // a subface/subsegment, a point, or a tet of background mesh. -inline tetgenmesh::tetrahedron tetgenmesh::point2tet(point pt) -{ - return ((tetrahedron*)(pt))[point2simindex]; +inline tetgenmesh::tetrahedron tetgenmesh::point2tet(point pt) { + return ((tetrahedron *) (pt))[point2simindex]; } -inline void tetgenmesh::setpoint2tet(point pt, tetrahedron value) -{ - ((tetrahedron*)(pt))[point2simindex] = value; +inline void tetgenmesh::setpoint2tet(point pt, tetrahedron value) { + ((tetrahedron *) (pt))[point2simindex] = value; } -inline tetgenmesh::point tetgenmesh::point2ppt(point pt) -{ - return (point)((tetrahedron*)(pt))[point2simindex + 1]; +inline tetgenmesh::point tetgenmesh::point2ppt(point pt) { + return (point) ((tetrahedron *) (pt))[point2simindex + 1]; } -inline void tetgenmesh::setpoint2ppt(point pt, point value) -{ - ((tetrahedron*)(pt))[point2simindex + 1] = (tetrahedron)value; +inline void tetgenmesh::setpoint2ppt(point pt, point value) { + ((tetrahedron *) (pt))[point2simindex + 1] = (tetrahedron) value; } -inline tetgenmesh::shellface tetgenmesh::point2sh(point pt) -{ - return (shellface)((tetrahedron*)(pt))[point2simindex + 2]; +inline tetgenmesh::shellface tetgenmesh::point2sh(point pt) { + return (shellface) ((tetrahedron *) (pt))[point2simindex + 2]; } -inline void tetgenmesh::setpoint2sh(point pt, shellface value) -{ - ((tetrahedron*)(pt))[point2simindex + 2] = (tetrahedron)value; +inline void tetgenmesh::setpoint2sh(point pt, shellface value) { + ((tetrahedron *) (pt))[point2simindex + 2] = (tetrahedron) value; } -inline tetgenmesh::tetrahedron tetgenmesh::point2bgmtet(point pt) -{ - return ((tetrahedron*)(pt))[point2simindex + 3]; + +inline tetgenmesh::tetrahedron tetgenmesh::point2bgmtet(point pt) { + return ((tetrahedron *) (pt))[point2simindex + 3]; } -inline void tetgenmesh::setpoint2bgmtet(point pt, tetrahedron value) -{ - ((tetrahedron*)(pt))[point2simindex + 3] = value; +inline void tetgenmesh::setpoint2bgmtet(point pt, tetrahedron value) { + ((tetrahedron *) (pt))[point2simindex + 3] = value; } + // The primitives for saving and getting the insertion radius. inline void tetgenmesh::setpointinsradius(point pt, REAL value) { - pt[pointinsradiusindex] = value; + pt[pointinsradiusindex] = value; } inline REAL tetgenmesh::getpointinsradius(point pt) { - return pt[pointinsradiusindex]; + return pt[pointinsradiusindex]; } -inline bool tetgenmesh::issteinerpoint(point pt) -{ - return (pointtype(pt) == FREESEGVERTEX) || (pointtype(pt) == FREEFACETVERTEX) || (pointtype(pt) == FREEVOLVERTEX); +inline bool tetgenmesh::issteinerpoint(point pt) { + return (pointtype(pt) == FREESEGVERTEX) || (pointtype(pt) == FREEFACETVERTEX) + || (pointtype(pt) == FREEVOLVERTEX); } // point2tetorg() Get the tetrahedron whose origin is the point. inline void tetgenmesh::point2tetorg(point pa, triface& searchtet) { - decode(point2tet(pa), searchtet); - if ((point)searchtet.tet[4] == pa) - { - searchtet.ver = 11; - } - else if ((point)searchtet.tet[5] == pa) - { - searchtet.ver = 3; - } - else if ((point)searchtet.tet[6] == pa) - { - searchtet.ver = 7; - } - else - { - searchtet.ver = 0; - } + decode(point2tet(pa), searchtet); + if ((point) searchtet.tet[4] == pa) { + searchtet.ver = 11; + } else if ((point) searchtet.tet[5] == pa) { + searchtet.ver = 3; + } else if ((point) searchtet.tet[6] == pa) { + searchtet.ver = 7; + } else { + searchtet.ver = 0; + } } // point2shorg() Get the subface/segment whose origin is the point. inline void tetgenmesh::point2shorg(point pa, face& searchsh) { - sdecode(point2sh(pa), searchsh); - if ((point)searchsh.sh[3] == pa) - { - searchsh.shver = 0; - } - else if ((point)searchsh.sh[4] == pa) - { - searchsh.shver = (searchsh.sh[5] != NULL ? 2 : 1); - } - else - { - searchsh.shver = 4; - } + sdecode(point2sh(pa), searchsh); + if ((point) searchsh.sh[3] == pa) { + searchsh.shver = 0; + } else if ((point) searchsh.sh[4] == pa) { + searchsh.shver = (searchsh.sh[5] != NULL ? 2 : 1); + } else { + searchsh.shver = 4; + } } // farsorg() Return the origin of the subsegment. @@ -3687,34 +3541,32 @@ inline void tetgenmesh::point2shorg(point pa, face& searchsh) inline tetgenmesh::point tetgenmesh::farsorg(face& s) { - face travesh, neighsh; + face travesh, neighsh; - travesh = s; - while (1) - { - senext2(travesh, neighsh); - spivotself(neighsh); - if (neighsh.sh == NULL) break; - if (sorg(neighsh) != sorg(travesh)) sesymself(neighsh); - senext2(neighsh, travesh); - } - return sorg(travesh); + travesh = s; + while (1) { + senext2(travesh, neighsh); + spivotself(neighsh); + if (neighsh.sh == NULL) break; + if (sorg(neighsh) != sorg(travesh)) sesymself(neighsh); + senext2(neighsh, travesh); + } + return sorg(travesh); } -inline tetgenmesh::point tetgenmesh::farsdest(face& s) +inline tetgenmesh::point tetgenmesh::farsdest(face& s) { - face travesh, neighsh; + face travesh, neighsh; - travesh = s; - while (1) - { - senext(travesh, neighsh); - spivotself(neighsh); - if (neighsh.sh == NULL) break; - if (sdest(neighsh) != sdest(travesh)) sesymself(neighsh); - senext(neighsh, travesh); - } - return sdest(travesh); + travesh = s; + while (1) { + senext(travesh, neighsh); + spivotself(neighsh); + if (neighsh.sh == NULL) break; + if (sdest(neighsh) != sdest(travesh)) sesymself(neighsh); + senext(neighsh, travesh); + } + return sdest(travesh); } /////////////////////////////////////////////////////////////////////////////// @@ -3724,34 +3576,38 @@ inline tetgenmesh::point tetgenmesh::farsdest(face& s) /////////////////////////////////////////////////////////////////////////////// // dot() returns the dot product: v1 dot v2. -inline REAL tetgenmesh::dot(REAL* v1, REAL* v2) +inline REAL tetgenmesh::dot(REAL* v1, REAL* v2) { - return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]; + return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]; } // cross() computes the cross product: n = v1 cross v2. -inline void tetgenmesh::cross(REAL* v1, REAL* v2, REAL* n) +inline void tetgenmesh::cross(REAL* v1, REAL* v2, REAL* n) { - n[0] = v1[1] * v2[2] - v2[1] * v1[2]; - n[1] = -(v1[0] * v2[2] - v2[0] * v1[2]); - n[2] = v1[0] * v2[1] - v2[0] * v1[1]; + n[0] = v1[1] * v2[2] - v2[1] * v1[2]; + n[1] = -(v1[0] * v2[2] - v2[0] * v1[2]); + n[2] = v1[0] * v2[1] - v2[0] * v1[1]; } // distance() computes the Euclidean distance between two points. inline REAL tetgenmesh::distance(REAL* p1, REAL* p2) { - return sqrt((p2[0] - p1[0]) * (p2[0] - p1[0]) + (p2[1] - p1[1]) * (p2[1] - p1[1]) - + (p2[2] - p1[2]) * (p2[2] - p1[2])); + return sqrt((p2[0] - p1[0]) * (p2[0] - p1[0]) + + (p2[1] - p1[1]) * (p2[1] - p1[1]) + + (p2[2] - p1[2]) * (p2[2] - p1[2])); } inline REAL tetgenmesh::distance2(REAL* p1, REAL* p2) { - return norm2(p2[0] - p1[0], p2[1] - p1[1], p2[2] - p1[2]); + return norm2(p2[0] - p1[0], p2[1] - p1[1], p2[2] - p1[2]); } inline REAL tetgenmesh::norm2(REAL x, REAL y, REAL z) { - return (x) * (x) + (y) * (y) + (z) * (z); + return (x) * (x) + (y) * (y) + (z) * (z); } -#endif // #ifndef tetgenH + + +#endif // #ifndef tetgenH + diff --git a/SKIRT/utils/PathSegmentGenerator.hpp b/SKIRT/utils/PathSegmentGenerator.hpp index 3e3feb4a..a5a37270 100644 --- a/SKIRT/utils/PathSegmentGenerator.hpp +++ b/SKIRT/utils/PathSegmentGenerator.hpp @@ -8,8 +8,6 @@ #include "SpatialGridPath.hpp" -class TetraMeshSnapshot; - ////////////////////////////////////////////////////////////////////// /** PathSegmentGenerator is the abstract base class for classes that calculate and return the @@ -199,8 +197,6 @@ class PathSegmentGenerator // ------- Data members ------- - friend TetraMeshSnapshot; - private: State _state{State::Unknown}; double _rx{0.}; From 495be245c2f0207e00b2150ca5eab4b5055f8f06 Mon Sep 17 00:00:00 2001 From: arlauwer Date: Thu, 12 Dec 2024 16:30:47 +0100 Subject: [PATCH 37/51] cleanup and documentation --- SKIRT/core/TetraMeshSpatialGrid.cpp | 144 +++++++++++++++------------- SKIRT/core/TetraMeshSpatialGrid.hpp | 52 +++++++--- 2 files changed, 118 insertions(+), 78 deletions(-) diff --git a/SKIRT/core/TetraMeshSpatialGrid.cpp b/SKIRT/core/TetraMeshSpatialGrid.cpp index 46db85ad..671a0fcb 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.cpp +++ b/SKIRT/core/TetraMeshSpatialGrid.cpp @@ -12,6 +12,7 @@ #include "SiteListInterface.hpp" #include "SpatialGridPlotFile.hpp" #include "StringUtils.hpp" +#include "TextInFile.hpp" #include "tetgen.h" using std::array; @@ -57,7 +58,7 @@ namespace } } -//////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////// TetraMeshSpatialGrid::Tetra::Tetra(const vector& vertices, const array& vertexIndices, const array& faces) @@ -119,28 +120,35 @@ bool TetraMeshSpatialGrid::Tetra::contains(const Position& bfr) const { if (!_extent.contains(bfr)) return false; - // could optimize this slightly by using same vertex for 3 faces and do final face seperately, but this is more readable - for (int f = 0; f < 4; f++) + // Use the same vertex for the first 3 faces + Vec v = vertex(3); // vertex 3 is on faces 0,1,2 + + for (int f = 0; f <= 2; f++) { const Face& face = _faces[f]; - Vec v = vertex((f + 1) % 4); // any vertex that is on the face - // if point->face is opposite direction as the outward pointing normal, the point is outside + // if bfr->v is opposite direction as the outward pointing normal, the point is outside if (Vec::dot(v - bfr, face._normal) < 0) return false; } + + // Check face 3 + const Face& face = _faces[3]; + v = vertex(0); // any vertex that is not vertex 3 + + if (Vec::dot(v - bfr, face._normal) < 0) return false; + return true; } - -// https://vcg.isti.cnr.it/activities/OLD/geometryegraphics/pointintetraedro.html +// Generating Random Points in a Tetrahedron: DOI 10.1080/10867651.2000.10487528 double TetraMeshSpatialGrid::Tetra::generateBarycentric(double& s, double& t, double& u) const { - if (s + t > 1.0) - { // cut'n fold the cube into a prism + if (s + t > 1.0) // cut'n fold the cube into a prism + { s = 1.0 - s; t = 1.0 - t; } - if (t + u > 1.0) - { // cut'n fold the prism into a tetrahedron + if (t + u > 1.0) // cut'n fold the prism into a tetrahedron + { double tmp = u; u = 1.0 - s - t; t = 1.0 - tmp; @@ -212,23 +220,17 @@ const Box& TetraMeshSpatialGrid::Tetra::extent() const ////////////////////////////////////////////////////////////////////// -// This helper class contains all cell information such as vertices and tetrahedra. -// It also organizes cuboidal blocks in a smart grid, such that it is easy to retrieve -// all cells inside a certain block given a position. +// This helper class organizes the cells into cuboidal blocks in a smart grid, +// such that it is easy to retrieve all cells inside a certain block given a position. class TetraMeshSpatialGrid::BlockGrid { - int _numCells; // number of Tetra cells and centroids - int _numVertices; // vertices are added/removed as the grid is built and refined - vector _tetrahedra; // list of all tetrahedra - vector _vertices; // list of all vertices - int _gridsize; // number of grid blocks in each spatial direction - Array _xgrid, _ygrid, _zgrid; // the m+1 grid separation points for each spatial direction - vector> _listv; // the m*m*m lists of indices for blocks overlapping each grid block - int _pmin, _pmax; // minimum, maximum nr of blocks in list; total nr of blocks in listv + const vector& _tetrahedra; // reference to list of all tetrahedra + int _gridsize; // number of grid blocks in each spatial direction + Array _xgrid, _ygrid, _zgrid; // the m+1 grid separation points for each spatial direction + vector> _listv; // the m*m*m lists of indices for blocks overlapping each grid block + int _pmin, _pmax; // minimum, maximum nr of blocks in list; total nr of blocks in listv public: - BlockGrid(){}; - // The constructor creates a cuboidal grid of the specified number of grid blocks in each // spatial direction, and for each of the grid blocks it builds a list of all blocks // overlapping the grid block. In an attempt to distribute the blocks evenly over the @@ -429,36 +431,55 @@ void TetraMeshSpatialGrid::setupSelfBefore() for (int m = 0; m != n; ++m) _vertices[m] = sites[m]; break; } - case Policy::ImportedSites: + case Policy::File: { - auto sli = find()->interface(2); - // prepare the data - int n = sli->numSites(); - _vertices.resize(n); - for (int m = 0; m != n; ++m) _vertices[m] = sli->sitePosition(m); - break; + // read the input file + TextInFile in(this, _filename, "tetrahedral vertices"); + in.addColumn("position x", "length", "pc"); + in.addColumn("position y", "length", "pc"); + in.addColumn("position z", "length", "pc"); + Array coords; + while (in.readRow(coords)) _vertices.emplace_back(coords[0], coords[1], coords[2]); + in.close(); } } - if (_vertices.empty()) - { - throw FATALERROR("No vertices available for mesh generation"); - } + removeOutside(); + if (_numVertices < 4) return; // abort if there are not enough vertices + buildMesh(); + buildSearch(); +} + +//////////////////////////////////////////////////////////////////// +void TetraMeshSpatialGrid::removeOutside() +{ _numVertices = _vertices.size(); - buildMesh(); - buildSearch(); + // remove vertices outside of the domain + auto sitesEnd = std::remove_if(_vertices.begin(), _vertices.end(), [this](const Vec& vertex) { + if (!contains(vertex)) return true; // remove vertex + return false; + }); + _vertices.erase(sitesEnd, _vertices.end()); + + // log removed vertices + int numOutside = _numVertices - _vertices.size(); + if (numOutside) _log->info("removed " + StringUtils::toString(numOutside, 'd') + " vertices outside of the domain"); + + _numVertices = _vertices.size(); } //////////////////////////////////////////////////////////////////// void TetraMeshSpatialGrid::buildMesh() { - tetgenio delaunay, refined; + tetgenio delaunay; buildDelaunay(delaunay); + if (refine()) { + tetgenio refined; refineDelaunay(delaunay, refined); storeTetrahedra(refined, true); } @@ -472,20 +493,9 @@ void TetraMeshSpatialGrid::buildMesh() void TetraMeshSpatialGrid::buildDelaunay(tetgenio& out) { - // remove vertices outside of the domain - auto sitesEnd = std::remove_if(_vertices.begin(), _vertices.end(), [this](const Vec& vertex) { - if (!contains(vertex)) return true; // remove vertex - return false; - }); - _vertices.erase(sitesEnd, _vertices.end()); - int numOutside = _numVertices - _vertices.size(); - _numVertices = _vertices.size(); - if (numOutside) _log->info("removed " + StringUtils::toString(numOutside, 'd') + " vertices outside of the domain"); - tetgenio in; tetgenbehavior behavior; - // in.firstnumber = 0; // remove me if no error in.numberofpoints = _vertices.size(); in.pointlist = new REAL[in.numberofpoints * 3]; for (int i = 0; i < in.numberofpoints; i++) @@ -495,7 +505,11 @@ void TetraMeshSpatialGrid::buildDelaunay(tetgenio& out) in.pointlist[i * 3 + 2] = _vertices[i].z(); } - behavior.psc = 1; // -s build Delaunay tetrahedralisation + behavior.psc = 1; // -s build Delaunay tetrahedralization + // correct output options for out + behavior.neighout = 2; // -nn + behavior.facesout = 1; // -f + behavior.zeroindex = 1; // -z _log->info("Building Delaunay triangulation using input vertices..."); tetrahedralize(&behavior, &in, &out); @@ -518,27 +532,26 @@ void TetraMeshSpatialGrid::refineDelaunay(tetgenio& in, tetgenio& out) _log->info("Refining triangulation..."); tetrahedralize(&behavior, &in, &out); - _log->info("Refined triangulation"); + _log->info("Built refined triangulation"); } //////////////////////////////////////////////////////////////////// -void TetraMeshSpatialGrid::storeTetrahedra(const tetgenio& out, bool storeVertices) +void TetraMeshSpatialGrid::storeTetrahedra(const tetgenio& final, bool storeVertices) { - // tranfser TetGen data to TetraMeshSpatialGrid data containers - _numCells = out.numberoftetrahedra; + _numCells = final.numberoftetrahedra; // replace old vertices if (storeVertices) { - _numVertices = out.numberofpoints; + _numVertices = final.numberofpoints; _vertices.resize(_numVertices); for (int i = 0; i < _numVertices; i++) { - double x = out.pointlist[3 * i + 0]; - double y = out.pointlist[3 * i + 1]; - double z = out.pointlist[3 * i + 2]; + double x = final.pointlist[3 * i + 0]; + double y = final.pointlist[3 * i + 1]; + double z = final.pointlist[3 * i + 2]; _vertices[i] = Vec(x, y, z); } @@ -553,14 +566,14 @@ void TetraMeshSpatialGrid::storeTetrahedra(const tetgenio& out, bool storeVertic // vertices for (int c = 0; c < 4; c++) { - vertexIndices[c] = out.tetrahedronlist[4 * i + c]; + vertexIndices[c] = final.tetrahedronlist[4 * i + c]; } // faces for (int f = 0; f < 4; f++) { // -1 if no neighbor - int ntetra = out.neighborlist[4 * i + f]; + int ntetra = final.neighborlist[4 * i + f]; // find which face is shared with neighbor int nface = -1; @@ -568,7 +581,7 @@ void TetraMeshSpatialGrid::storeTetrahedra(const tetgenio& out, bool storeVertic { for (int fn = 0; fn < 4; fn++) { - if (out.neighborlist[4 * ntetra + fn] == i) + if (final.neighborlist[4 * ntetra + fn] == i) { nface = fn; break; @@ -607,8 +620,8 @@ void TetraMeshSpatialGrid::storeTetrahedra(const tetgenio& out, bool storeVertic double avgVol = 1 / (double)_numCells; double varVol = (totalVol2 / _numCells / (V * V) - avgVol * avgVol); - // log neighbor statistics - _log->info("Done computing tetrahedralisation"); + // log statistics + _log->info("Done computing tetrahedralization"); _log->info(" Number of vertices " + std::to_string(_numVertices)); _log->info(" Number of tetrahedra " + std::to_string(_numCells)); _log->info(" Average volume fraction per cell: " + StringUtils::toString(avgVol, 'e')); @@ -683,7 +696,7 @@ void TetraMeshSpatialGrid::writeGridPlotFiles(const SimulationItem* probe) const SpatialGridPlotFile plotxyz(probe, probe->itemName() + "_grid_xyz"); // for each site, compute the corresponding cell and output its edges - _log->info("Writing plot files for tetrahedralisation with " + std::to_string(_numCells) + " tetrahedra"); + _log->info("Writing plot files for tetrahedralization with " + std::to_string(_numCells) + " tetrahedra"); _log->infoSetElapsed(_numCells); int numDone = 0; for (int i = 0; i < _numCells; i++) @@ -745,6 +758,9 @@ class TetraMeshSpatialGrid::MySegmentGenerator : public PathSegmentGenerator _mr = _grid->cellIndex(r()); _enteringFace = -1; + // sampled outside of convex hull + if (_mr < 0) return false; + // if the photon packet started outside the grid, return the corresponding nonzero-length segment; // otherwise fall through to determine the first actual segment if (ds() > 0.) return true; diff --git a/SKIRT/core/TetraMeshSpatialGrid.hpp b/SKIRT/core/TetraMeshSpatialGrid.hpp index e06d1c5c..7f986ac6 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.hpp +++ b/SKIRT/core/TetraMeshSpatialGrid.hpp @@ -18,7 +18,7 @@ class tetgenio; ////////////////////////////////////////////////////////////////////// /** TetraMeshSpatialGrid is a concrete subclass of the SpatialGrid class. It represents a - three-dimensional grid based on a Tetra tesselation of the cuboidal spatial domain of the + three-dimensional grid based on a tetrahedralization of the cuboidal spatial domain of the simulation. See the TetraMeshSnapshot class for more information on Tetra tesselations. The class offers several options for determining the positions of the sites generating the @@ -31,13 +31,13 @@ class tetgenio; class TetraMeshSpatialGrid : public BoxSpatialGrid { /** The enumeration type indicating the policy for determining the positions of the sites. */ - ENUM_DEF(Policy, Uniform, CentralPeak, DustDensity, ElectronDensity, GasDensity, ImportedSites) + ENUM_DEF(Policy, Uniform, CentralPeak, DustDensity, ElectronDensity, GasDensity, File) ENUM_VAL(Policy, Uniform, "random from uniform distribution") ENUM_VAL(Policy, CentralPeak, "random from distribution with a steep central peak") ENUM_VAL(Policy, DustDensity, "random from dust density distribution") ENUM_VAL(Policy, ElectronDensity, "random from electron density distribution") ENUM_VAL(Policy, GasDensity, "random from gas density distribution") - ENUM_VAL(Policy, ImportedSites, "positions of particles, sites or cells in imported distribution") + ENUM_VAL(Policy, File, "loaded from text column data file") ENUM_END() ITEM_CONCRETE(TetraMeshSpatialGrid, BoxSpatialGrid, "a tetrahedral spatial grid") @@ -52,6 +52,9 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid ATTRIBUTE_RELEVANT_IF(numSites, "policyUniform|policyCentralPeak|policyDustDensity|" "policyElectronDensity|policyGasDensity") + PROPERTY_STRING(filename, "the name of the file containing the site positions") + ATTRIBUTE_RELEVANT_IF(filename, "policyFile") + PROPERTY_BOOL(refine, "refine the grid to have higher quality cells by adding more vertices") ATTRIBUTE_DEFAULT_VALUE(refine, "false"); @@ -72,23 +75,27 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid //==================== Private construction ==================== private: + /** Private class to hold all infromation for a face of a tetrahedron to allow photon + traversal through it. */ struct Face { - Face(){}; - - Face(int ntetra, int nface, Vec normal) : _ntetra(ntetra), _nface(nface), _normal(normal) {} - int _ntetra; // index of neighbouring tetrahedron int _nface; // neighbouring face index Vec _normal; // outward facing normal + + Face(){}; + + Face(int ntetra, int nface, Vec normal) : _ntetra(ntetra), _nface(nface), _normal(normal) {} }; + /** Private class to hold all information for a tetrahedron to fully describe its geometry + and allow photon traversal through it. */ class Tetra { private: - const vector& _vertices; // reference to the full list of vertices - Box _extent; // bounding box of the tetrahedron - Vec _centroid; // barycenter of the tetrahedron + const vector& _vertices; // reference to all vertices, could be passed as arg but this is more convenient + Box _extent; // bounding box of the tetrahedron + Vec _centroid; // barycenter of the tetrahedron std::array _vertexIndices; // indices of the vertices in the full list std::array _faces; // face information @@ -120,17 +127,34 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid class BlockGrid; + /** This private function removes vertices that are outside the domain. */ + void removeOutside(); + + /** This private function builds the tetrahedral mesh. Starts by constructing the Delaunay + tetrahedralization and optionally refines it if the refine option is set to true. */ void buildMesh(); + /** This private function constructs the Delaunay tetrahedralization using the _vertices. */ void buildDelaunay(tetgenio& out); + /** This private function refines the Delaunay tetrahedralization to improve cell quality. + The refinement process is controlled by TetGen with default quality parameters. The + input is the initial Delaunay tetrahedralization in a tetgenio container. The output + is a tetgenio container with the refined Delaunay tetrahedralization. */ void refineDelaunay(tetgenio& in, tetgenio& out); - void storeTetrahedra(const tetgenio& out, bool storeVertices); + /** This private function stores the tetrahedra and vertices from the final tetgenio + container into the class members. The input is the tetgenio container with the + final tetrahedralization. The storeVertices parameter indicates whether to + overwrite the vertices with those from the tetgenio container. This function + also logs some cell statistics after storing the data. */ + void storeTetrahedra(const tetgenio& final, bool storeVertices); + /** This private function builds the search data structure for the tetrahedral mesh. + See the private BlockGrid class for more information. */ void buildSearch(); - //======================== Interrogation ======================= + //======================= Interrogation ======================= public: /** This function returns the number of cells in the grid. */ int numCells() const override; @@ -153,7 +177,7 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid /** This function returns a random location from the cell with index \f$m\f$. */ Position randomPositionInCell(int m) const override; - //====================== Path construction ===================== + //===================== Path construction ===================== /** This function creates and hands over ownership of a path segment generator (an instance of a PathSegmentGenerator subclass) appropriate for this spatial grid type. For the Tetra @@ -161,7 +185,7 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid class. */ std::unique_ptr createPathSegmentGenerator() const override; - //====================== Output ===================== + //===================== Output ===================== /** This function outputs the grid plot files; it is provided here because the regular mechanism does not apply. The function reconstructs the Tetra tesselation in order to From e0cf2fe145286f888490e216f98861db179f13c6 Mon Sep 17 00:00:00 2001 From: arlauwer Date: Fri, 13 Dec 2024 15:50:45 +0100 Subject: [PATCH 38/51] documentation --- SKIRT/core/TetraMeshSpatialGrid.cpp | 89 +++++++----- SKIRT/core/TetraMeshSpatialGrid.hpp | 206 ++++++++++++++++++++-------- 2 files changed, 199 insertions(+), 96 deletions(-) diff --git a/SKIRT/core/TetraMeshSpatialGrid.cpp b/SKIRT/core/TetraMeshSpatialGrid.cpp index 671a0fcb..cb147c1e 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.cpp +++ b/SKIRT/core/TetraMeshSpatialGrid.cpp @@ -4,12 +4,10 @@ ///////////////////////////////////////////////////////////////// */ #include "TetraMeshSpatialGrid.hpp" -#include "Configuration.hpp" #include "FatalError.hpp" #include "MediumSystem.hpp" #include "NR.hpp" #include "Random.hpp" -#include "SiteListInterface.hpp" #include "SpatialGridPlotFile.hpp" #include "StringUtils.hpp" #include "TextInFile.hpp" @@ -49,6 +47,7 @@ namespace return rv; } + // get the vertices around a face in clockwise order viewed from opposite vertex inline std::array clockwiseVertices(int face) { std::array cv = {(face + 3) % 4, (face + 2) % 4, (face + 1) % 4}; @@ -90,6 +89,8 @@ TetraMeshSpatialGrid::Tetra::Tetra(const vector& vertices, const array 1.0) // cut'n fold the cube into a prism @@ -162,6 +167,8 @@ double TetraMeshSpatialGrid::Tetra::generateBarycentric(double& s, double& t, do return 1 - u - t - s; } +////////////////////////////////////////////////////////////////////// + Position TetraMeshSpatialGrid::Tetra::generatePosition(Random* random) const { double s = random->uniform(); @@ -173,23 +180,29 @@ Position TetraMeshSpatialGrid::Tetra::generatePosition(Random* random) const return Position(r * vertex(0) + u * vertex(1) + t * vertex(2) + s * vertex(3)); } -// get the vertex using local indices [0, 3] +////////////////////////////////////////////////////////////////////// + Vec TetraMeshSpatialGrid::Tetra::vertex(int t) const { return _vertices[_vertexIndices[t]]; } -// get the edge t1->t2 using local indices [0, 3] +////////////////////////////////////////////////////////////////////// + Vec TetraMeshSpatialGrid::Tetra::edge(int t1, int t2) const { return vertex(t2) - vertex(t1); } +////////////////////////////////////////////////////////////////////// + double TetraMeshSpatialGrid::Tetra::volume() const { return 1 / 6. * abs(Vec::dot(Vec::cross(edge(0, 1), edge(0, 2)), edge(0, 3))); } +////////////////////////////////////////////////////////////////////// + double TetraMeshSpatialGrid::Tetra::diagonal() const { double sum = 0.0; @@ -203,16 +216,22 @@ double TetraMeshSpatialGrid::Tetra::diagonal() const return sqrt(sum / 6.0); } +////////////////////////////////////////////////////////////////////// + const array& TetraMeshSpatialGrid::Tetra::faces() const { return _faces; } +////////////////////////////////////////////////////////////////////// + const Vec& TetraMeshSpatialGrid::Tetra::centroid() const { return _centroid; } +////////////////////////////////////////////////////////////////////// + const Box& TetraMeshSpatialGrid::Tetra::extent() const { return _extent; @@ -220,15 +239,13 @@ const Box& TetraMeshSpatialGrid::Tetra::extent() const ////////////////////////////////////////////////////////////////////// -// This helper class organizes the cells into cuboidal blocks in a smart grid, -// such that it is easy to retrieve all cells inside a certain block given a position. class TetraMeshSpatialGrid::BlockGrid { const vector& _tetrahedra; // reference to list of all tetrahedra int _gridsize; // number of grid blocks in each spatial direction Array _xgrid, _ygrid, _zgrid; // the m+1 grid separation points for each spatial direction - vector> _listv; // the m*m*m lists of indices for blocks overlapping each grid block - int _pmin, _pmax; // minimum, maximum nr of blocks in list; total nr of blocks in listv + vector> _listv; // the m*m*m lists of indices for cells overlapping each grid block + int _pmin, _pmax; // statistics of the minimum and maximum number of cells per block public: // The constructor creates a cuboidal grid of the specified number of grid blocks in each @@ -280,6 +297,10 @@ class TetraMeshSpatialGrid::BlockGrid } } + // This function determines the grid separation points along a specified axis (x, y, or z) + // to ensure that the cells are evenly distributed across the grid blocks. It does this by + // binning the tetrahedra centers at a high resolution and then calculating the cumulative + // distribution to set the grid separation points. void makegrid(int axis, int gridsize, Array& grid, double cmin, double cmax) { int n = _tetrahedra.size(); @@ -530,6 +551,8 @@ void TetraMeshSpatialGrid::refineDelaunay(tetgenio& in, tetgenio& out) behavior.facesout = 1; // -f behavior.zeroindex = 1; // -z + behavior.quiet = 1; // -Q + _log->info("Refining triangulation..."); tetrahedralize(&behavior, &in, &out); _log->info("Built refined triangulation"); @@ -695,39 +718,37 @@ void TetraMeshSpatialGrid::writeGridPlotFiles(const SimulationItem* probe) const SpatialGridPlotFile plotyz(probe, probe->itemName() + "_grid_yz"); SpatialGridPlotFile plotxyz(probe, probe->itemName() + "_grid_xyz"); - // for each site, compute the corresponding cell and output its edges - _log->info("Writing plot files for tetrahedralization with " + std::to_string(_numCells) + " tetrahedra"); + // for each tetrahedron, compute its edges + _log->info("Writing plot files for tetrahedral grid with " + std::to_string(_numCells) + " tetrahedra"); _log->infoSetElapsed(_numCells); int numDone = 0; for (int i = 0; i < _numCells; i++) { const Tetra& tetra = _tetrahedra[i]; - vector coords; - coords.reserve(12); - vector indices; - indices.reserve(16); + vector coords(12); + vector indices(16); + // write each face as a polygon for (int v = 0; v < 4; v++) { - const Vec vertex = tetra.vertex(v); - coords.push_back(vertex.x()); - coords.push_back(vertex.y()); - coords.push_back(vertex.z()); + Vec vertex = tetra.vertex(v); + coords[3 * v + 0] = vertex.x(); + coords[3 * v + 1] = vertex.y(); + coords[3 * v + 2] = vertex.z(); // get vertices of opposite face std::array faceIndices = clockwiseVertices(v); - indices.push_back(3); // amount of vertices per face - indices.push_back(faceIndices[0]); - indices.push_back(faceIndices[1]); - indices.push_back(faceIndices[2]); + indices[4 * v + 0] = 3; // amount of vertices per face + indices[4 * v + 1] = faceIndices[0]; + indices[4 * v + 2] = faceIndices[1]; + indices[4 * v + 3] = faceIndices[2]; } const Box& extent = tetra.extent(); if (extent.zmin() <= 0 && extent.zmax() >= 0) plotxy.writePolyhedron(coords, indices); if (extent.ymin() <= 0 && extent.ymax() >= 0) plotxz.writePolyhedron(coords, indices); if (extent.xmin() <= 0 && extent.xmax() >= 0) plotyz.writePolyhedron(coords, indices); - if (i <= 1000) - plotxyz.writePolyhedron(coords, indices); // like TetraMeshSpatialGrid, but why even write at all? + if (i <= 1000) plotxyz.writePolyhedron(coords, indices); // log message if the minimum time has elapsed numDone++; @@ -750,23 +771,18 @@ class TetraMeshSpatialGrid::MySegmentGenerator : public PathSegmentGenerator { if (state() == State::Unknown) { - // try moving the photon packet inside the grid; if this is impossible, return an empty path - // this also changes the _state - if (!moveInside(_grid->extent(), _grid->_eps)) return false; + // moveInside does not move the photon packet inside the convex hull so it is currently disabled + // if (!moveInside(_grid->extent(), _grid->_eps)) return false; // get the index of the cell containing the current position _mr = _grid->cellIndex(r()); _enteringFace = -1; - // sampled outside of convex hull + // position is outside of convex hull if (_mr < 0) return false; - - // if the photon packet started outside the grid, return the corresponding nonzero-length segment; - // otherwise fall through to determine the first actual segment - if (ds() > 0.) return true; } - // intentionally falls through + // inside convex hull if (state() == State::Inside) { // loop in case no exit point was found (which should happen only rarely) @@ -786,13 +802,13 @@ class TetraMeshSpatialGrid::MySegmentGenerator : public PathSegmentGenerator // the translated Plücker moment in the local coordinate system Vec moment = Vec::cross(dir, pos - tetra.vertex(_enteringFace)); - // clockwise vertices around vertex 0 + // clockwise vertices around entering face std::array cv = clockwiseVertices(_enteringFace); // determine orientations for use in the decision tree double prod0 = Vec::dot(moment, tetra.edge(cv[0], _enteringFace)); int clock0 = prod0 < 0; - // if clockwise move clockwise else move cclockwise + // if clockwise move clockwise else move counterclockwise int i = clock0 ? 1 : 2; double prodi = Vec::dot(moment, tetra.edge(cv[i], _enteringFace)); int cclocki = prodi >= 0; @@ -828,6 +844,7 @@ class TetraMeshSpatialGrid::MySegmentGenerator : public PathSegmentGenerator // 0 1 -> 0 static constexpr int dtable[2][2] = {{1, 0}, {0, 2}}; leavingFace = cv[dtable[clock0][cclocki]]; + // plane intersection to leaving face const Vec& n = faces[leavingFace]._normal; const Vec& v = tetra.vertex(_enteringFace); double ndotk = Vec::dot(n, dir); diff --git a/SKIRT/core/TetraMeshSpatialGrid.hpp b/SKIRT/core/TetraMeshSpatialGrid.hpp index 7f986ac6..c592b6d6 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.hpp +++ b/SKIRT/core/TetraMeshSpatialGrid.hpp @@ -7,9 +7,7 @@ #define TETRAMESHSPATIALGRID_HPP #include "BoxSpatialGrid.hpp" -#include "DensityInCellInterface.hpp" #include "Log.hpp" -#include "Medium.hpp" #include "PathSegmentGenerator.hpp" #include @@ -17,20 +15,49 @@ class tetgenio; ////////////////////////////////////////////////////////////////////// -/** TetraMeshSpatialGrid is a concrete subclass of the SpatialGrid class. It represents a - three-dimensional grid based on a tetrahedralization of the cuboidal spatial domain of the - simulation. See the TetraMeshSnapshot class for more information on Tetra tesselations. - - The class offers several options for determining the positions of the sites generating the - Tetra tesselation. A specified number of sites can be distributed randomly over the domain, - either uniformly or with the same overall density distribution as the medium. Alternatively, - the positions can be copied from the sites in the imported distribution(s). - - Furthermore, the user can opt to perform a relaxation step on the site positions to avoid - overly elongated cells. */ +/** TetraMeshSpatialGrid is a concrete subclass of SpatialGrid representing a three-dimensional + tetrahedral mesh generated from a set of vertices. The resulting grid is always constrained + to lie within the convex hull of the point set. This means that the grid is not guaranteed + to fill the entire simulation domain! The grid is constructed using the open-source library + TetGen version 1.6.0 (released on August 31, 2020). TetGen is an advanced C++ tetrahedral + mesh generator with many features. This class uses the following key features of TetGen: + + - Delaunay Tetrahedralization: + Generates a unique Delaunay tetrahedral mesh from a set of vertices. + + - Mesh Refinement: + Refines the tetrahedral mesh using TetGen's Delaunay refinement algorithm. This option + is enabled through the \em refine property of this class. + + It should be noted that TetGen is a single-threaded library, but the algorithms used are + generally quite fast. The refinement process is by far the most time-consuming part of + the mesh generation. + + The Delaunay tetrahedralization tends to generate low-quality cells in 3D. While the 2D + Delaunay triangulation maximizes the smallest angle in each triangle, leading to more regular + cells, the 3D DT lacks this property. As a result, the cells in 3D tetrahedralization are often + elongated and less regular. To address this, Delaunay refinement algorithms are applied. TetGen + includes its own refinement process, which applies local mesh operations such as vertex + smoothing, edge/face swapping, edge contraction, and vertex insertion. This refinement attempts + to optimize several quality metrics such as the radius-edge ratio and the minimum dihedral angle. + + All refinement parameters are kept at their default values provided by TetGen. However, it is + uncertain whether this refinement process will greatly improve the grids for radiative transfer + applications. + + The positions of the vertices used for generating the tetrahedral mesh are determined by the + \em policy property. The available policies are: + + - Uniform: Randomly sampled from a uniform distribution. + - CentralPeak: Randomly sampled from a distribution with a steep central peak. + - DustDensity: Randomly sampled based on the dust density distribution. + - ElectronDensity: Randomly sampled based on the electron density distribution. + - GasDensity: Randomly sampled based on the gas density distribution. + - File: Loaded from a column data file specified by the \em filename property, + containing vertex coordinates (x, y, z) in each column. */ class TetraMeshSpatialGrid : public BoxSpatialGrid { - /** The enumeration type indicating the policy for determining the positions of the sites. */ + /** The enumeration type indicating the policy for determining the positions of the vertices. */ ENUM_DEF(Policy, Uniform, CentralPeak, DustDensity, ElectronDensity, GasDensity, File) ENUM_VAL(Policy, Uniform, "random from uniform distribution") ENUM_VAL(Policy, CentralPeak, "random from distribution with a steep central peak") @@ -43,19 +70,19 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid ITEM_CONCRETE(TetraMeshSpatialGrid, BoxSpatialGrid, "a tetrahedral spatial grid") ATTRIBUTE_TYPE_DISPLAYED_IF(TetraMeshSpatialGrid, "Level2") - PROPERTY_ENUM(policy, Policy, "the policy for determining the positions of the sites") + PROPERTY_ENUM(policy, Policy, "the policy for determining the positions of the vertices") ATTRIBUTE_DEFAULT_VALUE(policy, "DustDensity") - PROPERTY_INT(numSites, "the number of random sites to be used as vertices") + PROPERTY_INT(numSites, "the number of random positions to be used as vertices") ATTRIBUTE_MIN_VALUE(numSites, "4") ATTRIBUTE_DEFAULT_VALUE(numSites, "500") ATTRIBUTE_RELEVANT_IF(numSites, "policyUniform|policyCentralPeak|policyDustDensity|" "policyElectronDensity|policyGasDensity") - PROPERTY_STRING(filename, "the name of the file containing the site positions") + PROPERTY_STRING(filename, "the name of the file containing the vertex positions") ATTRIBUTE_RELEVANT_IF(filename, "policyFile") - PROPERTY_BOOL(refine, "refine the grid to have higher quality cells by adding more vertices") + PROPERTY_BOOL(refine, "refine the grid by performing local mesh operations") ATTRIBUTE_DEFAULT_VALUE(refine, "false"); ITEM_END() @@ -63,24 +90,24 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid //============= Construction - Setup - Destruction ============= public: - /** The destructor releases the Tetra mesh if this object owns it. */ + /** The destructor releases the BlockGrid search structure. */ ~TetraMeshSpatialGrid(); protected: - /** This function verifies that the attributes have been appropriately set, generates or - retrieves the site positions for constructing the Tetra tessellation according to the - configured policy, and finally constructs the Tetra tessellation through an instance of - the TetraMeshSnapshot class. */ + /** This function verifies that the attributes are correctly set, generates or retrieves + vertex positions based on the configured policy, builds (and optionally refines) the + tetrahedralization, and constructs the search structure to optimize the \em CellIndex + function. */ void setupSelfBefore() override; //==================== Private construction ==================== private: - /** Private class to hold all infromation for a face of a tetrahedron to allow photon - traversal through it. */ + /** Private class that represents a face of a tetrahedron, storing all relevant + information for photon traversal. */ struct Face { - int _ntetra; // index of neighbouring tetrahedron - int _nface; // neighbouring face index + int _ntetra; // cell index of neighbouring tetrahedron + int _nface; // index of the equivalent face in the neighbouring tetrahedron [0, 3] Vec _normal; // outward facing normal Face(){}; @@ -88,66 +115,89 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid Face(int ntetra, int nface, Vec normal) : _ntetra(ntetra), _nface(nface), _normal(normal) {} }; - /** Private class to hold all information for a tetrahedron to fully describe its geometry - and allow photon traversal through it. */ + /** Private class that represents a tetrahedron, storing all relevant information to fully + describe its geometry and allow photon traversal through it. */ class Tetra { private: const vector& _vertices; // reference to all vertices, could be passed as arg but this is more convenient Box _extent; // bounding box of the tetrahedron Vec _centroid; // barycenter of the tetrahedron - std::array _vertexIndices; // indices of the vertices in the full list - std::array _faces; // face information + std::array _vertexIndices; // indices of the vertices in the global vertex list + std::array _faces; // faces of the tetrahedron see struct Face public: + /** This constructor initializes the tetrahedron by setting its fields + and calculating the bounding box and centroid. */ Tetra(const vector& vertices, const std::array& vertexIndices, const std::array& faces); + /** This function finds the entering face or any face that is not the leaving face which can + act as the entering face in the traversal algorithm. */ int findEnteringFace(const Vec& pos, const Direction& dir) const; + /** This function checks if the given position is contained inside the tetrahedron. It starts by checking + if the position is inside the bounding box of the tetrahedron. */ bool contains(const Position& bfr) const; + /** This function generates three random barycentric coordinates for uniformly sampling inside this + tetrahedron. The fourth coordinate is calculated by ensuring their sum equals 1. + Source: Generating Random Points in a Tetrahedron: DOI 10.1080/10867651.2000.10487528 */ double generateBarycentric(double& s, double& t, double& u) const; + /** This function generates a random position inside the tetrahedron by generating random + barycentric coordinates and using the vertices of the tetrahedron. */ Position generatePosition(Random* random) const; + /** This function returns the vertex with index \f$t\f$ of the tetrahedron, where + \f$t \in \{0, 1, 2, 3\}\f$. */ Vec vertex(int t) const; + /** This function returns the edge from vertex \f$t1\f$ to \f$t2\f$ of the tetrahedron, + where \f$t1, t2 \in \{0, 1, 2, 3\}\f$. */ Vec edge(int t1, int t2) const; + /** This function calculates and returns the volume of the tetrahedron. */ double volume() const; + /** This function calculates and returns the approximate diagonal of the tetrahedron. */ double diagonal() const; + /** This function returns a reference to the faces of the tetrahedron. */ const std::array& faces() const; + /** This function returns the centroid of the tetrahedron. */ const Vec& centroid() const; + /** This function returns the extent of the tetrahedron. */ const Box& extent() const; }; + /** This private helper class organizes the cells into cuboidal blocks in a smart grid, such + that it is easy to retrieve all cells inside a certain block given a position. */ class BlockGrid; /** This private function removes vertices that are outside the domain. */ void removeOutside(); - /** This private function builds the tetrahedral mesh. Starts by constructing the Delaunay - tetrahedralization and optionally refines it if the refine option is set to true. */ + /** This private function builds the tetrahedral mesh. It starts by constructing the Delaunay + tetrahedralization and optionally refines it if the \em refine option is set to true. */ void buildMesh(); - /** This private function constructs the Delaunay tetrahedralization using the _vertices. */ + /** This private function constructs the Delaunay tetrahedralization using the vertices obtained + from the \em policy. The output is placed inside the tetgenio reference. */ void buildDelaunay(tetgenio& out); - /** This private function refines the Delaunay tetrahedralization to improve cell quality. - The refinement process is controlled by TetGen with default quality parameters. The - input is the initial Delaunay tetrahedralization in a tetgenio container. The output - is a tetgenio container with the refined Delaunay tetrahedralization. */ + /** This private function refines the Delaunay tetrahedralization to improve cell quality. The + refinement process is controlled by TetGen with default quality parameters. The input is + the initial Delaunay tetrahedralization in a tetgenio container. The output with the + refined Delaunay tetrahedralization is placed inside a tetgenio reference. */ void refineDelaunay(tetgenio& in, tetgenio& out); - /** This private function stores the tetrahedra and vertices from the final tetgenio - container into the class members. The input is the tetgenio container with the - final tetrahedralization. The storeVertices parameter indicates whether to - overwrite the vertices with those from the tetgenio container. This function - also logs some cell statistics after storing the data. */ + /** This private function stores the tetrahedra and vertices from the final tetgenio container + into the \em TetraMeshSpatialGrid members. The input is the tetgenio container with the final + tetrahedralization. The storeVertices parameter indicates whether to overwrite the vertices + with those from the tetgenio container. This function also logs some cell statistics after + it finishes storing the data. */ void storeTetrahedra(const tetgenio& final, bool storeVertices); /** This private function builds the search data structure for the tetrahedral mesh. @@ -163,33 +213,71 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid double volume(int m) const override; /** This function returns the approximate diagonal of the cell with index \f$m\f$. For a - Tetra grid, it returns \f$(3V)^(1/3)\f$ where \f$V\f$ is the volume of the cell. This - corresponds to the correct diagonal only for cubical cells. */ + tetrahedron, it takes the square root of the average of the squared edge lengths. */ double diagonal(int m) const override; - /** This function returns the index of the cell that contains the position \f${\bf{r}}\f$. */ + /** This function returns the index of the cell that contains the position \f${\bf{r}}\f$. + The function uses the data structure stored in the \em BlockGrid to accelerate the + search. If the position is outside the convex hull, the function returns -1. */ int cellIndex(Position bfr) const override; - /** This function returns the central location of the cell with index \f$m\f$. In this class - the function returns the centroid of the Tetra cell. */ + /** This function returns the centroid of the tetrahedron with index \f$m\f$. */ Position centralPositionInCell(int m) const override; - /** This function returns a random location from the cell with index \f$m\f$. */ + /** This function returns a random location from the tetrahedron with index \f$m\f$. */ Position randomPositionInCell(int m) const override; //===================== Path construction ===================== - /** This function creates and hands over ownership of a path segment generator (an instance of - a PathSegmentGenerator subclass) appropriate for this spatial grid type. For the Tetra - mesh grid, the path segment generator is actually implemented in the TetraMeshSnapshot - class. */ + /** This function creates and returns ownership of a path segment generator suitable for the + tetrahedral spatial grid, implemented as a private \em PathSegmentGenerator subclass. The + algorithm for constructing the path is taken from Maria et al. (2017). + + The algorithm uses Plücker coordinates to identify the exit face of a ray inside a given + tetrahedron. Plücker coordinates are a set of six values that describe a directed line in 3D space: + \f[\mathbf{\pi}_R = \{\mathbf{k} : \mathbf{k} \times \mathbf{r}\} = \{\mathbf{U}_R : \mathbf{V}_R\}\f] + where \f$\mathbf{r}\f$ is a position along the ray, and \f$\mathbf{k}\f$ is the ray's direction. + The Plücker product is defined as: + \f[\mathbf{\pi}_R \odot \mathbf{\pi}_S = \mathbf{U}_R \cdot \mathbf{V}_S + \mathbf{U}_S \cdot \mathbf{V}_R\f] + This product determines the relative orientation between two rays: + \f[\mathbf{\pi}_R \odot \mathbf{\pi}_S = \begin{cases} + > 0 & \iff S \text{ goes counterclockwise around } R\\ + < 0 & \iff S \text{ goes clockwise around } R\\ + = 0 & \iff S \text{ intersects or is parallel to } R \end{cases} \f] + A ray exits a tetrahedron through a particular face if the Plücker products with all three + clockwise-ordered edges of that face are negative. The algorithm in Maria et al. (2017) optimizes + this by requiring only two Plücker products to be evaluated. Our implementation is described below. + + In the first step, the function determines the cell index of the tetrahedron containing the starting + point. If none is found, the path is terminated. Before the traversal algorithm can commence, a + non-leaving face must be identified. This face acts as the entry face for the ray. Note that this + face does not necessarily have to be the actual entry face. This task is handled by the + \em findEnteringFace function of the \em Tetra class. + + Next, the traversal algorithm begins. The entering face is labeled as face 0, with its opposing vertex + labeled as vertex 0. We start by evaluating the Plücker product of the ray with the edge \f$1 \to 0\f$. + Based on whether this product is positive or negative, the next Plücker product to evaluate is determined. + If the product is negative (i.e., clockwise orientation), we evaluate the product with the edge in the + clockwise direction viewed from vertex 0. The same applies for a positive product. The exit face is + determined using the decision tree described in Maria et al. (2017). The distance traveled through the + cell is calculated using a simple line-plane intersection: + \f[s_i = \frac{\mathbf{n} \cdot (\mathbf{v} - \mathbf{r})}{\mathbf{n} \cdot \mathbf{k}}\f] + where \f$\mathbf{n}\f$ is the outward-pointing normal of the face, \f$\mathbf{v}\f$ is any vertex on + the exit face, and \f$\mathbf{r}\f$ is the current position. + + Although the algorithm described in Maria et al. (2017) works even if one of the Plücker products is zero, + we revert to a plane intersection algorithm in such cases. This approach is similar to the one used in the + \em VoronoiMeshSnapshot class, where the closest intersection distance with all faces is found. + + The algorithm continues until the exit face lies on the convex hull boundary. At this point, the path is + terminated. If neither the Maria et al. (2017) algorithm nor the plane intersection algorithm identifies + an exit face, the current point is advanced by a small distance, and the cell index is recalculated. */ std::unique_ptr createPathSegmentGenerator() const override; //===================== Output ===================== - /** This function outputs the grid plot files; it is provided here because the regular - mechanism does not apply. The function reconstructs the Tetra tesselation in order to - produce the coordinates of the Tetra cell vertices. */ + /** This function outputs the grid plot files. It writes each tetrahedral face as a triangle + to the grid plot file. */ void writeGridPlotFiles(const SimulationItem* probe) const override; //======================== Data Members ======================== @@ -197,17 +285,15 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid private: // data members initialized by setupSelfBefore() Log* _log{nullptr}; - - // data members initialized during configuration double _eps{0.}; // small fraction of extent + // data members describing the tetrahedralization int _numCells; // number of Tetra cells and centroids int _numVertices; // vertices are added/removed as the grid is built and refined vector _tetrahedra; vector _vertices; - // smart grid that contains all cell information - // allows for efficiently locating the cell at a given location + // smart grid that organizes the tetrahedra into blocks BlockGrid* _blocks{nullptr}; // allow our path segment generator to access our private data members From 896e7d6db422c254d0b31cba12c623b00c4e5ce3 Mon Sep 17 00:00:00 2001 From: arlauwer Date: Fri, 13 Dec 2024 16:37:17 +0100 Subject: [PATCH 39/51] rename property and fix small oversight in path generator --- SKIRT/core/TetraMeshSpatialGrid.cpp | 37 ++++++++++++++++------------- SKIRT/core/TetraMeshSpatialGrid.hpp | 8 +++---- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/SKIRT/core/TetraMeshSpatialGrid.cpp b/SKIRT/core/TetraMeshSpatialGrid.cpp index cb147c1e..a32d30e6 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.cpp +++ b/SKIRT/core/TetraMeshSpatialGrid.cpp @@ -381,14 +381,14 @@ void TetraMeshSpatialGrid::setupSelfBefore() _log = find(); _eps = 1e-12 * widths().norm(); - // determine an appropriate set of sites and construct the Tetra mesh + // determine an appropriate set of samples and construct the Tetra mesh switch (_policy) { case Policy::Uniform: { auto random = find(); - _vertices.resize(_numSites); - for (int m = 0; m != _numSites; ++m) _vertices[m] = random->position(extent()); + _vertices.resize(_numSamples); + for (int m = 0; m != _numSamples; ++m) _vertices[m] = random->position(extent()); break; } case Policy::CentralPeak: @@ -396,9 +396,9 @@ void TetraMeshSpatialGrid::setupSelfBefore() auto random = find(); const int a = 1000; // steepness of the peak; the central 1/a portion is NOT covered const double rscale = extent().rmax().norm(); - _vertices.resize(_numSites); + _vertices.resize(_numSamples); _vertices[0] = Vec(0, 0, 0); - for (int m = 1; m != _numSites;) // skip first particle so that it remains (0,0,0) + for (int m = 1; m != _numSamples;) // skip first particle so that it remains (0,0,0) { double r = rscale * pow(1. / a, random->uniform()); // random distribution according to 1/x Direction k = random->direction(); @@ -416,10 +416,10 @@ void TetraMeshSpatialGrid::setupSelfBefore() for (auto medium : ms->media()) if (medium->mix()->isDust()) media.push_back(medium); for (auto medium : media) weights.push_back(medium->mass()); - vector sites = sampleMedia(media, weights, extent(), _numSites); - int n = sites.size(); + vector samples = sampleMedia(media, weights, extent(), _numSamples); + int n = samples.size(); _vertices.resize(n); - for (int m = 0; m != n; ++m) _vertices[m] = sites[m]; + for (int m = 0; m != n; ++m) _vertices[m] = samples[m]; break; } case Policy::ElectronDensity: @@ -431,10 +431,10 @@ void TetraMeshSpatialGrid::setupSelfBefore() for (auto medium : ms->media()) if (medium->mix()->isElectrons()) media.push_back(medium); for (auto medium : media) weights.push_back(medium->number()); - vector sites = sampleMedia(media, weights, extent(), _numSites); - int n = sites.size(); + vector samples = sampleMedia(media, weights, extent(), _numSamples); + int n = samples.size(); _vertices.resize(n); - for (int m = 0; m != n; ++m) _vertices[m] = sites[m]; + for (int m = 0; m != n; ++m) _vertices[m] = samples[m]; break; } case Policy::GasDensity: @@ -446,10 +446,10 @@ void TetraMeshSpatialGrid::setupSelfBefore() for (auto medium : ms->media()) if (medium->mix()->isGas()) media.push_back(medium); for (auto medium : media) weights.push_back(medium->number()); - vector sites = sampleMedia(media, weights, extent(), _numSites); - int n = sites.size(); + vector samples = sampleMedia(media, weights, extent(), _numSamples); + int n = samples.size(); _vertices.resize(n); - for (int m = 0; m != n; ++m) _vertices[m] = sites[m]; + for (int m = 0; m != n; ++m) _vertices[m] = samples[m]; break; } case Policy::File: @@ -478,11 +478,11 @@ void TetraMeshSpatialGrid::removeOutside() _numVertices = _vertices.size(); // remove vertices outside of the domain - auto sitesEnd = std::remove_if(_vertices.begin(), _vertices.end(), [this](const Vec& vertex) { + auto verticesEnd = std::remove_if(_vertices.begin(), _vertices.end(), [this](const Vec& vertex) { if (!contains(vertex)) return true; // remove vertex return false; }); - _vertices.erase(sitesEnd, _vertices.end()); + _vertices.erase(verticesEnd, _vertices.end()); // log removed vertices int numOutside = _numVertices - _vertices.size(); @@ -531,8 +531,10 @@ void TetraMeshSpatialGrid::buildDelaunay(tetgenio& out) behavior.neighout = 2; // -nn behavior.facesout = 1; // -f behavior.zeroindex = 1; // -z + behavior.quiet = 1; // -Q - _log->info("Building Delaunay triangulation using input vertices..."); + _log->info("Building Delaunay triangulation using " + StringUtils::toString(_numVertices, 'd') + + " input vertices..."); tetrahedralize(&behavior, &in, &out); _log->info("Built Delaunay triangulation"); } @@ -776,6 +778,7 @@ class TetraMeshSpatialGrid::MySegmentGenerator : public PathSegmentGenerator // get the index of the cell containing the current position _mr = _grid->cellIndex(r()); + if (_mr > 0) setState(State::Inside); _enteringFace = -1; // position is outside of convex hull diff --git a/SKIRT/core/TetraMeshSpatialGrid.hpp b/SKIRT/core/TetraMeshSpatialGrid.hpp index c592b6d6..15669570 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.hpp +++ b/SKIRT/core/TetraMeshSpatialGrid.hpp @@ -73,10 +73,10 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid PROPERTY_ENUM(policy, Policy, "the policy for determining the positions of the vertices") ATTRIBUTE_DEFAULT_VALUE(policy, "DustDensity") - PROPERTY_INT(numSites, "the number of random positions to be used as vertices") - ATTRIBUTE_MIN_VALUE(numSites, "4") - ATTRIBUTE_DEFAULT_VALUE(numSites, "500") - ATTRIBUTE_RELEVANT_IF(numSites, "policyUniform|policyCentralPeak|policyDustDensity|" + PROPERTY_INT(numSamples, "the number of random positions to be used as vertices") + ATTRIBUTE_MIN_VALUE(numSamples, "4") + ATTRIBUTE_DEFAULT_VALUE(numSamples, "500") + ATTRIBUTE_RELEVANT_IF(numSamples, "policyUniform|policyCentralPeak|policyDustDensity|" "policyElectronDensity|policyGasDensity") PROPERTY_STRING(filename, "the name of the file containing the vertex positions") From d5dbc24db1233e5b7b7582a583ea9445bfc58111 Mon Sep 17 00:00:00 2001 From: arlauwer Date: Mon, 16 Dec 2024 13:40:34 +0100 Subject: [PATCH 40/51] final refactoring and documentation --- SKIRT/core/TetraMeshSpatialGrid.cpp | 46 +++++++-------- SKIRT/core/TetraMeshSpatialGrid.hpp | 89 +++++++++++++++-------------- 2 files changed, 67 insertions(+), 68 deletions(-) diff --git a/SKIRT/core/TetraMeshSpatialGrid.cpp b/SKIRT/core/TetraMeshSpatialGrid.cpp index a32d30e6..8cfe9017 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.cpp +++ b/SKIRT/core/TetraMeshSpatialGrid.cpp @@ -20,7 +20,7 @@ using std::array; namespace { // returns the linear index for element (i,j,k) in a p*p*p table - inline int index(int p, int i, int j, int k) + inline int blockIndex(int p, int i, int j, int k) { return ((i * p) + j) * p + k; } @@ -48,9 +48,9 @@ namespace } // get the vertices around a face in clockwise order viewed from opposite vertex - inline std::array clockwiseVertices(int face) + inline array clockwiseVertices(int face) { - std::array cv = {(face + 3) % 4, (face + 2) % 4, (face + 1) % 4}; + array cv = {(face + 3) % 4, (face + 2) % 4, (face + 1) % 4}; // if face is even we should swap two edges if (face % 2 == 0) std::swap(cv[0], cv[2]); return cv; @@ -96,15 +96,14 @@ int TetraMeshSpatialGrid::Tetra::findEnteringFace(const Vec& pos, const Directio int enteringFace = -1; // clockwise and cclockwise adjacent faces when checking edge v1->v2 static constexpr int etable[6][2] = {{3, 2}, {1, 3}, {2, 1}, {3, 0}, {0, 2}, {1, 0}}; // make this static? - // try all 6 edges because of very rare edge cases where ray is inside edge - // having only 1 non-zero Plücker product int e = 0; - for (int v1 = 0; v1 < 3; v1++) + // loop over all 6 edges because of rare cases where ray is inside edge and only 1 non-zero Plücker product + for (int t1 = 0; t1 < 3; t1++) { - for (int v2 = v1 + 1; v2 < 4; v2++) + for (int t2 = t1 + 1; t2 < 4; t2++) { - Vec moment12 = Vec::cross(dir, pos - vertex(v1)); - double prod12 = Vec::dot(moment12, edge(v1, v2)); + Vec moment12 = Vec::cross(dir, pos - vertex(t1)); + double prod12 = Vec::dot(moment12, edge(t1, t2)); if (prod12 != 0.) { enteringFace = prod12 < 0 ? etable[e][0] : etable[e][1]; @@ -282,7 +281,7 @@ class TetraMeshSpatialGrid::BlockGrid for (int j = j1; j <= j2; j++) for (int k = k1; k <= k2; k++) { - _listv[index(gridsize, i, j, k)].push_back(m); + _listv[blockIndex(gridsize, i, j, k)].push_back(m); } } @@ -324,8 +323,8 @@ class TetraMeshSpatialGrid::BlockGrid // determine grid separation points based on the cumulative distribution grid.resize(gridsize + 1); grid[0] = -std::numeric_limits::infinity(); - int perblock = n / gridsize; // target number of particles per block - int cumul = 0; // cumulative number of particles in processed bins + int perblock = n / gridsize; // target number of centroids per block + int cumul = 0; // cumulative number of centroids in processed bins int gridindex = 1; // index of the next grid separation point to be filled for (int binindex = 0; binindex < nbins; binindex++) { @@ -357,7 +356,7 @@ class TetraMeshSpatialGrid::BlockGrid int k = NR::locateClip(_zgrid, r.z()); // search the list of blocks for that grid block - for (int m : _listv[index(_gridsize, i, j, k)]) + for (int m : _listv[blockIndex(_gridsize, i, j, k)]) { if (_tetrahedra[m].contains(r)) return m; } @@ -398,7 +397,7 @@ void TetraMeshSpatialGrid::setupSelfBefore() const double rscale = extent().rmax().norm(); _vertices.resize(_numSamples); _vertices[0] = Vec(0, 0, 0); - for (int m = 1; m != _numSamples;) // skip first particle so that it remains (0,0,0) + for (int m = 1; m != _numSamples;) // skip first vertex so that it remains (0,0,0) { double r = rscale * pow(1. / a, random->uniform()); // random distribution according to 1/x Direction k = random->direction(); @@ -526,12 +525,12 @@ void TetraMeshSpatialGrid::buildDelaunay(tetgenio& out) in.pointlist[i * 3 + 2] = _vertices[i].z(); } - behavior.psc = 1; // -s build Delaunay tetrahedralization + behavior.quiet = 1; // -Q no console logging + behavior.psc = 1; // -s build Delaunay tetrahedralization // correct output options for out behavior.neighout = 2; // -nn behavior.facesout = 1; // -f behavior.zeroindex = 1; // -z - behavior.quiet = 1; // -Q _log->info("Building Delaunay triangulation using " + StringUtils::toString(_numVertices, 'd') + " input vertices..."); @@ -545,6 +544,7 @@ void TetraMeshSpatialGrid::refineDelaunay(tetgenio& in, tetgenio& out) { tetgenbehavior behavior; + behavior.quiet = 1; // -Q no console logging // tetgen refine options behavior.refine = 1; // -r behavior.quality = 1; // -q with default tetgen options for quality @@ -553,8 +553,6 @@ void TetraMeshSpatialGrid::refineDelaunay(tetgenio& in, tetgenio& out) behavior.facesout = 1; // -f behavior.zeroindex = 1; // -z - behavior.quiet = 1; // -Q - _log->info("Refining triangulation..."); tetrahedralize(&behavior, &in, &out); _log->info("Built refined triangulation"); @@ -585,8 +583,8 @@ void TetraMeshSpatialGrid::storeTetrahedra(const tetgenio& final, bool storeVert _tetrahedra.reserve(_numCells); // no default constructor for Tetra for (int i = 0; i < _numCells; i++) { - std::array vertexIndices; - std::array faces; + array vertexIndices; + array faces; // vertices for (int c = 0; c < 4; c++) @@ -615,7 +613,7 @@ void TetraMeshSpatialGrid::storeTetrahedra(const tetgenio& final, bool storeVert } // compute outward facing normal of face - std::array cv = clockwiseVertices(f); + array cv = clockwiseVertices(f); Vec v0 = _vertices[vertexIndices[cv[0]]]; Vec e12 = _vertices[vertexIndices[cv[1]]] - v0; Vec e13 = _vertices[vertexIndices[cv[2]]] - v0; @@ -661,7 +659,7 @@ void TetraMeshSpatialGrid::buildSearch() { int gridsize = max(20, static_cast(pow(_tetrahedra.size(), 1. / 3.) / 5)); string size = std::to_string(gridsize); - _log->info("Constructing intermediate " + size + "x" + size + "x" + size + " grid for tetrahedra..."); + _log->info("Constructing intermediate " + size + "x" + size + "x" + size + " BlockGrid for tetrahedra..."); _blocks = new BlockGrid(_tetrahedra, extent(), gridsize); _log->info(" Smallest number of tetrahedra per grid block: " + std::to_string(_blocks->minCellRefsPerBlock())); _log->info(" Largest number of tetrahedra per grid block: " + std::to_string(_blocks->maxCellRefsPerBlock())); @@ -739,7 +737,7 @@ void TetraMeshSpatialGrid::writeGridPlotFiles(const SimulationItem* probe) const coords[3 * v + 2] = vertex.z(); // get vertices of opposite face - std::array faceIndices = clockwiseVertices(v); + array faceIndices = clockwiseVertices(v); indices[4 * v + 0] = 3; // amount of vertices per face indices[4 * v + 1] = faceIndices[0]; indices[4 * v + 2] = faceIndices[1]; @@ -806,7 +804,7 @@ class TetraMeshSpatialGrid::MySegmentGenerator : public PathSegmentGenerator Vec moment = Vec::cross(dir, pos - tetra.vertex(_enteringFace)); // clockwise vertices around entering face - std::array cv = clockwiseVertices(_enteringFace); + array cv = clockwiseVertices(_enteringFace); // determine orientations for use in the decision tree double prod0 = Vec::dot(moment, tetra.edge(cv[0], _enteringFace)); diff --git a/SKIRT/core/TetraMeshSpatialGrid.hpp b/SKIRT/core/TetraMeshSpatialGrid.hpp index 15669570..9e6c98fb 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.hpp +++ b/SKIRT/core/TetraMeshSpatialGrid.hpp @@ -15,35 +15,34 @@ class tetgenio; ////////////////////////////////////////////////////////////////////// -/** TetraMeshSpatialGrid is a concrete subclass of SpatialGrid representing a three-dimensional - tetrahedral mesh generated from a set of vertices. The resulting grid is always constrained - to lie within the convex hull of the point set. This means that the grid is not guaranteed - to fill the entire simulation domain! The grid is constructed using the open-source library - TetGen version 1.6.0 (released on August 31, 2020). TetGen is an advanced C++ tetrahedral - mesh generator with many features. This class uses the following key features of TetGen: +/** TetraMeshSpatialGrid is a concrete subclass of SpatialGrid representing a 3D tetrahedral mesh + generated from a set of vertices. The resulting grid is always constrained to lie within the + convex hull of the point set. This implies that the grid is not guaranteed to fill the entire + simulation domain. The grid is constructed using the open-source library TetGen version 1.6.0 + (released on August 31, 2020). TetGen is an advanced C++ tetrahedral mesh generator with many + features. This class uses the following key features of TetGen: - Delaunay Tetrahedralization: - Generates a unique Delaunay tetrahedral mesh from a set of vertices. + Generate a unique Delaunay tetrahedral mesh from a given set of vertices. - Mesh Refinement: - Refines the tetrahedral mesh using TetGen's Delaunay refinement algorithm. This option + Refine the tetrahedral mesh using TetGen's Delaunay refinement algorithm. This option is enabled through the \em refine property of this class. It should be noted that TetGen is a single-threaded library, but the algorithms used are - generally quite fast. The refinement process is by far the most time-consuming part of - the mesh generation. - - The Delaunay tetrahedralization tends to generate low-quality cells in 3D. While the 2D - Delaunay triangulation maximizes the smallest angle in each triangle, leading to more regular - cells, the 3D DT lacks this property. As a result, the cells in 3D tetrahedralization are often - elongated and less regular. To address this, Delaunay refinement algorithms are applied. TetGen - includes its own refinement process, which applies local mesh operations such as vertex - smoothing, edge/face swapping, edge contraction, and vertex insertion. This refinement attempts - to optimize several quality metrics such as the radius-edge ratio and the minimum dihedral angle. - - All refinement parameters are kept at their default values provided by TetGen. However, it is - uncertain whether this refinement process will greatly improve the grids for radiative transfer - applications. + generally quite fast. The refinement process is by far the most time-consuming part of the + mesh generation. + + The 3D Delaunay tetrahedralization often contains cells less suited for a computational grid. + While the 2D Delaunay triangulation maximizes the smallest angle in each triangle, resulting + in more regular cells, the 3D version lacks this property. Consequently, 3D tetrahedralizations + frequently contain elongated, irregular cells. To address this, Delaunay refinement algorithms + can be used. TetGen implements its own refinement process, applying local mesh operations like + vertex smoothing, edge and face swapping, edge contraction, and vertex insertion. This + refinement aims to optimize quality metrics such as the radius-edge ratio and the minimum + dihedral angle. All refinement parameters are kept at their default values provided by TetGen. + However, it is uncertain whether this refinement process will greatly improve the grids for + radiative transfer applications. The positions of the vertices used for generating the tetrahedral mesh are determined by the \em policy property. The available policies are: @@ -77,7 +76,7 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid ATTRIBUTE_MIN_VALUE(numSamples, "4") ATTRIBUTE_DEFAULT_VALUE(numSamples, "500") ATTRIBUTE_RELEVANT_IF(numSamples, "policyUniform|policyCentralPeak|policyDustDensity|" - "policyElectronDensity|policyGasDensity") + "policyElectronDensity|policyGasDensity") PROPERTY_STRING(filename, "the name of the file containing the vertex positions") ATTRIBUTE_RELEVANT_IF(filename, "policyFile") @@ -90,7 +89,7 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid //============= Construction - Setup - Destruction ============= public: - /** The destructor releases the BlockGrid search structure. */ + /** This destructor releases the BlockGrid search structure. */ ~TetraMeshSpatialGrid(); protected: @@ -102,7 +101,7 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid //==================== Private construction ==================== private: - /** Private class that represents a face of a tetrahedron, storing all relevant + /** Private struct that represents a face of a tetrahedron, storing all relevant information for photon traversal. */ struct Face { @@ -124,23 +123,24 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid Box _extent; // bounding box of the tetrahedron Vec _centroid; // barycenter of the tetrahedron std::array _vertexIndices; // indices of the vertices in the global vertex list - std::array _faces; // faces of the tetrahedron see struct Face + std::array _faces; // faces of the tetrahedron, see struct Face public: - /** This constructor initializes the tetrahedron by setting its fields - and calculating the bounding box and centroid. */ + /** This constructor initializes the tetrahedron by setting its fields and calculating the + bounding box and centroid. */ Tetra(const vector& vertices, const std::array& vertexIndices, const std::array& faces); - /** This function finds the entering face or any face that is not the leaving face which can - act as the entering face in the traversal algorithm. */ + /** This function finds a face that is not the leaving face which can act as the entering + face in the traversal algorithm. */ int findEnteringFace(const Vec& pos, const Direction& dir) const; - /** This function checks if the given position is contained inside the tetrahedron. It starts by checking - if the position is inside the bounding box of the tetrahedron. */ + /** This function checks if the given position is contained inside the tetrahedron. + It frist checks if the position is inside the bounding box of the tetrahedron. */ bool contains(const Position& bfr) const; - /** This function generates three random barycentric coordinates for uniformly sampling inside this - tetrahedron. The fourth coordinate is calculated by ensuring their sum equals 1. + /** This function generates three random barycentric coordinates for uniformly sampling + inside this tetrahedron. The fourth coordinate is calculated by ensuring their sum + equals 1, i.e. r=1-s-t-u. Source: Generating Random Points in a Tetrahedron: DOI 10.1080/10867651.2000.10487528 */ double generateBarycentric(double& s, double& t, double& u) const; @@ -159,10 +159,11 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid /** This function calculates and returns the volume of the tetrahedron. */ double volume() const; - /** This function calculates and returns the approximate diagonal of the tetrahedron. */ + /** This function calculates and returns the approximate diagonal of the tetrahedron. + It calculates the square root of the average of the squared edge lengths. */ double diagonal() const; - /** This function returns a reference to the faces of the tetrahedron. */ + /** This function returns a reference to an array of the faces of the tetrahedron. */ const std::array& faces() const; /** This function returns the centroid of the tetrahedron. */ @@ -189,15 +190,15 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid /** This private function refines the Delaunay tetrahedralization to improve cell quality. The refinement process is controlled by TetGen with default quality parameters. The input is - the initial Delaunay tetrahedralization in a tetgenio container. The output with the - refined Delaunay tetrahedralization is placed inside a tetgenio reference. */ + the initial Delaunay tetrahedralization in the \em in tetgenio reference. The output, with + the refined Delaunay tetrahedralization, is placed inside the \em out tetgenio reference. */ void refineDelaunay(tetgenio& in, tetgenio& out); - /** This private function stores the tetrahedra and vertices from the final tetgenio container - into the \em TetraMeshSpatialGrid members. The input is the tetgenio container with the final - tetrahedralization. The storeVertices parameter indicates whether to overwrite the vertices + /** This private function stores the tetrahedra and vertices from the \em final tetgenio container + into the \em TetraMeshSpatialGrid members. The input is the tetgenio reference with the final + tetrahedralization. The \em storeVertices parameter indicates whether to overwrite the vertices with those from the tetgenio container. This function also logs some cell statistics after - it finishes storing the data. */ + it finishes transferring the data. */ void storeTetrahedra(const tetgenio& final, bool storeVertices); /** This private function builds the search data structure for the tetrahedral mesh. @@ -288,8 +289,8 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid double _eps{0.}; // small fraction of extent // data members describing the tetrahedralization - int _numCells; // number of Tetra cells and centroids - int _numVertices; // vertices are added/removed as the grid is built and refined + int _numCells; // total number of tetrahedra + int _numVertices; // total number of vertices vector _tetrahedra; vector _vertices; From 4353935233a5d2215149384167c575fd8f0e6173 Mon Sep 17 00:00:00 2001 From: arlauwer Date: Mon, 16 Dec 2024 13:42:18 +0100 Subject: [PATCH 41/51] remove debug files --- configSKIRT_debug.sh | 44 -------------------------------------------- makeSKIRT_debug.sh | 37 ------------------------------------- 2 files changed, 81 deletions(-) delete mode 100755 configSKIRT_debug.sh delete mode 100755 makeSKIRT_debug.sh diff --git a/configSKIRT_debug.sh b/configSKIRT_debug.sh deleted file mode 100755 index 891a818c..00000000 --- a/configSKIRT_debug.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/bin/bash -# (use "chmod +rx scriptname" to make script executable) -# -# For use on any Unix system, including Mac OS X and Linux -# -# Execute this script with "git" as default directory to configure -# the release build options for your current local copy of the code -# -# To adjust a build option, enter: ./configSKIRT.sh = -# For example, to enable MPI, enter: ./configSKIRT.sh BUILD_WITH_MPI=ON -# - -# -------------------------------------------------------------------- - -# Look for cmake in the default path; exit with an error if we don't find it -CMAKEPATH="$(which cmake)" -if [ "$CMAKEPATH" == "" ] -then -echo -echo Fatal error: there is no cmake in the default path -echo -exit -else -echo -echo Using $CMAKEPATH to generate build files -echo -fi - -# Assemble the user options from the script arguments -USEROPTIONS="" -for ARGUMENT in "$@" -do - USEROPTIONS="$USEROPTIONS -D$ARGUMENT" -done - -# Generate the build files -$CMAKEPATH -E make_directory ../debug -$CMAKEPATH -E chdir ../debug $CMAKEPATH $USEROPTIONS -DCMAKE_BUILD_TYPE:STRING=Debug -L ../git - -# Provide instructions to the user -echo -echo "To adjust build options, enter: ./configSKIRT.sh = = ..." -echo "For example, to enable MPI, enter: ./configSKIRT.sh BUILD_WITH_MPI=ON" -echo diff --git a/makeSKIRT_debug.sh b/makeSKIRT_debug.sh deleted file mode 100755 index 5f31e233..00000000 --- a/makeSKIRT_debug.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/bash -# (use "chmod +rx scriptname" to make script executable) -# -# For use on any Unix system, including Mac OS X and Linux -# -# Execute this script with "git" as default directory to -# build a release version of skirt in the "release" directory -# using your current local copy of the code -# -# By default the build uses a single thread; you can specify the -# number of parallel threads as the first command line argument -# - -# -------------------------------------------------------------------- - -# Look for cmake in the default path; exit with an error if we don't find it -CMAKEPATH="$(which cmake)" -if [ "$CMAKEPATH" == "" ] -then -echo -echo Fatal error: there is no cmake in the default path -echo -exit -else -echo -echo Using $CMAKEPATH to generate build files -echo -fi - -# Generate the build files -$CMAKEPATH -E make_directory ../debug -$CMAKEPATH -E chdir ../debug $CMAKEPATH -DCMAKE_BUILD_TYPE:STRING=Debug -DCMAKE_CXX_FLAGS:STRING="-O0 -g" -L ../git -echo - -# Perform the build -make -j ${1:-1} -C ../debug -echo From 9ce041188b4b30f6fbbe5568434a05bd816bbaa7 Mon Sep 17 00:00:00 2001 From: Peter Camps Date: Mon, 16 Dec 2024 16:56:11 +0100 Subject: [PATCH 42/51] avoid warnings with clang - not yet for other compilers --- SKIRT/core/CMakeLists.txt | 3 ++- SKIRT/core/TetraMeshSpatialGrid.cpp | 4 ++-- SKIRT/core/TetraMeshSpatialGrid.hpp | 28 ++++++++++++++-------------- SMILE/build/CompilerFlags.cmake | 5 +++-- 4 files changed, 21 insertions(+), 19 deletions(-) diff --git a/SKIRT/core/CMakeLists.txt b/SKIRT/core/CMakeLists.txt index c97f59e2..dd630882 100644 --- a/SKIRT/core/CMakeLists.txt +++ b/SKIRT/core/CMakeLists.txt @@ -23,7 +23,8 @@ include_directories(../../SMILE/schema ../../SMILE/fundamentals) # add SKIRT library dependencies target_link_libraries(${TARGET} fitsio voro mpi utils tetgen) -include_directories(../fitsio ../voro ../mpi ../utils ../tetgen) +include_directories(../fitsio ../voro ../mpi ../utils) +include_directories(SYSTEM ../tetgen) # suppress warnings in tetgen header # adjust C++ compiler flags to our needs include("../../SMILE/build/CompilerFlags.cmake") diff --git a/SKIRT/core/TetraMeshSpatialGrid.cpp b/SKIRT/core/TetraMeshSpatialGrid.cpp index 8cfe9017..09b30e29 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.cpp +++ b/SKIRT/core/TetraMeshSpatialGrid.cpp @@ -310,7 +310,7 @@ class TetraMeshSpatialGrid::BlockGrid vector bins(nbins); for (const Tetra& tetra : _tetrahedra) { - double center; + double center = 0.; switch (axis) { case 0: center = tetra.centroid().x(); break; @@ -640,7 +640,7 @@ void TetraMeshSpatialGrid::storeTetrahedra(const tetgenio& final, bool storeVert double V = Box::volume(); minVol /= V; maxVol /= V; - double avgVol = 1 / (double)_numCells; + double avgVol = 1. / _numCells; double varVol = (totalVol2 / _numCells / (V * V) - avgVol * avgVol); // log statistics diff --git a/SKIRT/core/TetraMeshSpatialGrid.hpp b/SKIRT/core/TetraMeshSpatialGrid.hpp index 9e6c98fb..863bc683 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.hpp +++ b/SKIRT/core/TetraMeshSpatialGrid.hpp @@ -21,14 +21,14 @@ class tetgenio; simulation domain. The grid is constructed using the open-source library TetGen version 1.6.0 (released on August 31, 2020). TetGen is an advanced C++ tetrahedral mesh generator with many features. This class uses the following key features of TetGen: - - - Delaunay Tetrahedralization: + + - Delaunay Tetrahedralization: Generate a unique Delaunay tetrahedral mesh from a given set of vertices. - - - Mesh Refinement: + + - Mesh Refinement: Refine the tetrahedral mesh using TetGen's Delaunay refinement algorithm. This option is enabled through the \em refine property of this class. - + It should be noted that TetGen is a single-threaded library, but the algorithms used are generally quite fast. The refinement process is by far the most time-consuming part of the mesh generation. @@ -43,16 +43,16 @@ class tetgenio; dihedral angle. All refinement parameters are kept at their default values provided by TetGen. However, it is uncertain whether this refinement process will greatly improve the grids for radiative transfer applications. - + The positions of the vertices used for generating the tetrahedral mesh are determined by the \em policy property. The available policies are: - + - Uniform: Randomly sampled from a uniform distribution. - CentralPeak: Randomly sampled from a distribution with a steep central peak. - DustDensity: Randomly sampled based on the dust density distribution. - ElectronDensity: Randomly sampled based on the electron density distribution. - GasDensity: Randomly sampled based on the gas density distribution. - - File: Loaded from a column data file specified by the \em filename property, + - File: Loaded from a column data file specified by the \em filename property, containing vertex coordinates (x, y, z) in each column. */ class TetraMeshSpatialGrid : public BoxSpatialGrid { @@ -109,7 +109,7 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid int _nface; // index of the equivalent face in the neighbouring tetrahedron [0, 3] Vec _normal; // outward facing normal - Face(){}; + Face(){} Face(int ntetra, int nface, Vec normal) : _ntetra(ntetra), _nface(nface), _normal(normal) {} }; @@ -134,13 +134,13 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid face in the traversal algorithm. */ int findEnteringFace(const Vec& pos, const Direction& dir) const; - /** This function checks if the given position is contained inside the tetrahedron. + /** This function checks if the given position is contained inside the tetrahedron. It frist checks if the position is inside the bounding box of the tetrahedron. */ bool contains(const Position& bfr) const; /** This function generates three random barycentric coordinates for uniformly sampling inside this tetrahedron. The fourth coordinate is calculated by ensuring their sum - equals 1, i.e. r=1-s-t-u. + equals 1, i.e. r=1-s-t-u. Source: Generating Random Points in a Tetrahedron: DOI 10.1080/10867651.2000.10487528 */ double generateBarycentric(double& s, double& t, double& u) const; @@ -180,7 +180,7 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid /** This private function removes vertices that are outside the domain. */ void removeOutside(); - /** This private function builds the tetrahedral mesh. It starts by constructing the Delaunay + /** This private function builds the tetrahedral mesh. It starts by constructing the Delaunay tetrahedralization and optionally refines it if the \em refine option is set to true. */ void buildMesh(); @@ -190,7 +190,7 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid /** This private function refines the Delaunay tetrahedralization to improve cell quality. The refinement process is controlled by TetGen with default quality parameters. The input is - the initial Delaunay tetrahedralization in the \em in tetgenio reference. The output, with + the initial Delaunay tetrahedralization in the \em in tetgenio reference. The output, with the refined Delaunay tetrahedralization, is placed inside the \em out tetgenio reference. */ void refineDelaunay(tetgenio& in, tetgenio& out); @@ -252,7 +252,7 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid In the first step, the function determines the cell index of the tetrahedron containing the starting point. If none is found, the path is terminated. Before the traversal algorithm can commence, a non-leaving face must be identified. This face acts as the entry face for the ray. Note that this - face does not necessarily have to be the actual entry face. This task is handled by the + face does not necessarily have to be the actual entry face. This task is handled by the \em findEnteringFace function of the \em Tetra class. Next, the traversal algorithm begins. The entering face is labeled as face 0, with its opposing vertex diff --git a/SMILE/build/CompilerFlags.cmake b/SMILE/build/CompilerFlags.cmake index f4392937..1be61486 100644 --- a/SMILE/build/CompilerFlags.cmake +++ b/SMILE/build/CompilerFlags.cmake @@ -13,13 +13,14 @@ set_property(TARGET ${TARGET} PROPERTY CXX_STANDARD_REQUIRED ON) if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") target_compile_options(${TARGET} PRIVATE -Wall -W -pedantic) if (NO_EXTRA_WARNINGS) - target_compile_options(${TARGET} PRIVATE -Wno-unused-parameter) + target_compile_options(${TARGET} PRIVATE -Wno-unused-parameter -Wno-unused-function -Wno-sign-compare + -Wno-deprecated-declarations -Wno-unused-variable -Wno-unused-but-set-variable + -Wno-deprecated-copy-with-user-provided-copy) else() target_compile_options(${TARGET} PRIVATE -Wdeprecated -Wextra-semi -Wold-style-cast -Wdouble-promotion -Wunused-exception-parameter -Wmissing-variable-declarations -Wconditional-uninitialized -Wswitch-enum -Wcovered-switch-default) - # -Wconversion (ignore size_t to/from int conversions) endif() elseif (CMAKE_CXX_COMPILER_ID MATCHES "GNU") target_compile_options(${TARGET} PRIVATE -Wall -W -pedantic) From 083a06e72de5f772290f48da03c165a5a8c94df3 Mon Sep 17 00:00:00 2001 From: Peter Camps Date: Mon, 16 Dec 2024 18:20:41 +0100 Subject: [PATCH 43/51] fix warnings for GCC and MSVC --- SKIRT/core/TetraMeshSpatialGrid.hpp | 2 +- SMILE/build/CompilerFlags.cmake | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/SKIRT/core/TetraMeshSpatialGrid.hpp b/SKIRT/core/TetraMeshSpatialGrid.hpp index 863bc683..ac5794b8 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.hpp +++ b/SKIRT/core/TetraMeshSpatialGrid.hpp @@ -109,7 +109,7 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid int _nface; // index of the equivalent face in the neighbouring tetrahedron [0, 3] Vec _normal; // outward facing normal - Face(){} + Face() {} Face(int ntetra, int nface, Vec normal) : _ntetra(ntetra), _nface(nface), _normal(normal) {} }; diff --git a/SMILE/build/CompilerFlags.cmake b/SMILE/build/CompilerFlags.cmake index 1be61486..f4bbc540 100644 --- a/SMILE/build/CompilerFlags.cmake +++ b/SMILE/build/CompilerFlags.cmake @@ -25,7 +25,9 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") elseif (CMAKE_CXX_COMPILER_ID MATCHES "GNU") target_compile_options(${TARGET} PRIVATE -Wall -W -pedantic) if (NO_EXTRA_WARNINGS) - target_compile_options(${TARGET} PRIVATE -Wno-misleading-indentation -Wno-unused-parameter) + target_compile_options(${TARGET} PRIVATE -Wno-misleading-indentation -Wno-unused-parameter + -Wno-unused-function -Wno-unused-result -Wno-deprecated-copy -Wno-sign-compare -Wno-restrict + -Wno-unused-variable -Wno-unused-but-set-variable -Wno-maybe-uninitialized) endif() elseif (CMAKE_CXX_COMPILER_ID MATCHES "Intel") target_compile_options(${TARGET} PRIVATE -fp-model precise -Wall) @@ -35,7 +37,7 @@ elseif (CMAKE_CXX_COMPILER_ID MATCHES "Intel") elseif (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") target_compile_options(${TARGET} PRIVATE /wd4267 /wd4244) # ignore size_t to/from int conversions if (NO_EXTRA_WARNINGS) - target_compile_options(${TARGET} PRIVATE /wd4996) # ignore unsafe C-style std functions + target_compile_options(${TARGET} PRIVATE /wd2220 /wd4018 /wd4101 /wd4477 /wd4996) endif() endif() From fcfa433fd95455131d8cabf417e22d5f525590a1 Mon Sep 17 00:00:00 2001 From: Peter Camps Date: Mon, 16 Dec 2024 20:02:09 +0100 Subject: [PATCH 44/51] try to fix warnings for GCC on MinGW --- SMILE/build/CompilerFlags.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SMILE/build/CompilerFlags.cmake b/SMILE/build/CompilerFlags.cmake index f4bbc540..fc4dea15 100644 --- a/SMILE/build/CompilerFlags.cmake +++ b/SMILE/build/CompilerFlags.cmake @@ -27,7 +27,7 @@ elseif (CMAKE_CXX_COMPILER_ID MATCHES "GNU") if (NO_EXTRA_WARNINGS) target_compile_options(${TARGET} PRIVATE -Wno-misleading-indentation -Wno-unused-parameter -Wno-unused-function -Wno-unused-result -Wno-deprecated-copy -Wno-sign-compare -Wno-restrict - -Wno-unused-variable -Wno-unused-but-set-variable -Wno-maybe-uninitialized) + -Wno-unused-variable -Wno-unused-but-set-variable -Wno-maybe-uninitialized -Wno-format) endif() elseif (CMAKE_CXX_COMPILER_ID MATCHES "Intel") target_compile_options(${TARGET} PRIVATE -fp-model precise -Wall) From 6b7490def31d8b2dd9e200868959ffca652499ca Mon Sep 17 00:00:00 2001 From: Peter Camps Date: Mon, 16 Dec 2024 21:45:10 +0100 Subject: [PATCH 45/51] add build check with Intel oneAPI --- .github/workflows/check-builds.yml | 31 ++++++++++++++++++++++++++++-- SMILE/build/CompilerFlags.cmake | 4 ++-- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/.github/workflows/check-builds.yml b/.github/workflows/check-builds.yml index f265cabd..fa899bc5 100644 --- a/.github/workflows/check-builds.yml +++ b/.github/workflows/check-builds.yml @@ -1,8 +1,7 @@ # GitHub action workflow that checks the build process on multiple platforms. # # The workflow builds the code (without MPI and without MakeUp) -# using GCC on Ubuntu and using Clang on macOS, in two different jobs. -# Other compiler/platform combinations can be added if the need arises. +# using various compilers and operating systems, in different jobs. # The workflow fails if there are any build errors and/or warnings. # name: Check builds @@ -86,3 +85,31 @@ jobs: - name: Build shell: cmd run: cd release && cmake --build . -j 2 + + # Intel on Ubuntu + check_build_intel: + # job name, displayed in the action log + name: Build using Intel oneAPI on Ubuntu + # run this job on the Github-provided runner with a recent Ubuntu version + runs-on: ubuntu-22.04 + # steps that make up this job + steps: + # checkout using a recent version of the checkout action + - name: Checkout + uses: actions/checkout@v3 + # add Intel oneAPI to apt repository + - name: add oneAPI to apt + run: | + wget -O- https://apt.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS.PUB | gpg --dearmor | sudo tee /usr/share/keyrings/oneapi-archive-keyring.gpg > /dev/null + echo "deb [signed-by=/usr/share/keyrings/oneapi-archive-keyring.gpg] https://apt.repos.intel.com/oneapi all main" | sudo tee /etc/apt/sources.list.d/oneAPI.list + sudo apt update + # install Intel oneAPI + - name: install oneAPI + run: sudo apt install intel-oneapi-compiler-dpcpp-cpp + # configure the build files through CMake + - name: Configure + run: cmake -B release CMAKE_C_COMPILER=/opt/intel/oneapi/compiler/latest/bin/icx CMAKE_CXX_COMPILER=/opt/intel/oneapi/compiler/latest/bin/icpx \ + -DCMAKE_BUILD_TYPE:STRING=Release -DWARNINGS_AS_ERRORS:BOOL=ON -DBUILD_DOX_STYLE:BOOL=ON -DBUILD_SMILE_SHAPES:BOOL=ON -DBUILD_SMILE_TOOL:BOOL=ON -L + # perform the actual build (Ubuntu runners have 2 cores) + - name: Build + run: make -C release -j 2 diff --git a/SMILE/build/CompilerFlags.cmake b/SMILE/build/CompilerFlags.cmake index fc4dea15..e10a5b4a 100644 --- a/SMILE/build/CompilerFlags.cmake +++ b/SMILE/build/CompilerFlags.cmake @@ -10,7 +10,7 @@ set_property(TARGET ${TARGET} PROPERTY CXX_STANDARD 14) set_property(TARGET ${TARGET} PROPERTY CXX_STANDARD_REQUIRED ON) -if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") +if (CMAKE_CXX_COMPILER_ID MATCHES "Clang|IntelLLVM") # the Intel oneAPI compiler supports Clang options target_compile_options(${TARGET} PRIVATE -Wall -W -pedantic) if (NO_EXTRA_WARNINGS) target_compile_options(${TARGET} PRIVATE -Wno-unused-parameter -Wno-unused-function -Wno-sign-compare @@ -29,7 +29,7 @@ elseif (CMAKE_CXX_COMPILER_ID MATCHES "GNU") -Wno-unused-function -Wno-unused-result -Wno-deprecated-copy -Wno-sign-compare -Wno-restrict -Wno-unused-variable -Wno-unused-but-set-variable -Wno-maybe-uninitialized -Wno-format) endif() -elseif (CMAKE_CXX_COMPILER_ID MATCHES "Intel") +elseif (CMAKE_CXX_COMPILER_ID MATCHES "Intel") # this catches the deprecated Intel classic compiler target_compile_options(${TARGET} PRIVATE -fp-model precise -Wall) if (NO_EXTRA_WARNINGS) target_compile_options(${TARGET} PRIVATE -Wno-deprecated) From afdf5d219ffdae876178cdf2578133b2d80bfde0 Mon Sep 17 00:00:00 2001 From: Peter Camps Date: Mon, 16 Dec 2024 21:58:51 +0100 Subject: [PATCH 46/51] try intel oneAPI runner again --- .github/workflows/check-builds.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check-builds.yml b/.github/workflows/check-builds.yml index fa899bc5..9a0c7ce0 100644 --- a/.github/workflows/check-builds.yml +++ b/.github/workflows/check-builds.yml @@ -108,8 +108,9 @@ jobs: run: sudo apt install intel-oneapi-compiler-dpcpp-cpp # configure the build files through CMake - name: Configure - run: cmake -B release CMAKE_C_COMPILER=/opt/intel/oneapi/compiler/latest/bin/icx CMAKE_CXX_COMPILER=/opt/intel/oneapi/compiler/latest/bin/icpx \ - -DCMAKE_BUILD_TYPE:STRING=Release -DWARNINGS_AS_ERRORS:BOOL=ON -DBUILD_DOX_STYLE:BOOL=ON -DBUILD_SMILE_SHAPES:BOOL=ON -DBUILD_SMILE_TOOL:BOOL=ON -L + run: | + ls /opt/intel/oneapi/compiler/latest/bin + cmake -B release CMAKE_C_COMPILER=/opt/intel/oneapi/compiler/latest/bin/icx CMAKE_CXX_COMPILER=/opt/intel/oneapi/compiler/latest/bin/icpx -DCMAKE_BUILD_TYPE:STRING=Release -DWARNINGS_AS_ERRORS:BOOL=ON -DBUILD_DOX_STYLE:BOOL=ON -DBUILD_SMILE_SHAPES:BOOL=ON -DBUILD_SMILE_TOOL:BOOL=ON -L # perform the actual build (Ubuntu runners have 2 cores) - name: Build run: make -C release -j 2 From b15f5a363ef17ee4df9eb51ba16b66487b8c4b5a Mon Sep 17 00:00:00 2001 From: Peter Camps Date: Mon, 16 Dec 2024 22:07:37 +0100 Subject: [PATCH 47/51] try oneAPI checker once more --- .github/workflows/check-builds.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/check-builds.yml b/.github/workflows/check-builds.yml index 9a0c7ce0..706bc0a1 100644 --- a/.github/workflows/check-builds.yml +++ b/.github/workflows/check-builds.yml @@ -108,9 +108,7 @@ jobs: run: sudo apt install intel-oneapi-compiler-dpcpp-cpp # configure the build files through CMake - name: Configure - run: | - ls /opt/intel/oneapi/compiler/latest/bin - cmake -B release CMAKE_C_COMPILER=/opt/intel/oneapi/compiler/latest/bin/icx CMAKE_CXX_COMPILER=/opt/intel/oneapi/compiler/latest/bin/icpx -DCMAKE_BUILD_TYPE:STRING=Release -DWARNINGS_AS_ERRORS:BOOL=ON -DBUILD_DOX_STYLE:BOOL=ON -DBUILD_SMILE_SHAPES:BOOL=ON -DBUILD_SMILE_TOOL:BOOL=ON -L + run: cmake -B release -DCMAKE_C_COMPILER=/opt/intel/oneapi/compiler/latest/bin/icx -DCMAKE_CXX_COMPILER=/opt/intel/oneapi/compiler/latest/bin/icpx -DCMAKE_BUILD_TYPE:STRING=Release -DWARNINGS_AS_ERRORS:BOOL=ON -DBUILD_DOX_STYLE:BOOL=ON -DBUILD_SMILE_SHAPES:BOOL=ON -DBUILD_SMILE_TOOL:BOOL=ON -L # perform the actual build (Ubuntu runners have 2 cores) - name: Build run: make -C release -j 2 From ecb9f46dcd231cd31b663354ed92aac5e4f99155 Mon Sep 17 00:00:00 2001 From: Peter Camps Date: Tue, 17 Dec 2024 15:28:30 +0100 Subject: [PATCH 48/51] cosmetic updates to source code --- SKIRT/core/TetraMeshSpatialGrid.cpp | 104 +++++++++++++--------------- SKIRT/core/TetraMeshSpatialGrid.hpp | 30 +++++--- 2 files changed, 69 insertions(+), 65 deletions(-) diff --git a/SKIRT/core/TetraMeshSpatialGrid.cpp b/SKIRT/core/TetraMeshSpatialGrid.cpp index 09b30e29..9b196136 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.cpp +++ b/SKIRT/core/TetraMeshSpatialGrid.cpp @@ -13,8 +13,6 @@ #include "TextInFile.hpp" #include "tetgen.h" -using std::array; - ////////////////////////////////////////////////////////////////////// namespace @@ -48,9 +46,9 @@ namespace } // get the vertices around a face in clockwise order viewed from opposite vertex - inline array clockwiseVertices(int face) + inline std::array clockwiseVertices(int face) { - array cv = {(face + 3) % 4, (face + 2) % 4, (face + 1) % 4}; + std::array cv = {(face + 3) % 4, (face + 2) % 4, (face + 1) % 4}; // if face is even we should swap two edges if (face % 2 == 0) std::swap(cv[0], cv[2]); return cv; @@ -59,8 +57,8 @@ namespace ////////////////////////////////////////////////////////////////////// -TetraMeshSpatialGrid::Tetra::Tetra(const vector& vertices, const array& vertexIndices, - const array& faces) +TetraMeshSpatialGrid::Tetra::Tetra(const vector& vertices, const FourIndices& vertexIndices, + const FourFaces& faces) : _vertices(vertices), _vertexIndices(vertexIndices), _faces(faces) { double xmin = DBL_MAX; @@ -95,7 +93,7 @@ int TetraMeshSpatialGrid::Tetra::findEnteringFace(const Vec& pos, const Directio { int enteringFace = -1; // clockwise and cclockwise adjacent faces when checking edge v1->v2 - static constexpr int etable[6][2] = {{3, 2}, {1, 3}, {2, 1}, {3, 0}, {0, 2}, {1, 0}}; // make this static? + static constexpr int etable[6][2] = {{3, 2}, {1, 3}, {2, 1}, {3, 0}, {0, 2}, {1, 0}}; // must be static int e = 0; // loop over all 6 edges because of rare cases where ray is inside edge and only 1 non-zero Plücker product for (int t1 = 0; t1 < 3; t1++) @@ -137,7 +135,7 @@ bool TetraMeshSpatialGrid::Tetra::contains(const Position& bfr) const const Face& face = _faces[3]; v = vertex(0); // any vertex that is not vertex 3 - if (Vec::dot(v - bfr, face._normal) < 0) return false; + if (Vec::dot(v - bfr, face._normal) < 0.) return false; return true; } @@ -146,24 +144,24 @@ bool TetraMeshSpatialGrid::Tetra::contains(const Position& bfr) const double TetraMeshSpatialGrid::Tetra::generateBarycentric(double& s, double& t, double& u) const { - if (s + t > 1.0) // cut'n fold the cube into a prism + if (s + t > 1.) // cut'n fold the cube into a prism { - s = 1.0 - s; - t = 1.0 - t; + s = 1. - s; + t = 1. - t; } - if (t + u > 1.0) // cut'n fold the prism into a tetrahedron + if (t + u > 1.) // cut'n fold the prism into a tetrahedron { double tmp = u; - u = 1.0 - s - t; - t = 1.0 - tmp; + u = 1. - s - t; + t = 1. - tmp; } - else if (s + t + u > 1.0) + else if (s + t + u > 1.) { double tmp = u; - u = s + t + u - 1.0; - s = 1 - t - tmp; + u = s + t + u - 1.; + s = 1. - t - tmp; } - return 1 - u - t - s; + return 1. - u - t - s; } ////////////////////////////////////////////////////////////////////// @@ -197,14 +195,14 @@ Vec TetraMeshSpatialGrid::Tetra::edge(int t1, int t2) const double TetraMeshSpatialGrid::Tetra::volume() const { - return 1 / 6. * abs(Vec::dot(Vec::cross(edge(0, 1), edge(0, 2)), edge(0, 3))); + return 1. / 6. * abs(Vec::dot(Vec::cross(edge(0, 1), edge(0, 2)), edge(0, 3))); } ////////////////////////////////////////////////////////////////////// double TetraMeshSpatialGrid::Tetra::diagonal() const { - double sum = 0.0; + double sum = 0.; for (int i = 0; i < 3; ++i) { for (int j = i + 1; j < 4; ++j) @@ -212,12 +210,12 @@ double TetraMeshSpatialGrid::Tetra::diagonal() const sum += edge(i, j).norm2(); } } - return sqrt(sum / 6.0); + return sqrt(sum / 6.); } ////////////////////////////////////////////////////////////////////// -const array& TetraMeshSpatialGrid::Tetra::faces() const +const TetraMeshSpatialGrid::FourFaces& TetraMeshSpatialGrid::Tetra::faces() const { return _faces; } @@ -248,10 +246,10 @@ class TetraMeshSpatialGrid::BlockGrid public: // The constructor creates a cuboidal grid of the specified number of grid blocks in each - // spatial direction, and for each of the grid blocks it builds a list of all blocks - // overlapping the grid block. In an attempt to distribute the blocks evenly over the + // spatial direction, and for each of the grid blocks it builds a list of all cells that may + // overlap the grid block. In an attempt to distribute the cells evenly over the // grid blocks, the sizes of the grid blocks in each spatial direction are chosen so that - // the block centers are evenly distributed over the grid blocks. + // the cell centers are evenly distributed over the grid blocks. BlockGrid(const vector& tetrahedra, Box extent, int gridsize) : _tetrahedra(tetrahedra), _gridsize(gridsize) { // build the grids in each spatial direction @@ -262,13 +260,13 @@ class TetraMeshSpatialGrid::BlockGrid // make room for p*p*p grid blocks _listv.resize(gridsize * gridsize * gridsize); - // add each block to the list for every grid block that it overlaps + // add each cell to the list for every grid block that its bounding box overlaps int n = _tetrahedra.size(); for (int m = 0; m != n; ++m) { Box boundingBox = _tetrahedra[m].extent(); - // find indices for first and last grid block overlapped by block, in each spatial direction + // find indices for first and last grid block overlapped by bounding box, in each spatial direction int i1 = NR::locateClip(_xgrid, boundingBox.xmin()); int i2 = NR::locateClip(_xgrid, boundingBox.xmax()); int j1 = NR::locateClip(_ygrid, boundingBox.ymin()); @@ -276,7 +274,7 @@ class TetraMeshSpatialGrid::BlockGrid int k1 = NR::locateClip(_zgrid, boundingBox.zmin()); int k2 = NR::locateClip(_zgrid, boundingBox.zmax()); - // add the block to all grid blocks in that 3D range + // add the cell to all grid blocks in that 3D range for (int i = i1; i <= i2; i++) for (int j = j1; j <= j2; j++) for (int k = k1; k <= k2; k++) @@ -339,15 +337,15 @@ class TetraMeshSpatialGrid::BlockGrid grid[gridsize] = std::numeric_limits::infinity(); } - // This function returns the smallest number of blocks overlapping a single grid block. + // This function returns the smallest number of cells overlapping a single grid block. int minCellRefsPerBlock() const { return _pmin; } - // This function returns the largest number of blocks overlapping a single grid block. + // This function returns the largest number of cells overlapping a single grid block. int maxCellRefsPerBlock() const { return _pmax; } // This function returns the index (in the list originally passed to the constructor) - // of the first block in the list that overlaps the specified position, - // or -1 if none of the blocks in the list overlap the specified position. + // of the first cell in the list that actually overlaps the specified position, + // or -1 if none of the cells in the list overlap the specified position. int cellIndexFor(Position r) const { // locate the grid block containing the specified position @@ -355,7 +353,7 @@ class TetraMeshSpatialGrid::BlockGrid int j = NR::locateClip(_ygrid, r.y()); int k = NR::locateClip(_zgrid, r.z()); - // search the list of blocks for that grid block + // search the list of cells for that grid block for (int m : _listv[blockIndex(_gridsize, i, j, k)]) { if (_tetrahedra[m].contains(r)) return m; @@ -396,7 +394,7 @@ void TetraMeshSpatialGrid::setupSelfBefore() const int a = 1000; // steepness of the peak; the central 1/a portion is NOT covered const double rscale = extent().rmax().norm(); _vertices.resize(_numSamples); - _vertices[0] = Vec(0, 0, 0); + _vertices[0] = Vec(); for (int m = 1; m != _numSamples;) // skip first vertex so that it remains (0,0,0) { double r = rscale * pow(1. / a, random->uniform()); // random distribution according to 1/x @@ -415,10 +413,7 @@ void TetraMeshSpatialGrid::setupSelfBefore() for (auto medium : ms->media()) if (medium->mix()->isDust()) media.push_back(medium); for (auto medium : media) weights.push_back(medium->mass()); - vector samples = sampleMedia(media, weights, extent(), _numSamples); - int n = samples.size(); - _vertices.resize(n); - for (int m = 0; m != n; ++m) _vertices[m] = samples[m]; + _vertices = sampleMedia(media, weights, extent(), _numSamples); break; } case Policy::ElectronDensity: @@ -430,10 +425,7 @@ void TetraMeshSpatialGrid::setupSelfBefore() for (auto medium : ms->media()) if (medium->mix()->isElectrons()) media.push_back(medium); for (auto medium : media) weights.push_back(medium->number()); - vector samples = sampleMedia(media, weights, extent(), _numSamples); - int n = samples.size(); - _vertices.resize(n); - for (int m = 0; m != n; ++m) _vertices[m] = samples[m]; + _vertices = sampleMedia(media, weights, extent(), _numSamples); break; } case Policy::GasDensity: @@ -445,10 +437,7 @@ void TetraMeshSpatialGrid::setupSelfBefore() for (auto medium : ms->media()) if (medium->mix()->isGas()) media.push_back(medium); for (auto medium : media) weights.push_back(medium->number()); - vector samples = sampleMedia(media, weights, extent(), _numSamples); - int n = samples.size(); - _vertices.resize(n); - for (int m = 0; m != n; ++m) _vertices[m] = samples[m]; + _vertices = sampleMedia(media, weights, extent(), _numSamples); break; } case Policy::File: @@ -583,16 +572,16 @@ void TetraMeshSpatialGrid::storeTetrahedra(const tetgenio& final, bool storeVert _tetrahedra.reserve(_numCells); // no default constructor for Tetra for (int i = 0; i < _numCells; i++) { - array vertexIndices; - array faces; // vertices + FourIndices vertexIndices; for (int c = 0; c < 4; c++) { vertexIndices[c] = final.tetrahedronlist[4 * i + c]; } // faces + FourFaces faces; for (int f = 0; f < 4; f++) { // -1 if no neighbor @@ -613,7 +602,7 @@ void TetraMeshSpatialGrid::storeTetrahedra(const tetgenio& final, bool storeVert } // compute outward facing normal of face - array cv = clockwiseVertices(f); + auto cv = clockwiseVertices(f); Vec v0 = _vertices[vertexIndices[cv[0]]]; Vec e12 = _vertices[vertexIndices[cv[1]]] - v0; Vec e13 = _vertices[vertexIndices[cv[2]]] - v0; @@ -641,7 +630,7 @@ void TetraMeshSpatialGrid::storeTetrahedra(const tetgenio& final, bool storeVert minVol /= V; maxVol /= V; double avgVol = 1. / _numCells; - double varVol = (totalVol2 / _numCells / (V * V) - avgVol * avgVol); + double varVol = totalVol2 / _numCells / (V * V) - avgVol * avgVol; // log statistics _log->info("Done computing tetrahedralization"); @@ -687,6 +676,7 @@ double TetraMeshSpatialGrid::diagonal(int m) const { return _tetrahedra[m].diagonal(); } + ////////////////////////////////////////////////////////////////////// int TetraMeshSpatialGrid::cellIndex(Position bfr) const @@ -737,7 +727,7 @@ void TetraMeshSpatialGrid::writeGridPlotFiles(const SimulationItem* probe) const coords[3 * v + 2] = vertex.z(); // get vertices of opposite face - array faceIndices = clockwiseVertices(v); + auto faceIndices = clockwiseVertices(v); indices[4 * v + 0] = 3; // amount of vertices per face indices[4 * v + 1] = faceIndices[0]; indices[4 * v + 2] = faceIndices[1]; @@ -752,7 +742,7 @@ void TetraMeshSpatialGrid::writeGridPlotFiles(const SimulationItem* probe) const // log message if the minimum time has elapsed numDone++; - if (numDone % 2000 == 0) _log->infoIfElapsed("Computed tetrehedra: ", 2000); + if (numDone % 2000 == 0) _log->infoIfElapsed("Computed tetrahedra: ", 2000); } } @@ -761,8 +751,8 @@ void TetraMeshSpatialGrid::writeGridPlotFiles(const SimulationItem* probe) const class TetraMeshSpatialGrid::MySegmentGenerator : public PathSegmentGenerator { const TetraMeshSpatialGrid* _grid{nullptr}; - int _mr; - int _enteringFace; + int _mr{-1}; + int _enteringFace{-1}; public: MySegmentGenerator(const TetraMeshSpatialGrid* grid) : _grid(grid) {} @@ -790,7 +780,7 @@ class TetraMeshSpatialGrid::MySegmentGenerator : public PathSegmentGenerator while (true) { const Tetra tetra = _grid->_tetrahedra[_mr]; - const array& faces = tetra.faces(); + const FourFaces& faces = tetra.faces(); Position pos = r(); Direction dir = k(); @@ -804,7 +794,7 @@ class TetraMeshSpatialGrid::MySegmentGenerator : public PathSegmentGenerator Vec moment = Vec::cross(dir, pos - tetra.vertex(_enteringFace)); // clockwise vertices around entering face - array cv = clockwiseVertices(_enteringFace); + auto cv = clockwiseVertices(_enteringFace); // determine orientations for use in the decision tree double prod0 = Vec::dot(moment, tetra.edge(cv[0], _enteringFace)); @@ -843,7 +833,7 @@ class TetraMeshSpatialGrid::MySegmentGenerator : public PathSegmentGenerator // 0 0 -> 1 // 1 0 -> 0 // 0 1 -> 0 - static constexpr int dtable[2][2] = {{1, 0}, {0, 2}}; + static constexpr int dtable[2][2] = {{1, 0}, {0, 2}}; // must be static leavingFace = cv[dtable[clock0][cclocki]]; // plane intersection to leaving face const Vec& n = faces[leavingFace]._normal; diff --git a/SKIRT/core/TetraMeshSpatialGrid.hpp b/SKIRT/core/TetraMeshSpatialGrid.hpp index ac5794b8..b1940dad 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.hpp +++ b/SKIRT/core/TetraMeshSpatialGrid.hpp @@ -53,7 +53,8 @@ class tetgenio; - ElectronDensity: Randomly sampled based on the electron density distribution. - GasDensity: Randomly sampled based on the gas density distribution. - File: Loaded from a column data file specified by the \em filename property, - containing vertex coordinates (x, y, z) in each column. */ + containing vertex coordinates (x, y, z) in each column. +*/ class TetraMeshSpatialGrid : public BoxSpatialGrid { /** The enumeration type indicating the policy for determining the positions of the vertices. */ @@ -99,7 +100,8 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid function. */ void setupSelfBefore() override; - //==================== Private construction ==================== + //==================== Private data types ==================== + private: /** Private struct that represents a face of a tetrahedron, storing all relevant information for photon traversal. */ @@ -109,11 +111,17 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid int _nface; // index of the equivalent face in the neighbouring tetrahedron [0, 3] Vec _normal; // outward facing normal - Face() {} + Face() : _ntetra(0), _nface(0) {} Face(int ntetra, int nface, Vec normal) : _ntetra(ntetra), _nface(nface), _normal(normal) {} }; + /** Alias for a fixed array of 4 integer indices. */ + using FourIndices = std::array; + + /** Alias for a fixed array of 4 Faces. */ + using FourFaces = std::array; + /** Private class that represents a tetrahedron, storing all relevant information to fully describe its geometry and allow photon traversal through it. */ class Tetra @@ -122,20 +130,20 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid const vector& _vertices; // reference to all vertices, could be passed as arg but this is more convenient Box _extent; // bounding box of the tetrahedron Vec _centroid; // barycenter of the tetrahedron - std::array _vertexIndices; // indices of the vertices in the global vertex list - std::array _faces; // faces of the tetrahedron, see struct Face + FourIndices _vertexIndices; // indices of the vertices in the global vertex list + FourFaces _faces; // faces of the tetrahedron, see struct Face public: /** This constructor initializes the tetrahedron by setting its fields and calculating the bounding box and centroid. */ - Tetra(const vector& vertices, const std::array& vertexIndices, const std::array& faces); + Tetra(const vector& vertices, const FourIndices& vertexIndices, const FourFaces& faces); /** This function finds a face that is not the leaving face which can act as the entering face in the traversal algorithm. */ int findEnteringFace(const Vec& pos, const Direction& dir) const; /** This function checks if the given position is contained inside the tetrahedron. - It frist checks if the position is inside the bounding box of the tetrahedron. */ + It first checks if the position is inside the bounding box of the tetrahedron. */ bool contains(const Position& bfr) const; /** This function generates three random barycentric coordinates for uniformly sampling @@ -164,7 +172,7 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid double diagonal() const; /** This function returns a reference to an array of the faces of the tetrahedron. */ - const std::array& faces() const; + const FourFaces& faces() const; /** This function returns the centroid of the tetrahedron. */ const Vec& centroid() const; @@ -173,6 +181,9 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid const Box& extent() const; }; + //==================== Private construction ==================== + +private: /** This private helper class organizes the cells into cuboidal blocks in a smart grid, such that it is easy to retrieve all cells inside a certain block given a position. */ class BlockGrid; @@ -206,6 +217,7 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid void buildSearch(); //======================= Interrogation ======================= + public: /** This function returns the number of cells in the grid. */ int numCells() const override; @@ -230,6 +242,7 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid //===================== Path construction ===================== +public: /** This function creates and returns ownership of a path segment generator suitable for the tetrahedral spatial grid, implemented as a private \em PathSegmentGenerator subclass. The algorithm for constructing the path is taken from Maria et al. (2017). @@ -277,6 +290,7 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid //===================== Output ===================== +public: /** This function outputs the grid plot files. It writes each tetrahedral face as a triangle to the grid plot file. */ void writeGridPlotFiles(const SimulationItem* probe) const override; From 7ebc8470571b3ea176e39dd292531173b7db85b5 Mon Sep 17 00:00:00 2001 From: arlauwer Date: Wed, 18 Dec 2024 17:34:34 +0100 Subject: [PATCH 49/51] add error if no grid --- SKIRT/core/TetraMeshSpatialGrid.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SKIRT/core/TetraMeshSpatialGrid.cpp b/SKIRT/core/TetraMeshSpatialGrid.cpp index 9b196136..ec378157 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.cpp +++ b/SKIRT/core/TetraMeshSpatialGrid.cpp @@ -454,7 +454,7 @@ void TetraMeshSpatialGrid::setupSelfBefore() } removeOutside(); - if (_numVertices < 4) return; // abort if there are not enough vertices + if (_numVertices < 4) throw FATALERROR("Not enough vertices to build a tetrahedral grid"); buildMesh(); buildSearch(); } From 9c05bec11dbcdd9cfe536d48768e09112a7c1ad2 Mon Sep 17 00:00:00 2001 From: arlauwer Date: Thu, 19 Dec 2024 15:51:35 +0100 Subject: [PATCH 50/51] add 8 corner vertices to cover full domain --- SKIRT/core/TetraMeshSpatialGrid.cpp | 58 +++++++++++++++++++++++++---- SKIRT/core/TetraMeshSpatialGrid.hpp | 40 ++++++++++++-------- SKIRT/core/VoronoiMeshSnapshot.hpp | 2 +- 3 files changed, 77 insertions(+), 23 deletions(-) diff --git a/SKIRT/core/TetraMeshSpatialGrid.cpp b/SKIRT/core/TetraMeshSpatialGrid.cpp index ec378157..8c11ed1c 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.cpp +++ b/SKIRT/core/TetraMeshSpatialGrid.cpp @@ -453,15 +453,15 @@ void TetraMeshSpatialGrid::setupSelfBefore() } } - removeOutside(); - if (_numVertices < 4) throw FATALERROR("Not enough vertices to build a tetrahedral grid"); + addCorners(); + removeInvalid(); buildMesh(); buildSearch(); } //////////////////////////////////////////////////////////////////// -void TetraMeshSpatialGrid::removeOutside() +void TetraMeshSpatialGrid::removeInvalid() { _numVertices = _vertices.size(); @@ -475,8 +475,50 @@ void TetraMeshSpatialGrid::removeOutside() // log removed vertices int numOutside = _numVertices - _vertices.size(); if (numOutside) _log->info("removed " + StringUtils::toString(numOutside, 'd') + " vertices outside of the domain"); + _numVertices = _vertices.size(); + + // sort vertices in order of increasing x coordinate to accelerate search for nearby sites + std::sort(_vertices.begin(), _vertices.end(), [](Vec& v1, Vec& v2) { return v1.x() < v2.x(); }); + // mark vertices that lie too nearby another site + std::vector toRemove; + for (int i = 0; i < _numVertices; ++i) + { + for (int j = i + 1; j < _numVertices && (_vertices[j].x() - _vertices[i].x() < _eps); ++j) + { + if ((_vertices[j] - _vertices[i]).norm2() < _eps * _eps) + { + toRemove.push_back(i); + break; + } + } + } + // remove marked vertices in reverse order + std::sort(toRemove.begin(), toRemove.end(), [](int i, int j) { return i > j; }); + for (int index : toRemove) _vertices.erase(_vertices.begin() + index); + + // log removed vertices + int numNearby = toRemove.size(); + if (numNearby) _log->info("removed " + StringUtils::toString(numNearby, 'd') + " vertices too nearby to others"); + _numVertices = _vertices.size(); +} + +//////////////////////////////////////////////////////////////////// +void TetraMeshSpatialGrid::addCorners() +{ _numVertices = _vertices.size(); + // add the 8 corners of the domain to the list of vertices + double xmin, ymin, zmin, xmax, ymax, zmax; + extent(xmin, ymin, zmin, xmax, ymax, zmax); + _vertices.reserve(_numVertices + 8); + _vertices.emplace_back(xmin, ymin, zmin); + _vertices.emplace_back(xmin, ymin, zmax); + _vertices.emplace_back(xmin, ymax, zmin); + _vertices.emplace_back(xmin, ymax, zmax); + _vertices.emplace_back(xmax, ymin, zmin); + _vertices.emplace_back(xmax, ymin, zmax); + _vertices.emplace_back(xmax, ymax, zmin); + _vertices.emplace_back(xmax, ymax, zmax); } //////////////////////////////////////////////////////////////////// @@ -762,15 +804,17 @@ class TetraMeshSpatialGrid::MySegmentGenerator : public PathSegmentGenerator if (state() == State::Unknown) { // moveInside does not move the photon packet inside the convex hull so it is currently disabled - // if (!moveInside(_grid->extent(), _grid->_eps)) return false; + if (!moveInside(_grid->extent(), _grid->_eps)) return false; // get the index of the cell containing the current position _mr = _grid->cellIndex(r()); - if (_mr > 0) setState(State::Inside); _enteringFace = -1; + // very rare edge case where no cell is found at domain boundary + if (_mr == -1) return false; - // position is outside of convex hull - if (_mr < 0) return false; + // if the photon packet started outside the grid, return the corresponding nonzero-length segment; + // otherwise fall through to determine the first actual segment + if (ds() > 0.) return true; } // inside convex hull diff --git a/SKIRT/core/TetraMeshSpatialGrid.hpp b/SKIRT/core/TetraMeshSpatialGrid.hpp index b1940dad..e047960a 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.hpp +++ b/SKIRT/core/TetraMeshSpatialGrid.hpp @@ -16,11 +16,12 @@ class tetgenio; ////////////////////////////////////////////////////////////////////// /** TetraMeshSpatialGrid is a concrete subclass of SpatialGrid representing a 3D tetrahedral mesh - generated from a set of vertices. The resulting grid is always constrained to lie within the - convex hull of the point set. This implies that the grid is not guaranteed to fill the entire - simulation domain. The grid is constructed using the open-source library TetGen version 1.6.0 - (released on August 31, 2020). TetGen is an advanced C++ tetrahedral mesh generator with many - features. This class uses the following key features of TetGen: + generated from a set of vertices. The resulting grid fully covers the domain by including the + 8 corners of the domain as vertices. Since a tetrahedral mesh always fills the convex hull of + the vertices, the domain is always fully covered. This grid will thus always have at least 8 + vertices. The grid is constructed using the open-source library TetGen version 1.6.0 (released + on August 31, 2020). TetGen is an advanced C++ tetrahedral mesh generator with many features. + This class uses the following key features of TetGen: - Delaunay Tetrahedralization: Generate a unique Delaunay tetrahedral mesh from a given set of vertices. @@ -54,6 +55,8 @@ class tetgenio; - GasDensity: Randomly sampled based on the gas density distribution. - File: Loaded from a column data file specified by the \em filename property, containing vertex coordinates (x, y, z) in each column. + + Vertices are removed if they lie outside the simulation domain or are too close to another. */ class TetraMeshSpatialGrid : public BoxSpatialGrid { @@ -188,8 +191,12 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid that it is easy to retrieve all cells inside a certain block given a position. */ class BlockGrid; - /** This private function removes vertices that are outside the domain. */ - void removeOutside(); + /** This private function removes vertices that are outside the domain or too close to other vertices. */ + void removeInvalid(); + + /** This private function adds the 8 corners of the domain to the vertex list. This way the full + domain will be tetrahedralized. */ + void addCorners(); /** This private function builds the tetrahedral mesh. It starts by constructing the Delaunay tetrahedralization and optionally refines it if the \em refine option is set to true. */ @@ -231,7 +238,7 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid /** This function returns the index of the cell that contains the position \f${\bf{r}}\f$. The function uses the data structure stored in the \em BlockGrid to accelerate the - search. If the position is outside the convex hull, the function returns -1. */ + search. If no cell is found to contain this position, the function returns -1. */ int cellIndex(Position bfr) const override; /** This function returns the centroid of the tetrahedron with index \f$m\f$. */ @@ -262,11 +269,14 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid clockwise-ordered edges of that face are negative. The algorithm in Maria et al. (2017) optimizes this by requiring only two Plücker products to be evaluated. Our implementation is described below. - In the first step, the function determines the cell index of the tetrahedron containing the starting - point. If none is found, the path is terminated. Before the traversal algorithm can commence, a - non-leaving face must be identified. This face acts as the entry face for the ray. Note that this - face does not necessarily have to be the actual entry face. This task is handled by the - \em findEnteringFace function of the \em Tetra class. + In the first step, the function checks whether the start point is inside the domain. If so, the current + point is simply initialized to the start point. If not, the function computes the path segment to the + first intersection with one of the domain walls and moves the current point inside the domain. Next, + the function determines the cell index of the tetrahedron containing the current point. If none is + found, the path is terminated. Before the traversal algorithm can commence, a non-leaving face must + be identified. This face acts as the entry face for the ray. Note that this face does not necessarily + have to be the actual entry face. This task is handled by the \em findEnteringFace function of the + \em Tetra class. Next, the traversal algorithm begins. The entering face is labeled as face 0, with its opposing vertex labeled as vertex 0. We start by evaluating the Plücker product of the ray with the edge \f$1 \to 0\f$. @@ -284,8 +294,8 @@ class TetraMeshSpatialGrid : public BoxSpatialGrid \em VoronoiMeshSnapshot class, where the closest intersection distance with all faces is found. The algorithm continues until the exit face lies on the convex hull boundary. At this point, the path is - terminated. If neither the Maria et al. (2017) algorithm nor the plane intersection algorithm identifies - an exit face, the current point is advanced by a small distance, and the cell index is recalculated. */ + terminated. If the exit face is not found, which should only rarely happen due to computational + inaccuracies, the current point is advanced by a small distance, and the cell index is recalculated. */ std::unique_ptr createPathSegmentGenerator() const override; //===================== Output ===================== diff --git a/SKIRT/core/VoronoiMeshSnapshot.hpp b/SKIRT/core/VoronoiMeshSnapshot.hpp index 2629ace4..7a596543 100644 --- a/SKIRT/core/VoronoiMeshSnapshot.hpp +++ b/SKIRT/core/VoronoiMeshSnapshot.hpp @@ -383,7 +383,7 @@ class VoronoiMeshSnapshot : public Snapshot the path is complete and the loop is terminated. If no exit point is found, which shouldn't happen too often, this must be due to computational inaccuracies. In that case, no path segment is added, the current point is advanced by a small amount, and the new current cell - is determined by calling the function whichcell(). + is determined by calling the function cellIndex(). The algorithm that computes the exit point has the following input data: From 4d31565d50baf751da78c55c0e2bab87afb90b72 Mon Sep 17 00:00:00 2001 From: Peter Camps Date: Fri, 20 Dec 2024 11:27:07 +0100 Subject: [PATCH 51/51] trivial adjustments to comments --- SKIRT/core/TetraMeshSpatialGrid.cpp | 3 ++- SKIRT/core/TetraMeshSpatialGrid.hpp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/SKIRT/core/TetraMeshSpatialGrid.cpp b/SKIRT/core/TetraMeshSpatialGrid.cpp index 8c11ed1c..3aba1fe9 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.cpp +++ b/SKIRT/core/TetraMeshSpatialGrid.cpp @@ -803,12 +803,13 @@ class TetraMeshSpatialGrid::MySegmentGenerator : public PathSegmentGenerator { if (state() == State::Unknown) { - // moveInside does not move the photon packet inside the convex hull so it is currently disabled + // try moving the photon packet inside the grid; if this is impossible, return an empty path if (!moveInside(_grid->extent(), _grid->_eps)) return false; // get the index of the cell containing the current position _mr = _grid->cellIndex(r()); _enteringFace = -1; + // very rare edge case where no cell is found at domain boundary if (_mr == -1) return false; diff --git a/SKIRT/core/TetraMeshSpatialGrid.hpp b/SKIRT/core/TetraMeshSpatialGrid.hpp index e047960a..5e340bee 100644 --- a/SKIRT/core/TetraMeshSpatialGrid.hpp +++ b/SKIRT/core/TetraMeshSpatialGrid.hpp @@ -26,7 +26,7 @@ class tetgenio; - Delaunay Tetrahedralization: Generate a unique Delaunay tetrahedral mesh from a given set of vertices. - - Mesh Refinement: + - %Mesh Refinement: Refine the tetrahedral mesh using TetGen's Delaunay refinement algorithm. This option is enabled through the \em refine property of this class.