Program Listing for File triangulation_layers.hpp¶
↰ Return to documentation for file (src/triangulation_layers.hpp)
/* This file is part of brille.
Copyright © 2019,2020 Greg Tucker <greg.tucker@stfc.ac.uk>
brille 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.
brille 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 brille. If not, see <https://www.gnu.org/licenses/>. */
#ifndef BRILLE_TRIANGULATION_HPP_
#define BRILLE_TRIANGULATION_HPP_
#include <set>
#include <vector>
#include <array>
#include <omp.h>
#include <cassert>
#include <algorithm>
#include "array_latvec.hpp" // defines bArray
#include "tetgen.h"
#include "debug.hpp"
#include "polyhedron.hpp"
#include "approx.hpp"
namespace brille {
template<class T, size_t N> static size_t find_first(const std::array<T,N>& x, const T val){
auto at = std::find(x.begin(), x.end(), val);
if (at == x.end()) throw std::logic_error("Value not found?!");
return std::distance(x.begin(), at);
}
class TetTriLayer{
using ind_t = brille::ind_t;
ind_t nVertices;
ind_t nTetrahedra;
bArray<double> vertex_positions; // (nVertices, 3)
bArray<ind_t> vertices_per_tetrahedron; // (nTetrahedra, 4)
std::vector<std::vector<ind_t>> tetrahedra_per_vertex; // (nVertices,)(1+,)
std::vector<std::vector<ind_t>> neighbours_per_tetrahedron; // (nTetrahedra,)(1+,)
bArray<double> circum_centres; // (nTetrahedra, 3);
std::vector<double> circum_radii; // (nTetrahedra,)
public:
ind_t number_of_vertices(void) const {return nVertices;}
ind_t number_of_tetrahedra(void) const {return nTetrahedra;}
const bArray<double>& get_vertex_positions(void) const {return vertex_positions;}
const bArray<ind_t>& get_vertices_per_tetrahedron(void) const {return vertices_per_tetrahedron;}
const bArray<double>& get_circum_centres(void) const {return circum_centres;}
const std::vector<double>& get_circum_radii(void) const {return circum_radii;}
Polyhedron get_tetrahedron(const ind_t idx) const {
if (nTetrahedra <= idx)
throw std::out_of_range("The requested tetrahedron does not exist.");
auto tet = vertices_per_tetrahedron.view(idx);
std::vector<std::vector<int>> vpf{{0,1,2},{0,2,3},{1,0,3},{1,3,2}};
return Polyhedron(vertex_positions.extract(tet), vpf);
}
explicit TetTriLayer()
: nVertices(0), nTetrahedra(0), vertex_positions(0u,3u),
vertices_per_tetrahedron(0u,4u), circum_centres(0u,3u)
{}
TetTriLayer(const tetgenio& tgio)
: vertex_positions(0u,3u), vertices_per_tetrahedron(0u,4u), circum_centres(0u,3u)
{
nVertices = static_cast<ind_t>(tgio.numberofpoints);
nTetrahedra = static_cast<ind_t>(tgio.numberoftetrahedra);
// copy-over all vertex positions:
vertex_positions.resize(nVertices);
for (ind_t i=0; i<nVertices; ++i) for (ind_t j=0; j<3u; ++j)
vertex_positions.val(i,j)=tgio.pointlist[3*i+j];
// copy-over all tetrahedron vertex indices
vertices_per_tetrahedron.resize(nTetrahedra);
for (ind_t i=0; i<nTetrahedra; ++i) for (ind_t j=0; j<4u; ++j)
vertices_per_tetrahedron.val(i,j) = static_cast<ind_t>(tgio.tetrahedronlist[i*tgio.numberofcorners+j]);
// Construct the tetrahedra per vertex vector of vectors
tetrahedra_per_vertex.resize(nVertices);
for (ind_t i=0; i<nVertices; ++i) for (ind_t j=0; j<nTetrahedra; ++j) for (ind_t k=0; k<4u; ++k)
if (vertices_per_tetrahedron.val(j,k)==i) tetrahedra_per_vertex[i].push_back(j);
// Construct the neighbours per tetrahedron vector of vectors
neighbours_per_tetrahedron.resize(nTetrahedra);
for (ind_t i=0; i<nTetrahedra; ++i)
for (ind_t j=0; j<4u; ++j)
if (tgio.neighborlist[i*4u+j] >= 0)
neighbours_per_tetrahedron[i].push_back(static_cast<ind_t>(tgio.neighborlist[i*4u+j]));
// ensure that all tetrahedra have positive (orient3d) volume
this->correct_tetrahedra_vertex_ordering();
// Calculate the circumsphere information:
this->determine_circumspheres();
}
// Create a string full of object information:
std::string to_string(void) const {
std::string str;
str = std::to_string(nVertices) + " vertices";
str += " in " + std::to_string(nTetrahedra) + " tetrahedra";
return str;
}
// ind_t locate(const bArray<double>& x, std::vector<ind_t>& v, std::vector<double>& w) const {
// // no specified tetrahedra to check against, so check them all
// std::vector<ind_t> tosearch(nTetrahedra);
// std::iota(tosearch.begin(), tosearch.end(), 0u);
// return locate(tosearch, x, v, w);
// }
// ind_t locate(const std::vector<ind_t>& tosearch, const bArray<double>& x, std::vector<ind_t>& v, std::vector<double>& w) const {
// if (x.ndim()!=2u || x.size(0)!=1u || x.size(1)!=3u)
// throw std::runtime_error("locate requires a single 3-element vector.");
// if (std::any_of(tosearch.begin(), tosearch.end(), [this](ind_t a){return a>=this->nTetrahedra;}))
// throw std::domain_error("Out-of-bounds tetrahedra index to search");
// ind_t found = this->unsafe_locate(tosearch, x, v, w);
// if (found >= nTetrahedra)
// throw std::runtime_error("The point was not located!");
// return found;
// }
// ind_t unsafe_locate(const std::vector<ind_t>& tosearch, const bArray<double>& x, std::vector<ind_t>& v, std::vector<double>& w) const {
// std::array<double,4> ws;
// v.clear();
// w.clear(); // make sure w is back to zero-elements
// for (ind_t idx: tosearch){
// if (this->unsafe_might_contain(idx, x) && this->unsafe_contains(idx, x, ws)){
// // unsafe_contains sets the weights in ws
// for (ind_t i=0; i<4u; ++i) if (!brille::approx::scalar(ws[i], 0.)){
// v.push_back(vertices_per_tetrahedron.val(idx,i));
// w.push_back(ws[i]);
// }
// return idx;
// }
// }
// return nTetrahedra;
// }
ind_t locate(const bArray<double>& x, std::vector<std::pair<ind_t,double>>& vw) const {
if (x.ndim()!=2u || x.size(0)!=1u || x.size(1)!=3u)
throw std::runtime_error("locate requires a single 3-element vector.");
return unsafe_locate(x, vw);
}
ind_t unsafe_locate(const bArray<double>& x, std::vector<std::pair<ind_t,double>>& vw) const {
// no specified tetrahedra to check against, so check them all
std::vector<ind_t> tosearch(nTetrahedra);
std::iota(tosearch.begin(), tosearch.end(), 0u);
return unsafe_locate(tosearch, x, vw);
}
ind_t locate(const std::vector<ind_t>& tosearch, const bArray<double>& x, std::vector<std::pair<ind_t,double>>& vw) const {
if (x.ndim()!=2u || x.size(0)!=1u || x.size(1)!=3u)
throw std::runtime_error("locate requires a single 3-element vector.");
if (std::any_of(tosearch.begin(), tosearch.end(), [this](ind_t a){return a>=this->nTetrahedra;}))
throw std::domain_error("Out-of-bounds tetrahedra index to search");
ind_t found = this->unsafe_locate(tosearch, x, vw);
if (found >= nTetrahedra)
throw std::runtime_error("The point was not located!");
return found;
}
ind_t unsafe_locate(const std::vector<ind_t>& tosearch, const bArray<double>& x, std::vector<std::pair<ind_t,double>>& vw) const {
std::array<double,4> ws;
vw.clear();// make sure w is back to zero-elements
for (ind_t idx: tosearch){
if (this->unsafe_might_contain(idx, x) && this->unsafe_contains(idx, x, ws)){
// unsafe_contains sets the weights in ws
for (ind_t i=0; i<4u; ++i) if (!brille::approx::scalar(ws[i], 0.))
vw.push_back(std::make_pair(vertices_per_tetrahedron.val(idx,i), ws[i]));
return idx;
}
}
return nTetrahedra;
}
std::vector<ind_t> neighbours(const ind_t vert) const {
if (vert >= this->nVertices){
std::string msg = "The provided vertex index is out of bounds";
throw std::out_of_range(msg);
}
std::vector<ind_t> n;
ind_t v;
for (ind_t t: this->tetrahedra_per_vertex[vert]) for (ind_t j=0; j<4u; ++j)
{
v = this->vertices_per_tetrahedron.val(t,j);
if ( v!= vert && std::find(n.begin(), n.end(), v) == n.end() ) n.push_back(v);
}
return n;
}
double volume(const ind_t tet) const {
const ind_t* i = vertices_per_tetrahedron.ptr(tet,0);
double v;
v = orient3d(
vertex_positions.ptr(i[0],0),
vertex_positions.ptr(i[1],0),
vertex_positions.ptr(i[2],0),
vertex_positions.ptr(i[3],0) )/6.0;
return v;
}
std::array<double,3> volume_statistics() const {
// total volume, minimum volume, maximum volume
std::array<double,3> vs{0.,(std::numeric_limits<double>::max)(),std::numeric_limits<double>::lowest()};
for (ind_t i=0; i<nTetrahedra; ++i){
double v = this->volume(i);
vs[0] += v; // add the volume to the total
if (v<vs[1]) vs[1] = v; // new minimum
if (v>vs[2]) vs[2] = v; // new maximum
}
return vs;
}
bool might_contain(const ind_t tet, const bArray<double>& x) const {
if (x.ndim()!=2u || x.size(0)!=1u || x.size(1)!=3u)
throw std::runtime_error("x must be a single 3-vector");
if (tet >= nTetrahedra) return false;
return this->unsafe_might_contain(tet, x);
}
bool contains(const ind_t tet, const bArray<double>& x) const{
if (x.ndim()!=2u || x.size(0)!=1u || x.size(1)!=3u)
throw std::runtime_error("x must be a single 3-vector");
if (tet >= nTetrahedra) return false;
return this->unsafe_contains(tet, x);
}
std::set<size_t> collect_keys() const {
long long ntets = brille::utils::u2s<long long, ind_t>(this->number_of_tetrahedra());
ind_t nvert = this->number_of_vertices();
std::set<size_t> keys;
#pragma omp parallel for default(none) shared(ntets, keys, nvert)
for (long long si=0; si<ntets; ++si){
ind_t i = brille::utils::s2u<ind_t, long long>(si);
auto v = vertices_per_tetrahedron.view(i).to_std();
std::set<size_t> t = permutation_table_keys_from_indicies(v.begin(), v.end(), nvert);
#pragma omp critical
{
keys.insert(t.begin(), t.end());
}
}
return keys;
}
protected:
bool unsafe_might_contain(const ind_t tet, const bArray<double>& x) const {
return norm(x-circum_centres.view(tet)).all(brille::cmp::le, circum_radii[tet]);
}
bool unsafe_contains(const ind_t tet, const bArray<double>& x) const {
std::array<double,4> w{0.,0.,0.,0.};
return this->unsafe_contains(tet,x,w);
}
bool unsafe_contains(const ind_t tet, const bArray<double>& x, std::array<double,4>& w) const {
this->weights(tet, x, w);
return std::all_of(w.begin(), w.end(), [](double z){ return (z>0.||brille::approx::scalar(z,0.)); });
}
void weights(const ind_t tet, const bArray<double>& x, std::array<double,4>& w) const {
const ind_t* i = vertices_per_tetrahedron.ptr(tet);
double vol6 = 6.0*this->volume(tet);
w[0] = orient3d(
x.ptr(0),
vertex_positions.ptr(i[1]),
vertex_positions.ptr(i[2]),
vertex_positions.ptr(i[3]) )/vol6;
w[1] = orient3d(
vertex_positions.ptr(i[0]),
x.ptr(0),
vertex_positions.ptr(i[2]),
vertex_positions.ptr(i[3]) )/vol6;
w[2] = orient3d(
vertex_positions.ptr(i[0]),
vertex_positions.ptr(i[1]),
x.ptr(0),
vertex_positions.ptr(i[3]) )/vol6;
w[3] = orient3d(
vertex_positions.ptr(i[0]),
vertex_positions.ptr(i[1]),
vertex_positions.ptr(i[2]),
x.ptr(0) )/vol6;
}
void correct_tetrahedra_vertex_ordering(void){
for (ind_t i=0; i<nTetrahedra; ++i)
if (std::signbit(this->volume(i))) // the volume of tetrahedra i is negative
vertices_per_tetrahedron.swap(i, 0,1); // swap two vertices to switch sign
}
void determine_circumspheres(void){
// ensure that the properties can hold all data
circum_centres.resize(nTetrahedra);
circum_radii.resize(nTetrahedra);
verbose_update("Pull together the circumsphere information for all tetrahedra");
tetgenmesh tgm; // to get access to circumsphere
for (ind_t i=0; i<nTetrahedra; ++i){
const ind_t* v = vertices_per_tetrahedron.ptr(i);
// use tetgen's circumsphere to find the centre and radius for each tetrahedra
tgm.circumsphere(
vertex_positions.ptr(v[0]),
vertex_positions.ptr(v[1]),
vertex_positions.ptr(v[2]),
vertex_positions.ptr(v[3]),
circum_centres.ptr(i), circum_radii.data()+i);
}
}
};
// If we ever get around to making the tetrahedra mesh refinable, then we will
// likely only ever need to refine the lowest layer or a TetTri object, followed
// by re-running find-connections from the next-highest layer.
// If we ever get around to *saving* the mesh to file, we will only need to save
// the *lowest* layer since we can produce a (possibly suboptimal) strata of
// meshes by taking convex-hull of the saved mesh and working down from there.
class TetTri{
using ind_t = brille::ind_t;
typedef std::vector<ind_t> TetSet;
// typedef std::map<size_t, TetSet> TetMap;
typedef std::vector<TetSet> TetMap;
//
std::vector<TetTriLayer> layers;
std::vector<TetMap> connections;
public:
explicit TetTri() {};
TetTri(const std::vector<TetTriLayer>& l)
: layers(l)
{
this->find_connections();
}
void find_connections(const size_t highest=0){
if (highest < layers.size()-1)
for (size_t i=highest; i<layers.size()-1; ++i)
connections.push_back(this->connect(i,i+1));
}
// make general locate and neighbour methods which drill-down through the layers
// ind_t locate(const bArray<double>& x, std::vector<ind_t>& v, std::vector<double>& w) const {
// if (x.ndim()!=2u || x.size(0)!=1u || x.size(1)!=3u)
// throw std::runtime_error("locate requires a single 3-element vector.");
// if (layers.size() < 1)
// throw std::runtime_error("Can not locate without triangulation");
// // find the point within the highest-layer tetrahedra:
// ind_t idx = layers[0].locate(x,v,w);
// // use the layer-connection map to restrict the search in the next layer's tetrahedra
// for (size_t i=1; i<layers.size(); ++i){
// const TetSet& tosearch = connections[i-1][idx];
// idx = layers[i].unsafe_locate(tosearch, x, v, w);
// }
// return idx;
// }
// ind_t locate(const bArray<double>& x, std::vector<ind_t>& v) const{
// std::vector<double> w;
// return this->locate(x, v, w);
// }
std::vector<std::pair<ind_t,double>>
locate(const bArray<double>& x) const {
if (x.ndim()!=2u || x.size(0)!=1u || x.size(1)!=3u)
throw std::runtime_error("locate requires a single 3-element vector.");
if (layers.size() < 1)
throw std::runtime_error("Can not locate without triangulation");
// setup temporary vertex(index) and weight vectors
std::vector<std::pair<ind_t,double>> vw;
// find the point within the highest-layer tetrahedra:
ind_t idx = layers[0].unsafe_locate(x,vw);
// use the layer-connection map to restrict the search in the next layer's tetrahedra
for (size_t i=1; i<layers.size(); ++i){
const TetSet& tosearch = connections[i-1][idx];
idx = layers[i].unsafe_locate(tosearch, x, vw);
}
return vw;
}
// return the neighbouring vertices to a provided mesh-vertex in the lowest layer.
std::vector<ind_t> neighbours(const bArray<double>& x) const {
std::vector<std::pair<ind_t,double>> vw = this->locate(x);
if (vw.size() != 1u){
std::string msg = "The provided point is not a mesh vertex.";
throw std::runtime_error(msg);
}
return layers.back().neighbours(vw[0].first);
}
std::vector<ind_t> neighbours(const ind_t v) const {
return layers.back().neighbours(v);
}
std::string to_string() const {
std::string str="";
if (layers.size()>1){
str += "Layers|";
for (auto layer: layers) str += layer.to_string() + "|";
} else {
str += layers[0].to_string();
}
return str;
}
// provide convenience functions which pass-through to the lowest layer
ind_t number_of_tetrahedra() const {return layers.back().number_of_tetrahedra(); }
ind_t number_of_vertices() const {return layers.back().number_of_vertices(); }
const bArray<double>& get_vertex_positions() const {return layers.back().get_vertex_positions(); }
const bArray<ind_t>& get_vertices_per_tetrahedron() const { return layers.back().get_vertices_per_tetrahedron(); }
std::set<size_t> collect_keys() const {return layers.back().collect_keys();}
private:
TetMap connect(const size_t high, const size_t low) const{
omp_set_num_threads(omp_get_max_threads());
Stopwatch<> stopwatch;
stopwatch.tic();
TetMap map(layers[high].number_of_tetrahedra());
long mapsize = brille::utils::u2s<long, ind_t>(map.size());
#if defined(__GNUC__) && !defined(__llvm__) && __GNUC__ < 9
// this version is necessary with g++ <= 8.3.0
#pragma omp parallel for default(none) shared(map, mapsize) schedule(dynamic)
#else
// this version is necessary with g++ == 9.2.0
#pragma omp parallel for default(none) shared(map, mapsize, high, low) schedule(dynamic)
#endif
for (long ui=0; ui<mapsize; ++ui){
ind_t i = brille::utils::s2u<ind_t, long>(ui);
// initialize the map
map[i] = TetSet();
auto cchi = layers[high].get_circum_centres().view(i);
// get a Polyhedron object for the ith higher-tetrahedra in case we need it
Polyhedron tethi = layers[high].get_tetrahedron(i);
std::vector<double> sumrad;
for (double r: layers[low].get_circum_radii()) sumrad.push_back(layers[high].get_circum_radii()[i]+r);
// if two circumsphere centers are closer than the sum of their radii
// they are close enough to possibly overlap:
auto close_enough = norm(layers[low].get_circum_centres() - cchi).is(brille::cmp::le, sumrad);
for (ind_t j=0; j < close_enough.size(); ++j) if (close_enough[j])
{
bool add = false;
// check if any vertex of the jth lower-tetrahedra is inside of the ith higher-tetrahedra
for (ind_t k=0; k<4u; ++k)
{
if (!add && layers[high].contains(i, layers[low].get_vertex_positions().view(layers[low].get_vertices_per_tetrahedron().val(j,k))))
add = true;
}
// even if no vertex is inside of the ith higher-tetrahedra, the two tetrahedra
// can overlap -- and checking for this overlap is complicated.
// make the Polyhedron class do the heavy lifting.
// if (add || tethi.intersects(ll.get_tetrahedron(j))) map[i].push_back(j);
if (add || layers[low].get_tetrahedron(j).intersects(tethi)) map[i].push_back(j);
}
}
stopwatch.toc();
info_update("Connect ",layers[high].number_of_tetrahedra()," to ",layers[low].number_of_tetrahedra()," completed in ",stopwatch.elapsed()," ms");
// we now have a TetMap which contains, for every tetrahedral index of the
// higher level, all tetrahedral indices of the lower level which touch the
// higher tetrahedron or share some part of its volume.
return map;
}
};
template <typename T>
TetTriLayer
triangulate_one_layer(const bArray<T>& verts,
const std::vector<std::vector<int>>& vpf,
const double max_cell_size=-1.0,
const int max_mesh_points=-1)
{
assert(verts.ndim()==2 && verts.size(1)==3); // otherwise we can't make a 3-D triangulation
// create the tetgenbehavior object which contains all options/switches for tetrahedralize
verbose_update("Creating `tetgenbehavior` object");
tetgenbehavior tgb;
tgb.plc = 1; // we will always tetrahedralize a piecewise linear complex
tgb.quality = 1; // we will (almost) always improve the tetrahedral mesh
tgb.neighout = 1; // we *need* the neighbour information to be stored into tgo.
if (max_cell_size > 0){
// volume constraint with a specified size
tgb.fixedvolume = 1;
tgb.maxvolume = max_cell_size;
} else{
//volume constraint without a specified size?
tgb.varvolume = 1;
}
if (max_mesh_points>0) tgb.steinerleft = max_mesh_points;
#ifndef VERBOSE_MESHING
tgb.quiet = 1;
#endif
#ifdef VERBOSE_MESHING
tgb.verbose = 10000;
#endif
// make the input and output tetgenio objects and fill the input with our polyhedron
verbose_update("Creating input and output `tetgenio` objects");
tetgenio tgi, tgo;
// we have to handle initializing points/facets, but tetgenio has a destructor
// which handles deleting all non-NULL fields.
verbose_update("Initialize and fill the input object's pointlist parameter");
tgi.numberofpoints = static_cast<int>(verts.size(0));
tgi.pointlist = nullptr;
tgi.pointlist = new double[3*tgi.numberofpoints];
tgi.pointmarkerlist = nullptr;
tgi.pointmarkerlist = new int[tgi.numberofpoints];
//tgi.point2tetlist = new int[tgi.numberofpoints];
using ind_t = brille::ind_t;
int idx=0;
for (ind_t i=0; i<verts.size(0); ++i){
tgi.pointmarkerlist[i] = static_cast<int>(i);
for (ind_t j=0; j<verts.size(1); ++j) tgi.pointlist[idx++] = verts.val(i,j);
}
verbose_update("Initialize and fill the input object's facetlist parameter");
tgi.numberoffacets = static_cast<int>(vpf.size());
tgi.facetlist = new tetgenio::facet[tgi.numberoffacets];
tgi.facetmarkerlist = nullptr;
tgi.facetmarkerlist = new int[tgi.numberoffacets];
for (ind_t i=0; i<vpf.size(); ++i){
tgi.facetmarkerlist[i] = static_cast<int>(i);
tgi.facetlist[i].numberofpolygons = 1;
tgi.facetlist[i].polygonlist = new tetgenio::polygon[1];
tgi.facetlist[i].polygonlist[0].numberofvertices = static_cast<int>(vpf[i].size());
tgi.facetlist[i].polygonlist[0].vertexlist = nullptr;
tgi.facetlist[i].polygonlist[0].vertexlist = new int[tgi.facetlist[i].polygonlist[0].numberofvertices];
for (ind_t j=0; j<vpf[i].size(); ++j)
tgi.facetlist[i].polygonlist[0].vertexlist[j] = vpf[i][j];
}
// The input is now filled with the piecewise linear complex information.
// so we can call tetrahedralize:
verbose_update("Calling tetgen::tetrahedralize");
try {
tetrahedralize(&tgb, &tgi, &tgo);
} catch (const std::logic_error& e) {
std::string msg = "tetgen threw a logic error with message\n" + std::string(e.what());
throw std::runtime_error(msg);
} catch (const std::runtime_error& e) {
std::string msg = "tetgen threw a runtime_error with message\n" + std::string(e.what());
throw std::runtime_error(msg);
} catch (...) {
std::string msg = "tetgen threw an undetermined error";
throw std::runtime_error(msg);
}
verbose_update("Constructing TetTriLayer object");
return TetTriLayer(tgo);
}
template <typename T>
TetTri
triangulate(const bArray<T>& verts,
const std::vector<std::vector<int>>& vpf,
const double max_cell_size=-1.0,
const int layer_count=5,
const int max_mesh_points=-1)
{
//
profile_update("Create layered triangulation");
assert(verts.ndim()==2 && verts.size(1)==3);
std::vector<TetTriLayer> layers;
if (layer_count < 2){
profile_update("Less than two layers requested");
layers.push_back(triangulate_one_layer(verts, vpf, max_cell_size, max_mesh_points));
} else {
profile_update(layer_count," layers requested");
// the highest-layer is the most-basic tetrahedral triangulation for the input
layers.push_back(triangulate_one_layer(verts, vpf, -1, max_mesh_points));
if (max_cell_size > 0.0){
std::array<double,3> layer_vol_stats = layers[0].volume_statistics();
profile_update("Highest layer has volume total, min, max: ", layer_vol_stats);
double highest_maxvol = layer_vol_stats[2];
TetTriLayer lowest = triangulate_one_layer(verts, vpf, max_cell_size, max_mesh_points);
layer_vol_stats = lowest.volume_statistics();
profile_update(" Lowest layer has volume total, min, max: ", layer_vol_stats);
double lowest_maxvol = layer_vol_stats[2];
double exponent = std::log(highest_maxvol/lowest_maxvol)/std::log(static_cast<double>(layer_count));
profile_update("So an exponent of ",exponent," will be used.");
for (int i=layer_count-1; i>1; --i){ // start from second highest layer, finish at second lowest
double layer_maxvol = lowest_maxvol * std::pow(static_cast<double>(i), exponent);
layers.push_back(triangulate_one_layer(verts, vpf, layer_maxvol, max_mesh_points));
}
// keep the lowest layer as well
layers.push_back(lowest);
}
}
return TetTri(layers);
}
} // end namespace brille
#endif // _TRIANGULATION_H_