简介
我已成功实施了Ju等人的Dual Contouring of Hermite Data。人。双轮廓是一种通过使用八叉树自适应地对带符号的距离场进行采样而获得的轮廓体积数据的方法。
该算法有两个步骤。第一部分计算与八叉树单元边缘的交点和法线。然后,它继续计算每个叶子单元的顶点,从而最小化二次误差函数。
算法的第二部分,通过将最小化顶点与四边形和三角形连接起来构建一个三角形网格,用于共享最小(最大深度)边缘的叶子单元。作者使用三个递归函数来达到这些最小边。
在每个八叉树单元格上调用 procCell
,并生成:
为每个节点的子节点调用procCell
,为任意两个相邻子节点共享的每个内部面调用procFace
调用12次,为每个内部边缘调用procEdge
6次调用由4个子节点共享。
procFace
最多可以产生4次调用procFace
,4个子节点共享的4个内部边缘中的每一个调用procEdge
4次
procEdge
可以产生2次对procEdge
的调用。
当所有节点都是叶节点时,递归终止,此时它继续使用四边形或三角形连接最小化4个相邻单元格中的顶点。这些递归函数具有遍历八叉树的双重图的效果。
我可以在帖子的底部找到我的粗略实现的递归双八叉树遍历,剥离不相关的代码。请注意,代码未经过优化,需要进行大量重构。
Ju et。 al。方法存在一些需要解决的问题。大多数Dual Contouring或Dual Marching Cubes算法变体,在学术文献中产生非流形网格,或创建自相交三角形。
对于对此主题感兴趣的其他人,我选择使用Manson和Schaefer在Isosurfaces Over Simplicial Partitions of Multiresolution Grids中描述的方法。该算法产生没有自相交的流形网格。它还具有有效表示尖锐特征(由符号距离场中的不连续性引起)以及薄特征的优点。
问题
第二种技术使用相同的递归函数procCell
,procFace
和procEdge
作为原始的双轮廓纸。我的最终目标是将大部分算法(核心外)移到GPU上,最终移到FPGA上。我正在使用OpenCL来实现这一目标。
为了做到这一点,我需要将算法转换为迭代算法。即使在CPU上,我也担心会导致调用堆栈溢出的递归。我的代码需要处理极大且深度的体积数据集。递归是前沿的事实尤其是一个大问题。
如何使用迭代实现相同的复杂树遍历?
Ju的代码。等。人
DualRecursive.hpp
#pragma once
#include <functional>
#include <Math/Vec.hpp>
namespace arc {
class DualTree;
class IDualTreeNode;
class DualTreeInternalNode;
class DualTreeHeteroNode;
class DualTreeHomoNode;
class LocationCode;
// const bool useHardNormalsFlag = true;
// Vertex corner indices for each of the 12 cube eges.
// Vertices are orientated -x to +x, -y to +y, and -z to +z.
const int edgeVertexMap[12][2] = {{0, 1},
{1, 2},
{3, 2},
{0, 3},
{4, 5},
{5, 6},
{7, 6},
{4, 7},
{0, 4},
{1, 5},
{2, 6},
{3, 7}};
// Used to call `edgeProc` from `cellProc`.
// 0-3 are the four child nodes which share one of the 6 internal edges of
// parent. 4 is the direction.
const int cellProcEdgeMask[6][5] = {{7, 4, 0, 3, 0},
{6, 5, 1, 2, 1},
{5, 1, 0, 4, 2},
{6, 2, 3, 7, 3},
{2, 3, 0, 1, 4},
{6, 7, 4, 5, 5}};
// Used to call `faceProc` from inside `cellProc`.
// 0 is frist node index
// 1 is second node index
// 2 is direction
const int cellProcFaceMask[12][3] = {{0, 1, 1},
{1, 2, 3},
{2, 3, 0},
{3, 0, 2},
{4, 5, 1},
{5, 6, 3},
{6, 7, 0},
{7, 4, 2},
{0, 4, 5},
{1, 5, 5},
{2, 6, 5},
{3, 7, 5}};
// Used to call `faceProc` from inside `faceProc`.
// 0-7 are node index pairs for adjacent faces
// 8 is direction
const int faceProcFaceMask[6][9] = {{7, 6, 4, 5, 0, 1, 3, 2, 0},
{6, 7, 5, 4, 1, 0, 2, 3, 1},
{5, 6, 1, 2, 0, 3, 4, 7, 2},
{6, 5, 2, 1, 3, 0, 7, 4, 3},
{2, 6, 3, 7, 0, 4, 1, 5, 4},
{6, 2, 7, 3, 4, 0, 5, 1, 5}};
// Used to call `edgeProc` from inside `faceProc`.
// 0-3 are node indices
// 4-7 are side orders
// 8 is direction
const int faceProcEdgeMask[6][4][9] = {
// Direction 0
{{4, 0, 1, 5, 0, 0, 1, 1, 2},
{3, 2, 1, 0, 0, 1, 1, 0, 4},
{7, 3, 2, 6, 0, 0, 1, 1, 3},
{7, 6, 5, 4, 0, 1, 1, 0, 5}},
// Direction 1
{{4, 0, 1, 5, 1, 1, 0, 0, 2},
{3, 2, 1, 0, 1, 0, 0, 1, 4},
{7, 3, 2, 6, 1, 1, 0, 0, 3},
{7, 6, 5, 4, 1, 0, 0, 1, 5}},
// Direction 2
{{1, 0, 3, 2, 0, 0, 1, 1, 4},
{4, 7, 3, 0, 0, 1, 1, 0, 0},
{5, 4, 7, 6, 0, 0, 1, 1, 5},
{5, 6, 2, 1, 0, 1, 1, 0, 1}},
// Direction 3
{{1, 0, 3, 2, 1, 1, 0, 0, 4},
{4, 7, 3, 0, 1, 0, 0, 1, 0},
{5, 4, 7, 6, 1, 1, 0, 0, 5},
{5, 6, 2, 1, 1, 0, 0, 1, 1}},
// Direction 4
{{3, 0, 4, 7, 0, 0, 1, 1, 0},
{1, 5, 4, 0, 0, 1, 1, 0, 2},
{2, 1, 5, 6, 0, 0, 1, 1, 1},
{2, 6, 7, 3, 0, 1, 1, 0, 3}},
// Direction 5
{{3, 0, 4, 7, 1, 1, 0, 0, 0},
{1, 5, 4, 0, 1, 0, 0, 1, 2},
{2, 1, 5, 6, 1, 1, 0, 0, 1},
{2, 6, 7, 3, 1, 0, 0, 1, 3}}};
// Used to call `edgeProc` from inside `edgeProc`.
// 0-1 are child nodes along current edge of first node,
// 2-3 are child nodes along current edge of second node,
// etc.
// 8 is the direction.
const int edgeProcEdgeMask[6][9] = {
{0, 1, 3, 2, 7, 6, 4, 5, 0},
{0, 1, 3, 2, 7, 6, 4, 5, 1},
{0, 3, 4, 7, 5, 6, 1, 2, 2},
{0, 3, 4, 7, 5, 6, 1, 2, 3},
{0, 4, 1, 5, 2, 6, 3, 7, 4},
{0, 4, 1, 5, 2, 6, 3, 7, 5},
};
void buildTreeMesh( DualTree* dualTree,
std::function<float( Vec3f const& )>& distFunction,
bool drawEdges = false );
void cellProc( IDualTreeNode* node, LocationCode code );
void faceProc( int mask[2],
int dir,
INode* nodes[2],
LocationCode codes[2] );
void edgeProc( int mask[4],
int dir,
INode* nodes[4],
LocationCode codes[4] );
} // namespace arc
DualRecursive.cpp
#include <array>
#include <cassert>
#include <functional>
#include <vector>
#include "DualRecursive.hpp"
namespace arc {
static std::function<float( Vec3f const& )> distF;
// -- buildTreeMesh function --
void buildTreeMesh( DualTree* dualTree,
std::function<float( Vec3f const& )>& distFunction,
bool drawEdges ) {
distF = distFunction;
dualTreeGlobal = dualTree;
LocationCode a;
cellProc( dualTree->rootNode, a );
}
// -- cellProc function --
void cellProc( IDualTreeNode* node, LocationCode code ) {
assert( node );
if( !node ) {
return;
}
auto t = node->getType();
if( t != NodeType::Internal ) {
return;
}
auto c = dynamic_cast<InternalNode*>( node )->getChildren();
// Process 8 cells for children of this internal node.
code.down();
for( size_t i = 0; i < 8; i++ ) {
cellProc( c[i], code );
if( i < 7 ) {
code.right();
}
}
code.up();
// Process 6 common edges shared by this internal node's children.
code.down();
for( int i = 0; i < 6; i++ ) {
int m[4];
m[0] = cellProcEdgeMask[i][0];
m[1] = cellProcEdgeMask[i][1];
m[2] = cellProcEdgeMask[i][2];
m[3] = cellProcEdgeMask[i][3];
INode* n[4];
n[0] = c[m[0]];
n[1] = c[m[1]];
n[2] = c[m[2]];
n[3] = c[m[3]];
LocationCode codes[4] = {code, code, code, code};
codes[0].setBack( m[0] );
codes[1].setBack( m[1] );
codes[2].setBack( m[2] );
codes[3].setBack( m[3] );
edgeProc( m, i, n, codes );
}
code.up();
// Process 12 pairs of touching faces in this node's children.
code.down();
for( int i = 0; i < 12; i++ ) {
int m[2];
m[0] = cellProcFaceMask[i][0];
m[1] = cellProcFaceMask[i][1];
auto dir = cellProcFaceMask[i][2];
INode* n[2];
n[0] = c[m[0]];
n[1] = c[m[1]];
LocationCode codes[2] = {code, code};
codes[0].setBack( m[0] );
codes[1].setBack( m[1] );
faceProc( m, dir, n, codes );
}
code.up();
}
// -- faceProc function --
void faceProc( int mask[2],
int dir,
INode* nodes[2],
LocationCode codes[2] ) {
NodeType t[2] = {nodes[0]->getType(), nodes[1]->getType()};
if( t[0] != NodeType::Internal && t[1] != NodeType::Internal ) {
} else {
if( t[0] == NodeType::Homo || t[1] == NodeType::Homo ) {
return;
}
// Process 4 pairs of adjacent faces which make up this face.
for( size_t i = 0; i < 4; i++ ) {
int m[2];
INode* n[2];
LocationCode c[2];
m[0] = faceProcFaceMask[dir][i * 2];
m[1] = faceProcFaceMask[dir][i * 2 + 1];
auto dirFromMask = faceProcFaceMask[dir][8];
assert( dirFromMask == dir );
if( t[0] == NodeType::Internal ) {
auto internal0 = (InternalNode*)nodes[0];
n[0] = internal0->getChildAt( m[0] );
c[0] = codes[0];
c[0].down();
c[0].setBack( m[0] );
} else {
n[0] = nodes[0];
c[0] = codes[0];
}
if( t[1] == NodeType::Internal ) {
auto internal1 = (InternalNode*)nodes[1];
n[1] = internal1->getChildAt( m[1] );
c[1] = codes[1];
c[1].down();
c[1].setBack( m[1] );
} else {
n[1] = nodes[1];
c[1] = codes[1];
}
faceProc( m, dir, n, c );
}
// Process edges internal to current face pair.
for( size_t i = 0; i < 4; i++ ) {
int m[4];
INode* n[4];
LocationCode c[4];
for( size_t j = 0; j < 4; j++ ) {
m[j] = faceProcEdgeMask[dir][i][j];
auto order = faceProcEdgeMask[dir][i][j + 4];
assert( order == 0 || order == 1 );
c[j] = codes[order];
assert( t[order] != NodeType::Homo );
if( t[order] == NodeType::Hetero ) {
n[j] = nodes[order];
} else {
c[j].down();
c[j].setBack( m[j] );
n[j] = dynamic_cast<InternalNode*>( nodes[order] )
->getChildAt( m[j] );
}
}
auto d = faceProcEdgeMask[dir][i][8];
edgeProc( m, d, n, c );
}
}
}
// -- edgeProc function --
void edgeProc( int mask[4],
int dir,
INode* nodes[4],
LocationCode codes[4] ) {
NodeType t[4] = {nodes[0]->getType(),
nodes[1]->getType(),
nodes[2]->getType(),
nodes[3]->getType()};
auto cond0 = t[0] == NodeType::Hetero || t[0] == NodeType::Homo;
auto cond1 = t[1] == NodeType::Hetero || t[1] == NodeType::Homo;
auto cond2 = t[2] == NodeType::Hetero || t[2] == NodeType::Homo;
auto cond3 = t[3] == NodeType::Hetero || t[3] == NodeType::Homo;
if( cond0 && cond1 && cond2 && cond3 ) {
// if( t[0] == NodeType::Hetero && t[1] == NodeType::Hetero &&
// t[2] == NodeType::Hetero && t[3] == NodeType::Hetero ) {
vector<size_t> indices;
vector<HeteroNode*> n;
vector<Vec3f> p;
vector<Vec3f> norms;
for( size_t i = 0; i < 4; i++ ) {
if( t[i] == NodeType::Hetero ) {
bool unique = true;
for( size_t j = 0; j < n.size(); j++ ) {
if( nodes[i] == (INode*)n[j] ) {
unique = false;
break;
}
}
if( unique ) {
indices.emplace_back( i );
n.emplace_back( (HeteroNode*)nodes[i] );
p.emplace_back( n.back()->hermiteData.minimizer );
norms.emplace_back( n.back()->hermiteData.normal );
}
}
}
if( indices.size() > 2 ) {
// Find deepest node.
int maxDepth = -1;
int iMaxDepth = -1;
for( size_t i = 0; i < indices.size(); i++ ) {
auto depth = codes[indices[i]].getDepth();
if( (int)depth > maxDepth ) {
maxDepth = depth;
iMaxDepth = indices[i];
}
}
assert( maxDepth > 0 );
assert( iMaxDepth > -1 );
assert( nodes[iMaxDepth]->getType() == NodeType::Hetero );
array<array<array<int, 2>, 3>, 4> fooTable;
fooTable[0][0] = {0, 1};
fooTable[0][1] = {0, 3};
fooTable[0][2] = {0, 4};
fooTable[1][0] = {2, 3};
fooTable[1][1] = {4, 7};
fooTable[1][2] = {1, 5};
fooTable[2][0] = {6, 7};
fooTable[2][1] = {5, 6};
fooTable[2][2] = {2, 6};
fooTable[3][0] = {4, 5};
fooTable[3][1] = {1, 2};
fooTable[3][2] = {3, 7};
int index0 = 0, index1 = 0;
index0 = fooTable[iMaxDepth][dir / 2][0];
index1 = fooTable[iMaxDepth][dir / 2][1];
auto heteroMaxDepth = (HeteroNode*)nodes[iMaxDepth];
auto codeMaxDepth = codes[iMaxDepth];
auto m0 = heteroMaxDepth->m[index0];
auto m1 = heteroMaxDepth->m[index1];
auto c0 = codeMaxDepth.getCorners()[index0];
auto c1 = codeMaxDepth.getCorners()[index1];
auto flipAxis =
m1 != Material::Vacuum ? ( c0 - c1 ) : ( c1 - c0 );
auto signChange = m0 != m1;
auto mSolid = m0 != Material::Vacuum ? m0 : m1;
if( signChange ) {
if( n.size() == 4 ) {
if( useHardNormalsFlag ) {
bool badNormal0 =
p[0] == p[1] || p[1] == p[2] || p[3] == p[0];
bool badNormal1 =
p[0] == p[2] || p[2] == p[3] || p[3] == p[0];
bool badNormal2 = lengthSquared( p[0] ) <= 0.0 ||
lengthSquared( p[1] ) <= 0.0 ||
lengthSquared( p[2] ) <= 0.0 ||
lengthSquared( p[3] ) <= 0.0;
// badNormal0 = false;
// badNormal1 = false;
// if( !badNormal0 && !badNormal1 && !badNormal2 ) {
if( true ) {
assert( p[0] != p[1] );
assert( p[1] != p[2] );
assert( p[2] != p[0] );
assert( p[0] != p[2] );
assert( p[2] != p[3] );
assert( p[3] != p[0] );
auto cross0 = cross( p[1] - p[0], p[2] - p[1] );
assert( lengthSquared( cross0 ) > 0.0 );
auto n0 = normalize(
cross( p[1] - p[0], p[2] - p[1] ) );
norms[0] = n0;
norms[1] = n0;
norms[2] = n0;
auto cross1 = cross( p[2] - p[0], p[3] - p[0] );
assert( lengthSquared( cross1 ) > 0.0 );
auto n1 = normalize( cross1 );
norms[0] = n1;
norms[2] = n1;
norms[3] = n1;
bool swap0 = false;
if( dot( n0, flipAxis ) < 0.0 ) {
n0 = -n0;
std::swap( p[0], p[2] );
swap0 = true;
}
array<Vec3f, 6> vNorms;
vNorms[0] = csg::calculateGrad( p[0], distF );
vNorms[1] = csg::calculateGrad( p[1], distF );
vNorms[2] = csg::calculateGrad( p[2], distF );
vNorms[0] = n0;
vNorms[1] = n0;
vNorms[2] = n0;
triangleMesh->emplace_back(
p[0], vNorms[0], color0 );
triangleMesh->emplace_back(
p[1], vNorms[1], color0 );
triangleMesh->emplace_back(
p[2], vNorms[2], color0 );
if( swap0 ) {
std::swap( p[0], p[2] );
}
if( dot( n1, flipAxis ) < 0.0 ) {
n1 = -n1;
std::swap( p[0], p[2] );
}
vNorms[3] = csg::calculateGrad( p[0], distF );
vNorms[4] = csg::calculateGrad( p[2], distF );
vNorms[5] = csg::calculateGrad( p[3], distF );
vNorms[3] = n1;
vNorms[4] = n1;
vNorms[5] = n1;
triangleMesh->emplace_back(
p[0], vNorms[3], color1 );
triangleMesh->emplace_back(
p[2], vNorms[4], color1 );
triangleMesh->emplace_back(
p[3], vNorms[5], color1 );
}
}
} else if( n.size() == 3 ) {
if( useHardNormalsFlag ) {
bool badNormal =
p[0] == p[1] || p[1] == p[2] || p[2] == p[0];
bool badNormal2 = lengthSquared( p[0] ) <= 0.0 ||
lengthSquared( p[1] ) <= 0.0 ||
lengthSquared( p[2] ) <= 0.0;
// badNormal = false;
// if( !badNormal && !badNormal2 ) {
if( true ) {
assert( p[0] != p[1] );
assert( p[1] != p[2] );
assert( p[2] != p[0] );
auto cross0 = cross( p[1] - p[0], p[2] - p[1] );
assert( lengthSquared( cross0 ) > 0.0 );
auto n0 = normalize( cross0 );
norms[0] = n0;
norms[1] = norms[0];
norms[2] = norms[0];
if( dot( n0, flipAxis ) < 0.0 ) {
n0 = -n0;
std::swap( p[0], p[2] );
}
array<Vec3f, 3> vNorms;
vNorms[0] = csg::calculateGrad( p[0], distF );
vNorms[1] = csg::calculateGrad( p[1], distF );
vNorms[2] = csg::calculateGrad( p[2], distF );
vNorms[0] = n0;
vNorms[1] = n0;
vNorms[2] = n0;
triangleMesh->emplace_back(
p[0], vNorms[0], color0 );
triangleMesh->emplace_back(
p[1], vNorms[1], color0 );
triangleMesh->emplace_back(
p[2], vNorms[2], color0 );
// getDebugRenderer()->drawLine(
// p[0], p[0] + 0.05 * vNorms[0], Magenta );
// getDebugRenderer()->drawLine(
// p[1], p[1] + 0.05 * vNorms[1], Magenta );
// getDebugRenderer()->drawLine(
// p[2], p[2] + 0.05 * vNorms[2], Magenta );
}
}
}
}
}
} else if( t[0] == NodeType::Internal || t[1] == NodeType::Internal ||
t[2] == NodeType::Internal || t[3] == NodeType::Internal ) {
auto test0 = codes[0] == LocationCode( {2, 0, 5} );
auto test1 = codes[1] == LocationCode( {2, 0, 4} );
auto test2 = codes[2] == LocationCode( {1, 3, 7} );
auto test3 = codes[3] == LocationCode( {1, 3, 6} );
auto test = test0 && test1 && test2 && test3;
// assert( !test );
size_t possibleHeteros = 0;
for( size_t i = 0; i < 4; i++ ) {
if( t[i] == NodeType::Internal || t[i] == NodeType::Hetero ) {
possibleHeteros++;
}
}
if( possibleHeteros > 2 ) {
for( size_t i = 0; i < 2; i++ ) {
int m[4];
INode* n[4];
LocationCode c[4];
assert( dir == edgeProcEdgeMask[dir][8] );
m[0] = edgeProcEdgeMask[dir][i];
m[1] = edgeProcEdgeMask[dir][i + 2];
m[2] = edgeProcEdgeMask[dir][i + 4];
m[3] = edgeProcEdgeMask[dir][i + 6];
for( size_t j = 0; j < 4; j++ ) {
if( t[j] == NodeType::Internal ) {
n[j] = dynamic_cast<InternalNode*>( nodes[j] )
->getChildAt( m[j] );
c[j] = codes[j];
c[j].down();
c[j].setBack( m[j] );
} else {
n[j] = nodes[j];
c[j] = codes[j];
}
}
edgeProc( m, dir, n, c );
}
}
}
}
} // namespace arc