This repository has been archived on 2023-10-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
blender-archive/extern/mantaflow/preprocessed/plugin/meshplugins.cpp
Sebastián Barschkis 4ff7c5eed6 Mantaflow [Part 1]: Added preprocessed Mantaflow source files
Includes preprocessed Mantaflow source files for both OpenMP and TBB (if OpenMP is not present, TBB files will be used instead).

These files come directly from the Mantaflow repository. Future updates to the core fluid solver will take place by updating the files.

Reviewed By: sergey, mont29

Maniphest Tasks: T59995

Differential Revision: https://developer.blender.org/D3850
2019-12-16 16:27:26 +01:00

781 lines
23 KiB
C++

// DO NOT EDIT !
// This file is generated using the MantaFlow preprocessor (prep generate).
/******************************************************************************
*
* MantaFlow fluid solver framework
* Copyright 2011 Tobias Pfaff, Nils Thuerey
*
* This program is free software, distributed under the terms of the
* Apache License, Version 2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Smoothing etc. for meshes
*
******************************************************************************/
/******************************************************************************/
// Copyright note:
//
// These functions (C) Chris Wojtan
// Long-term goal is to unify with his split&merge codebase
//
/******************************************************************************/
#include <queue>
#include <algorithm>
#include "mesh.h"
#include "kernel.h"
#include "edgecollapse.h"
#include <mesh.h>
#include <stack>
using namespace std;
namespace Manta {
//! Mesh smoothing
/*! see Desbrun 99 "Implicit fairing of of irregular meshes using diffusion and curvature flow"*/
void smoothMesh(Mesh &mesh, Real strength, int steps = 1, Real minLength = 1e-5)
{
const Real dt = mesh.getParent()->getDt();
const Real str = min(dt * strength, (Real)1);
mesh.rebuildQuickCheck();
// calculate original mesh volume
Vec3 origCM;
Real origVolume = mesh.computeCenterOfMass(origCM);
// temp vertices
const int numCorners = mesh.numTris() * 3;
const int numNodes = mesh.numNodes();
vector<Vec3> temp(numNodes);
vector<bool> visited(numNodes);
for (int s = 0; s < steps; s++) {
// reset markers
for (size_t i = 0; i < visited.size(); i++)
visited[i] = false;
for (int c = 0; c < numCorners; c++) {
const int node = mesh.corners(c).node;
if (visited[node])
continue;
const Vec3 pos = mesh.nodes(node).pos;
Vec3 dx(0.0);
Real totalLen = 0;
// rotate around vertex
set<int> &ring = mesh.get1Ring(node).nodes;
for (set<int>::iterator it = ring.begin(); it != ring.end(); it++) {
Vec3 edge = mesh.nodes(*it).pos - pos;
Real len = norm(edge);
if (len > minLength) {
dx += edge * (1.0 / len);
totalLen += len;
}
else {
totalLen = 0.0;
break;
}
}
visited[node] = true;
temp[node] = pos;
if (totalLen != 0)
temp[node] += dx * (str / totalLen);
}
// copy back
for (int n = 0; n < numNodes; n++)
if (!mesh.isNodeFixed(n))
mesh.nodes(n).pos = temp[n];
}
// calculate new mesh volume
Vec3 newCM;
Real newVolume = mesh.computeCenterOfMass(newCM);
// preserve volume : scale relative to CM
Real beta;
#if defined(WIN32) || defined(_WIN32)
beta = pow((Real)std::abs(origVolume / newVolume), (Real)(1. / 3.));
#else
beta = cbrt(origVolume / newVolume);
#endif
for (int n = 0; n < numNodes; n++)
if (!mesh.isNodeFixed(n))
mesh.nodes(n).pos = origCM + (mesh.nodes(n).pos - newCM) * beta;
}
static PyObject *_W_0(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
{
try {
PbArgs _args(_linargs, _kwds);
FluidSolver *parent = _args.obtainParent();
bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
pbPreparePlugin(parent, "smoothMesh", !noTiming);
PyObject *_retval = 0;
{
ArgLocker _lock;
Mesh &mesh = *_args.getPtr<Mesh>("mesh", 0, &_lock);
Real strength = _args.get<Real>("strength", 1, &_lock);
int steps = _args.getOpt<int>("steps", 2, 1, &_lock);
Real minLength = _args.getOpt<Real>("minLength", 3, 1e-5, &_lock);
_retval = getPyNone();
smoothMesh(mesh, strength, steps, minLength);
_args.check();
}
pbFinalizePlugin(parent, "smoothMesh", !noTiming);
return _retval;
}
catch (std::exception &e) {
pbSetError("smoothMesh", e.what());
return 0;
}
}
static const Pb::Register _RP_smoothMesh("", "smoothMesh", _W_0);
extern "C" {
void PbRegister_smoothMesh()
{
KEEP_UNUSED(_RP_smoothMesh);
}
}
//! Subdivide and edgecollapse to guarantee mesh with edgelengths between
//! min/maxLength and an angle below minAngle
void subdivideMesh(
Mesh &mesh, Real minAngle, Real minLength, Real maxLength, bool cutTubes = false)
{
// gather some statistics
int edgeSubdivs = 0, edgeCollsAngle = 0, edgeCollsLen = 0, edgeKill = 0;
mesh.rebuildQuickCheck();
vector<int> deletedNodes;
map<int, bool> taintedTris;
priority_queue<pair<Real, int>> pq;
//////////////////////////////////////////
// EDGE COLLAPSE //
// - particles marked for deletation //
//////////////////////////////////////////
for (int t = 0; t < mesh.numTris(); t++) {
if (taintedTris.find(t) != taintedTris.end())
continue;
// check if at least 2 nodes are marked for delete
bool k[3];
int numKill = 0;
for (int i = 0; i < 3; i++) {
k[i] = mesh.nodes(mesh.tris(t).c[i]).flags & Mesh::NfKillme;
if (k[i])
numKill++;
}
if (numKill < 2)
continue;
if (k[0] && k[1])
CollapseEdge(mesh,
t,
2,
mesh.getEdge(t, 0),
mesh.getNode(t, 0),
deletedNodes,
taintedTris,
edgeKill,
cutTubes);
else if (k[1] && k[2])
CollapseEdge(mesh,
t,
0,
mesh.getEdge(t, 1),
mesh.getNode(t, 1),
deletedNodes,
taintedTris,
edgeKill,
cutTubes);
else if (k[2] && k[0])
CollapseEdge(mesh,
t,
1,
mesh.getEdge(t, 2),
mesh.getNode(t, 2),
deletedNodes,
taintedTris,
edgeKill,
cutTubes);
}
//////////////////////////////////////////
// EDGE COLLAPSING //
// - based on small triangle angle //
//////////////////////////////////////////
if (minAngle > 0) {
for (int t = 0; t < mesh.numTris(); t++) {
// we only want to run through the edge list ONCE.
// we achieve this in a method very similar to the above subdivision method.
// if this triangle has already been deleted, ignore it
if (taintedTris.find(t) != taintedTris.end())
continue;
// first we find the angles of this triangle
Vec3 e0 = mesh.getEdge(t, 0), e1 = mesh.getEdge(t, 1), e2 = mesh.getEdge(t, 2);
Vec3 ne0 = e0;
Vec3 ne1 = e1;
Vec3 ne2 = e2;
normalize(ne0);
normalize(ne1);
normalize(ne2);
// Real thisArea = sqrMag(cross(-e2,e0));
// small angle approximation says sin(x) = arcsin(x) = x,
// arccos(x) = pi/2 - arcsin(x),
// cos(x) = dot(A,B),
// so angle is approximately 1 - dot(A,B).
Real angle[3];
angle[0] = 1.0 - dot(ne0, -ne2);
angle[1] = 1.0 - dot(ne1, -ne0);
angle[2] = 1.0 - dot(ne2, -ne1);
Real worstAngle = angle[0];
int which = 0;
if (angle[1] < worstAngle) {
worstAngle = angle[1];
which = 1;
}
if (angle[2] < worstAngle) {
worstAngle = angle[2];
which = 2;
}
// then we see if the angle is too small
if (worstAngle < minAngle) {
Vec3 edgevect;
Vec3 endpoint;
switch (which) {
case 0:
endpoint = mesh.getNode(t, 1);
edgevect = e1;
break;
case 1:
endpoint = mesh.getNode(t, 2);
edgevect = e2;
break;
case 2:
endpoint = mesh.getNode(t, 0);
edgevect = e0;
break;
default:
break;
}
CollapseEdge(mesh,
t,
which,
edgevect,
endpoint,
deletedNodes,
taintedTris,
edgeCollsAngle,
cutTubes);
}
}
}
//////////////////////
// EDGE SUBDIVISION //
//////////////////////
Real maxLength2 = maxLength * maxLength;
for (int t = 0; t < mesh.numTris(); t++) {
// first we find the maximum length edge in this triangle
Vec3 e0 = mesh.getEdge(t, 0), e1 = mesh.getEdge(t, 1), e2 = mesh.getEdge(t, 2);
Real d0 = normSquare(e0);
Real d1 = normSquare(e1);
Real d2 = normSquare(e2);
Real longest = max(d0, max(d1, d2));
if (longest > maxLength2) {
pq.push(pair<Real, int>(longest, t));
}
}
if (maxLength > 0) {
while (!pq.empty() && pq.top().first > maxLength2) {
// we only want to run through the edge list ONCE
// and we want to subdivide the original edges before we subdivide any newer, shorter edges,
// so whenever we subdivide, we add the 2 new triangles on the end of the SurfaceTri vector
// and mark the original subdivided triangles for deletion.
// when we are done subdividing, we delete the obsolete triangles
int triA = pq.top().second;
pq.pop();
if (taintedTris.find(triA) != taintedTris.end())
continue;
// first we find the maximum length edge in this triangle
Vec3 e0 = mesh.getEdge(triA, 0), e1 = mesh.getEdge(triA, 1), e2 = mesh.getEdge(triA, 2);
Real d0 = normSquare(e0);
Real d1 = normSquare(e1);
Real d2 = normSquare(e2);
Vec3 edgevect;
Vec3 endpoint;
int which;
if (d0 > d1) {
if (d0 > d2) {
edgevect = e0;
endpoint = mesh.getNode(triA, 0);
;
which = 2; // 2 opposite of edge 0-1
}
else {
edgevect = e2;
endpoint = mesh.getNode(triA, 2);
which = 1; // 1 opposite of edge 2-0
}
}
else {
if (d1 > d2) {
edgevect = e1;
endpoint = mesh.getNode(triA, 1);
which = 0; // 0 opposite of edge 1-2
}
else {
edgevect = e2;
endpoint = mesh.getNode(triA, 2);
which = 1; // 1 opposite of edge 2-0
}
}
// This edge is too long, so we split it in the middle
// *
// / \.
// /C0 \.
// / \.
// / \.
// / B \.
// / \.
// /C1 C2 \.
// *---------------*
// \C2 C1 /
// \ /
// \ A /
// \ /
// \ /
// \C0 /
// \ /
// *
//
// BECOMES
//
// *
// /|\.
// / | \.
// /C0|C0\.
// / | \.
// / B1 | B2 \.
// / | \.
// /C1 C2|C1 C2 \.
// *-------*-------*
// \C2 C1|C2 C1/
// \ | /
// \ A2 | A1 /
// \ | /
// \C0|C0/
// \ | /
// \|/
// *
int triB = -1;
bool haveB = false;
Corner ca_old[3], cb_old[3];
ca_old[0] = mesh.corners(triA, which);
ca_old[1] = mesh.corners(ca_old[0].next);
ca_old[2] = mesh.corners(ca_old[0].prev);
if (ca_old[0].opposite >= 0) {
cb_old[0] = mesh.corners(ca_old[0].opposite);
cb_old[1] = mesh.corners(cb_old[0].next);
cb_old[2] = mesh.corners(cb_old[0].prev);
triB = cb_old[0].tri;
haveB = true;
}
// else throw Error("nonmanifold");
// subdivide in the middle of the edge and create new triangles
Node newNode;
newNode.flags = 0;
newNode.pos = endpoint + 0.5 * edgevect; // fallback: linear average
// default: use butterfly
if (haveB)
newNode.pos = ModifiedButterflySubdivision(mesh, ca_old[0], cb_old[0], newNode.pos);
// find indices of two points of 'which'-edge
// merge flags
int P0 = ca_old[1].node;
int P1 = ca_old[2].node;
newNode.flags = mesh.nodes(P0).flags | mesh.nodes(P1).flags;
Real len0 = norm(mesh.nodes(P0).pos - newNode.pos);
Real len1 = norm(mesh.nodes(P1).pos - newNode.pos);
// remove P0/P1 1-ring connection
mesh.get1Ring(P0).nodes.erase(P1);
mesh.get1Ring(P1).nodes.erase(P0);
mesh.get1Ring(P0).tris.erase(triA);
mesh.get1Ring(P1).tris.erase(triA);
mesh.get1Ring(ca_old[0].node).tris.erase(triA);
if (haveB) {
mesh.get1Ring(P0).tris.erase(triB);
mesh.get1Ring(P1).tris.erase(triB);
mesh.get1Ring(cb_old[0].node).tris.erase(triB);
}
// init channel properties for new node
for (int i = 0; i < mesh.numNodeChannels(); i++) {
mesh.nodeChannel(i)->addInterpol(P0, P1, len0 / (len0 + len1));
}
// write to array
mesh.addTri(Triangle(ca_old[0].node, ca_old[1].node, mesh.numNodes()));
mesh.addTri(Triangle(ca_old[0].node, mesh.numNodes(), ca_old[2].node));
if (haveB) {
mesh.addTri(Triangle(cb_old[0].node, cb_old[1].node, mesh.numNodes()));
mesh.addTri(Triangle(cb_old[0].node, mesh.numNodes(), cb_old[2].node));
}
mesh.addNode(newNode);
const int nt = haveB ? 4 : 2;
int triA1 = mesh.numTris() - nt;
int triA2 = mesh.numTris() - nt + 1;
int triB1 = 0, triB2 = 0;
if (haveB) {
triB1 = mesh.numTris() - nt + 2;
triB2 = mesh.numTris() - nt + 3;
}
mesh.tris(triA1).flags = mesh.tris(triA).flags;
mesh.tris(triA2).flags = mesh.tris(triA).flags;
mesh.tris(triB1).flags = mesh.tris(triB).flags;
mesh.tris(triB2).flags = mesh.tris(triB).flags;
// connect new triangles to outside triangles,
// and connect outside triangles to these new ones
for (int c = 0; c < 3; c++)
mesh.addCorner(Corner(triA1, mesh.tris(triA1).c[c]));
for (int c = 0; c < 3; c++)
mesh.addCorner(Corner(triA2, mesh.tris(triA2).c[c]));
if (haveB) {
for (int c = 0; c < 3; c++)
mesh.addCorner(Corner(triB1, mesh.tris(triB1).c[c]));
for (int c = 0; c < 3; c++)
mesh.addCorner(Corner(triB2, mesh.tris(triB2).c[c]));
}
int baseIdx = 3 * (mesh.numTris() - nt);
Corner *cBase = &mesh.corners(baseIdx);
// set next/prev
for (int t = 0; t < nt; t++)
for (int c = 0; c < 3; c++) {
cBase[t * 3 + c].next = baseIdx + t * 3 + ((c + 1) % 3);
cBase[t * 3 + c].prev = baseIdx + t * 3 + ((c + 2) % 3);
}
// set opposites
// A1
cBase[0].opposite = haveB ? (baseIdx + 9) : -1;
cBase[1].opposite = baseIdx + 5;
cBase[2].opposite = -1;
if (ca_old[2].opposite >= 0) {
cBase[2].opposite = ca_old[2].opposite;
mesh.corners(cBase[2].opposite).opposite = baseIdx + 2;
}
// A2
cBase[3].opposite = haveB ? (baseIdx + 6) : -1;
cBase[4].opposite = -1;
if (ca_old[1].opposite >= 0) {
cBase[4].opposite = ca_old[1].opposite;
mesh.corners(cBase[4].opposite).opposite = baseIdx + 4;
}
cBase[5].opposite = baseIdx + 1;
if (haveB) {
// B1
cBase[6].opposite = baseIdx + 3;
cBase[7].opposite = baseIdx + 11;
cBase[8].opposite = -1;
if (cb_old[2].opposite >= 0) {
cBase[8].opposite = cb_old[2].opposite;
mesh.corners(cBase[8].opposite).opposite = baseIdx + 8;
}
// B2
cBase[9].opposite = baseIdx + 0;
cBase[10].opposite = -1;
if (cb_old[1].opposite >= 0) {
cBase[10].opposite = cb_old[1].opposite;
mesh.corners(cBase[10].opposite).opposite = baseIdx + 10;
}
cBase[11].opposite = baseIdx + 7;
}
////////////////////
// mark the two original triangles for deletion
taintedTris[triA] = true;
mesh.removeTriFromLookup(triA);
if (haveB) {
taintedTris[triB] = true;
mesh.removeTriFromLookup(triB);
}
Real areaA1 = mesh.getFaceArea(triA1), areaA2 = mesh.getFaceArea(triA2);
Real areaB1 = 0, areaB2 = 0;
if (haveB) {
areaB1 = mesh.getFaceArea(triB1);
areaB2 = mesh.getFaceArea(triB2);
}
// add channel props for new triangles
for (int i = 0; i < mesh.numTriChannels(); i++) {
mesh.triChannel(i)->addSplit(triA, areaA1 / (areaA1 + areaA2));
mesh.triChannel(i)->addSplit(triA, areaA2 / (areaA1 + areaA2));
if (haveB) {
mesh.triChannel(i)->addSplit(triB, areaB1 / (areaB1 + areaB2));
mesh.triChannel(i)->addSplit(triB, areaB2 / (areaB1 + areaB2));
}
}
// add the four new triangles to the prority queue
for (int i = mesh.numTris() - nt; i < mesh.numTris(); i++) {
// find the maximum length edge in this triangle
Vec3 ne0 = mesh.getEdge(i, 0), ne1 = mesh.getEdge(i, 1), ne2 = mesh.getEdge(i, 2);
Real nd0 = normSquare(ne0);
Real nd1 = normSquare(ne1);
Real nd2 = normSquare(ne2);
Real longest = max(nd0, max(nd1, nd2));
// longest = (int)(longest * 1e2) / 1e2; // HACK: truncate
pq.push(pair<Real, int>(longest, i));
}
edgeSubdivs++;
}
}
//////////////////////////////////////////
// EDGE COLLAPSING //
// - based on short edge length //
//////////////////////////////////////////
if (minLength > 0) {
const Real minLength2 = minLength * minLength;
for (int t = 0; t < mesh.numTris(); t++) {
// we only want to run through the edge list ONCE.
// we achieve this in a method very similar to the above subdivision method.
// NOTE:
// priority queue does not work so great in the edge collapse case,
// because collapsing one triangle affects the edge lengths
// of many neighbor triangles,
// and we do not update their maximum edge length in the queue.
// if this triangle has already been deleted, ignore it
// if(taintedTris[t])
// continue;
if (taintedTris.find(t) != taintedTris.end())
continue;
// first we find the minimum length edge in this triangle
Vec3 e0 = mesh.getEdge(t, 0), e1 = mesh.getEdge(t, 1), e2 = mesh.getEdge(t, 2);
Real d0 = normSquare(e0);
Real d1 = normSquare(e1);
Real d2 = normSquare(e2);
Vec3 edgevect;
Vec3 endpoint;
Real dist2;
int which;
if (d0 < d1) {
if (d0 < d2) {
dist2 = d0;
edgevect = e0;
endpoint = mesh.getNode(t, 0);
which = 2; // 2 opposite of edge 0-1
}
else {
dist2 = d2;
edgevect = e2;
endpoint = mesh.getNode(t, 2);
which = 1; // 1 opposite of edge 2-0
}
}
else {
if (d1 < d2) {
dist2 = d1;
edgevect = e1;
endpoint = mesh.getNode(t, 1);
which = 0; // 0 opposite of edge 1-2
}
else {
dist2 = d2;
edgevect = e2;
endpoint = mesh.getNode(t, 2);
which = 1; // 1 opposite of edge 2-0
}
}
// then we see if the min length edge is too short
if (dist2 < minLength2) {
CollapseEdge(
mesh, t, which, edgevect, endpoint, deletedNodes, taintedTris, edgeCollsLen, cutTubes);
}
}
}
// cleanup nodes and triangles marked for deletion
// we run backwards through the deleted array,
// replacing triangles with ones from the back
// (this avoids the potential problem of overwriting a triangle
// with a to-be-deleted triangle)
std::map<int, bool>::reverse_iterator tti = taintedTris.rbegin();
for (; tti != taintedTris.rend(); tti++)
mesh.removeTri(tti->first);
mesh.removeNodes(deletedNodes);
cout << "Surface subdivision finished with " << mesh.numNodes() << " surface nodes and "
<< mesh.numTris();
cout << " surface triangles, edgeSubdivs:" << edgeSubdivs << ", edgeCollapses: " << edgeCollsLen;
cout << " + " << edgeCollsAngle << " + " << edgeKill << endl;
// mesh.sanityCheck();
}
static PyObject *_W_1(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
{
try {
PbArgs _args(_linargs, _kwds);
FluidSolver *parent = _args.obtainParent();
bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
pbPreparePlugin(parent, "subdivideMesh", !noTiming);
PyObject *_retval = 0;
{
ArgLocker _lock;
Mesh &mesh = *_args.getPtr<Mesh>("mesh", 0, &_lock);
Real minAngle = _args.get<Real>("minAngle", 1, &_lock);
Real minLength = _args.get<Real>("minLength", 2, &_lock);
Real maxLength = _args.get<Real>("maxLength", 3, &_lock);
bool cutTubes = _args.getOpt<bool>("cutTubes", 4, false, &_lock);
_retval = getPyNone();
subdivideMesh(mesh, minAngle, minLength, maxLength, cutTubes);
_args.check();
}
pbFinalizePlugin(parent, "subdivideMesh", !noTiming);
return _retval;
}
catch (std::exception &e) {
pbSetError("subdivideMesh", e.what());
return 0;
}
}
static const Pb::Register _RP_subdivideMesh("", "subdivideMesh", _W_1);
extern "C" {
void PbRegister_subdivideMesh()
{
KEEP_UNUSED(_RP_subdivideMesh);
}
}
void killSmallComponents(Mesh &mesh, int elements = 10)
{
const int num = mesh.numTris();
vector<int> comp(num);
vector<int> numEl;
vector<int> deletedNodes;
vector<bool> isNodeDel(mesh.numNodes());
map<int, bool> taintedTris;
// enumerate components
int cur = 0;
for (int i = 0; i < num; i++) {
if (comp[i] == 0) {
cur++;
comp[i] = cur;
stack<int> stack;
stack.push(i);
int cnt = 1;
while (!stack.empty()) {
int tri = stack.top();
stack.pop();
for (int c = 0; c < 3; c++) {
int op = mesh.corners(tri, c).opposite;
if (op < 0)
continue;
int ntri = mesh.corners(op).tri;
if (comp[ntri] == 0) {
comp[ntri] = cur;
stack.push(ntri);
cnt++;
}
}
}
numEl.push_back(cnt);
}
}
// kill small components
for (int j = 0; j < num; j++) {
if (numEl[comp[j] - 1] < elements) {
taintedTris[j] = true;
for (int c = 0; c < 3; c++) {
int n = mesh.tris(j).c[c];
if (!isNodeDel[n]) {
isNodeDel[n] = true;
deletedNodes.push_back(n);
}
}
}
}
std::map<int, bool>::reverse_iterator tti = taintedTris.rbegin();
for (; tti != taintedTris.rend(); tti++)
mesh.removeTri(tti->first);
mesh.removeNodes(deletedNodes);
if (!taintedTris.empty())
cout << "Killed small components : " << deletedNodes.size() << " nodes, " << taintedTris.size()
<< " tris deleted." << endl;
}
static PyObject *_W_2(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
{
try {
PbArgs _args(_linargs, _kwds);
FluidSolver *parent = _args.obtainParent();
bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
pbPreparePlugin(parent, "killSmallComponents", !noTiming);
PyObject *_retval = 0;
{
ArgLocker _lock;
Mesh &mesh = *_args.getPtr<Mesh>("mesh", 0, &_lock);
int elements = _args.getOpt<int>("elements", 1, 10, &_lock);
_retval = getPyNone();
killSmallComponents(mesh, elements);
_args.check();
}
pbFinalizePlugin(parent, "killSmallComponents", !noTiming);
return _retval;
}
catch (std::exception &e) {
pbSetError("killSmallComponents", e.what());
return 0;
}
}
static const Pb::Register _RP_killSmallComponents("", "killSmallComponents", _W_2);
extern "C" {
void PbRegister_killSmallComponents()
{
KEEP_UNUSED(_RP_killSmallComponents);
}
}
} // namespace Manta