781 lines
23 KiB
C++
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 = nullptr;
|
|
{
|
|
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 = nullptr;
|
|
{
|
|
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 = nullptr;
|
|
{
|
|
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
|