mirror of
https://github.com/lucaspalomodevelop/meshlab.git
synced 2026-03-15 09:04:36 +00:00
429 lines
17 KiB
C++
429 lines
17 KiB
C++
#include "volume.h"
|
|
#include "myheap.h" // maxheap for correspondence band
|
|
#include "../filter_plymc/plymc.h" // remove bad triangles from marching cubes
|
|
#include <vcg/complex/algorithms/smooth.h> // mesh smoothing
|
|
|
|
using namespace vcg;
|
|
/// the array is used to scan a single voxel that contains the triangle in the initialization
|
|
const Point3i off[8] = { Point3i(0,0,0), Point3i(0,0,1), Point3i(0,1,0), Point3i(0,1,1),
|
|
Point3i(1,0,0), Point3i(1,0,1), Point3i(1,1,0), Point3i(1,1,1) };
|
|
// the array is used to scan the 26-neighborhood
|
|
const Point3i off26[26] = { Point3i(-1, -1, -1), Point3i(-1, 0, -1), Point3i(-1, +1, -1),
|
|
Point3i( 0, -1, -1), Point3i( 0, 0, -1), Point3i( 0, +1, -1),
|
|
Point3i( 1, -1, -1), Point3i( 1, 0, -1), Point3i( 1, +1, -1),
|
|
Point3i(-1, -1, 0), Point3i(-1, 0, 0), Point3i(-1, +1, 0),
|
|
Point3i( 0, -1, 0), /* skip center */ Point3i( 0, +1, 0),
|
|
Point3i( 1, -1, 0), Point3i( 1, 0, 0), Point3i( 1, +1, 0),
|
|
Point3i(-1, -1, 1), Point3i(-1, 0, 1), Point3i(-1, +1, 1),
|
|
Point3i( 0, -1, 1), Point3i( 0, 0, 1), Point3i( 0, +1, 1),
|
|
Point3i( 1, -1, 1), Point3i( 1, 0, 1), Point3i( 1, +1, 1) };
|
|
|
|
void MyVolume::init( int gridsize, int padsize, vcg::Box3f bbox ){
|
|
// Extract length of longest edge
|
|
int maxdimi = bbox.MaxDim();
|
|
Point3f dim = bbox.Dim();
|
|
float maxdim = dim[maxdimi]; // qDebug() << "MaxDim: " << maxdim;
|
|
this->delta = maxdim / ( gridsize ); // qDebug() << "Delta: " << delta;
|
|
this->padsize = padsize; // qDebug() << "Padsize: " << padsize;
|
|
this->bbox = bbox; // qDebug() << "bbox_m: " << bbox.min[0] << " " << bbox.min[1] << " " << bbox.min[2];
|
|
|
|
// Debug Stuff
|
|
if( false ){
|
|
Point3i offmin, offmax;
|
|
pos2off( bbox.min, offmin );
|
|
pos2off( bbox.max, offmax );
|
|
qDebug() << "Imin: (" << offmin[0] << " " << offmin[1] << " " << offmin[2] <<")";
|
|
qDebug() << "Imax: (" << offmax[0] << " " << offmax[1] << " " << offmax[2] <<")";
|
|
}
|
|
|
|
// Initialize the grid (uniform padding)
|
|
int dimx = ceil( gridsize * bbox.DimX() / maxdim ) + 2*padsize;
|
|
int dimy = ceil( gridsize * bbox.DimY() / maxdim ) + 2*padsize;
|
|
int dimz = ceil( gridsize * bbox.DimZ() / maxdim ) + 2*padsize;
|
|
|
|
this->sz = Point3i( dimx, dimy, dimz );
|
|
grid.Init( this->sz );
|
|
|
|
// Brute force initialize the grid... VCG doesn't have much...
|
|
for(int i=0;i<size(0);i++)
|
|
for(int j=0;j<size(1);j++)
|
|
for(int k=0;k<size(2);k++){
|
|
grid.Val(i,j,k) = 0;
|
|
}
|
|
|
|
// Allocate Slices & Painters
|
|
slices_2D[0] = QPixmap( dimz, dimy );
|
|
slices_2D[1] = QPixmap( dimx,dimz );
|
|
slices_2D[2] = QPixmap( dimx,dimy );
|
|
}
|
|
|
|
QPixmap& MyVolume::getSlice(int dim, int slice){
|
|
assert( dim>=0 && dim<3 );
|
|
|
|
float max_val = 0;
|
|
for(int i=0; i<size(0); i++)
|
|
for(int j=0; j<size(1); j++)
|
|
for(int k=0;k<size(2);k++)
|
|
max_val = std::max( grid.Val(i,j,k), max_val );
|
|
|
|
if( max_val== 0 )
|
|
max_val = 1;
|
|
|
|
// Create a painter for the dimension
|
|
QPainter painter( &slices_2D[dim]);
|
|
// Retrieve appropriate values
|
|
switch( dim ){
|
|
case 0:
|
|
for(int j=0; j<size(1); j++)
|
|
for(int k=0;k<size(2);k++){
|
|
float val = fabs( grid.Val(slice,j,k) ) / max_val * 255;
|
|
val = val<0?0:val;
|
|
val = val>255?255:val;
|
|
painter.setPen( QColor(val,val,val) );
|
|
painter.drawPoint(k,j);
|
|
}
|
|
break;
|
|
case 1:
|
|
for(int i=0; i<size(0); i++)
|
|
for(int k=0;k<size(2);k++){
|
|
float val = fabs( grid.Val(i,slice,k) ) / max_val * 255;
|
|
val = val<0?0:val;
|
|
val = val>255?255:val;
|
|
painter.setPen( QColor(val,val,val) );
|
|
painter.drawPoint(i,k);
|
|
}
|
|
break;
|
|
case 2:
|
|
for(int i=0; i<size(0); i++)
|
|
for(int j=0;j<size(1);j++){
|
|
float val = fabs( grid.Val(i,j,slice) ) / max_val * 255;
|
|
val = val<0?0:val;
|
|
val = val>255?255:val;
|
|
painter.setPen( QColor(val,val,val) );
|
|
// Flip image upside down
|
|
painter.drawPoint(i,size(1)-j-1);
|
|
}
|
|
break;
|
|
}
|
|
return slices_2D[dim];
|
|
}
|
|
|
|
void MyVolume::initField( const vcg::Box3f& inbbox ){
|
|
// Triangulate the bounding box
|
|
if( true ){
|
|
qDebug() << "constructing EDF of box: [" << vcg::toString(inbbox.min) << " " << vcg::toString(inbbox.max) << "]";
|
|
Point3f pos;
|
|
float d;
|
|
float sgn;
|
|
for(int i=0; i<size(0); i++) for(int j=0;j<size(1);j++) for(int k=0;k<size(2);k++){
|
|
// Get 3D coordinate of current voxel & compute distance to bbox
|
|
off2pos( i,j,k, pos );
|
|
d = DistancePoint3Box3( pos, inbbox );
|
|
sgn = inbbox.IsInEx( pos )==true?-1:1;
|
|
//if(d<2.0*delta)
|
|
grid.Val(i,j,k) = sgn*d;
|
|
//else
|
|
// grid.Val(i,j,k) = NAN;
|
|
grid.V(i,j,k).status = 0;
|
|
grid.V(i,j,k).face = 0;
|
|
}
|
|
}
|
|
// Completely fill volume with zeros
|
|
else if( false ){
|
|
for(int i=0; i<size(0); i++)
|
|
for(int j=0;j<size(1);j++)
|
|
for(int k=0;k<size(2);k++){
|
|
grid.Val(i,j,k) = 0;
|
|
}
|
|
}
|
|
// Fill sub-portion of volume
|
|
else if( false ){
|
|
for(int i=0; i<size(0); i++)
|
|
for(int j=0;j<size(1);j++)
|
|
for(int k=0;k<size(2);k++)
|
|
if( i>=2 && i<6 &&
|
|
j>=2 && j<6 &&
|
|
k>=2 && k<6 )
|
|
grid.Val(i,j,k) = -1;
|
|
else
|
|
grid.Val(i,j,k) = +1;
|
|
}
|
|
else if( true ){
|
|
// Radius in object space
|
|
double r = 1;
|
|
Point3f p;
|
|
Point3f center = bbox.Center();
|
|
qDebug() << "Sphere in: " << center[0] << " " << center[1] << " " << center[2] << endl;
|
|
center[1] = 0;
|
|
for(int i=0;i<size(0);i++)
|
|
for(int j=0;j<size(1);j++)
|
|
for(int k=0;k<size(2);k++){
|
|
// implicit sphere equation
|
|
off2pos(i,j,k,p);
|
|
p -= center;
|
|
// Cylinder along Y (vertical)
|
|
grid.Val(i,j,k) = p[0]*p[0] + p[2]*p[2] + p[1]*p[1] - r*r;
|
|
}
|
|
}
|
|
else{
|
|
// Void all volume
|
|
for(int i=0;i<size(0);i++) for(int j=0;j<size(1);j++) for(int k=0;k<size(2);k++)
|
|
grid.Val(i,j,k) = NAN;
|
|
// Radius in object space
|
|
double r = 1.1;
|
|
Point3f p;
|
|
Point3f center = bbox.Center();
|
|
qDebug() << "Cylinder along Z: " << center[0] << " " << center[1] << " " << center[2] << endl;
|
|
center[1] = 0;
|
|
|
|
for(int i=0;i<size(0);i++)
|
|
for(int j=0;j<size(1);j++)
|
|
for(int k=getPadding();k<size(2)-getPadding()+1;k++){
|
|
// implicit cylinder equation
|
|
off2pos(i,j,k,p);
|
|
p -= center;
|
|
// Cylinder along Y (vertical)
|
|
grid.Val(i,j,k) = p[0]*p[0] + p[1]*p[1] - r*r;
|
|
}
|
|
}
|
|
}
|
|
void MyVolume::initField( CMeshO& surface, GridAccell& accell ){
|
|
//--- Extract a new iso-surface, which linearly approximates surface in a marching cube-sense
|
|
isosurface( surface, 0 );
|
|
|
|
//--- Clear the current band
|
|
for(unsigned int i=0; i<band.size(); i++){
|
|
Point3i& voxi = band[i];
|
|
MyVoxel& v = Voxel(voxi);
|
|
v.status = 0;
|
|
v.face = 0;
|
|
v.index = 0;
|
|
v.field = NAN;
|
|
v.sfield = NAN;
|
|
}
|
|
band.clear();
|
|
|
|
//--- Compute active band distances around surface and correspondences
|
|
// Memory estimation: we move at most by 1 voxel, so we need to update at least the 2 neighborhood
|
|
// of a voxel on each side so that the implicit function will be correct, this resulting in 2 voxels
|
|
// per side thus: 2+2+1 per face.
|
|
const float DELTA = 2*getDelta();
|
|
band.reserve(5*surface.fn);
|
|
updateSurfaceCorrespondence( surface, accell, DELTA );
|
|
}
|
|
|
|
void MyVolume::isosurface( CMeshO& surf, float offset ){
|
|
// Run marching cubes on regular lattice getting isoband at "offset" distance
|
|
typedef vcg::tri::TrivialWalker<CMeshO, SimpleVolume<MyVoxel> > MyWalker;
|
|
typedef vcg::tri::MarchingCubes<CMeshO, MyWalker> MyMarchingCubes;
|
|
MyWalker walker;
|
|
MyMarchingCubes mc(surf, walker);
|
|
walker.BuildMesh<MyMarchingCubes>(surf, this->grid, mc, offset);
|
|
|
|
// Rescale the marching cube mesh
|
|
for(CMeshO::VertexIterator vi=surf.vert.begin();vi!=surf.vert.end();vi++){
|
|
(*vi).P()[0] = ((*vi).P()[0]-padsize) * delta + bbox.min[0];
|
|
(*vi).P()[1] = ((*vi).P()[1]-padsize) * delta + bbox.min[1];
|
|
(*vi).P()[2] = ((*vi).P()[2]-padsize) * delta + bbox.min[2];
|
|
}
|
|
|
|
//--- Remove slivers which might crash the whole system (reuses the one defined by plymc)
|
|
// Paolo: il parametro perc va dato in funzione della grandezza del voxel se gli dai come
|
|
// perc: perc=voxel.side/4 sei safe
|
|
surf.vert.EnableMark();
|
|
surf.vert.EnableVFAdjacency();
|
|
surf.face.EnableVFAdjacency();
|
|
tri::UpdateTopology<CMeshO>::VertexFace( surf );
|
|
tri::MCSimplify<CMeshO>( surf, getDelta()/4 );
|
|
|
|
//--- The simplify operation removed some vertices
|
|
tri::Allocator<CMeshO>::CompactVertexVector( surf );
|
|
tri::Allocator<CMeshO>::CompactFaceVector( surf );
|
|
|
|
//--- Final Cleanup
|
|
surf.face.EnableFFAdjacency();
|
|
tri::Clean<CMeshO>::RemoveTVertexByFlip(surf,20,true);
|
|
tri::Clean<CMeshO>::RemoveFaceFoldByFlip(surf);
|
|
|
|
#ifdef REMOVE_DEGENERATE_FACES
|
|
int total = tri::Clean<CMeshO>::MergeCloseVertex(surf, getDelta()/8);
|
|
qDebug("Successfully merged %d vertices", total);
|
|
// merging close might introduce non-manifolds
|
|
tri::Clean<CMeshO>::RemoveNonManifoldFace(surf);
|
|
// Make sure this is not messing up the topology
|
|
tri::Allocator<CMeshO>::CompactVertexVector( surf );
|
|
tri::Allocator<CMeshO>::CompactFaceVector( surf );
|
|
surf.face.EnableFFAdjacency();
|
|
surf.vert.EnableMark();
|
|
surf.vert.EnableVFAdjacency();
|
|
surf.face.EnableVFAdjacency();
|
|
tri::UpdateTopology<CMeshO>::VertexFace( surf );
|
|
#endif
|
|
|
|
//--- Post-processing
|
|
// tri::Smooth<CMeshO>::VertexCoordLaplacian(m.cm,stepSmoothNum,Selected,cb);
|
|
|
|
//--- Update surface normals
|
|
tri::UpdateNormals<CMeshO>::PerVertexNormalizedPerFaceNormalized( surf );
|
|
|
|
//--- Face orientation, needed to have more robust face distance tests
|
|
tri::UpdateFlags<CMeshO>::FaceProjection(surf);
|
|
|
|
//--- Attributes for curvature computation
|
|
surf.vert.EnableCurvature();
|
|
surf.vert.EnableCurvatureDir(); // quadric based needs it
|
|
}
|
|
|
|
/// gridaccell is needed only to retrieve the correct face=>voxel index
|
|
void MyVolume::updateSurfaceCorrespondence( CMeshO& surf, GridAccell& gridAccell, float DELTA ){
|
|
// The capacity of the band is estiamted to be enough to reach DELTA away
|
|
MinHeap<float> pq( band.capacity() );
|
|
|
|
//--- INITIALIZATION
|
|
Point3i o, newo;
|
|
Point3f p, newp;
|
|
float dist;
|
|
|
|
for(CMeshO::FaceIterator fi=surf.face.begin();fi!=surf.face.end();fi++){
|
|
// Compute hash index from face center
|
|
CFaceO& f = *(fi);
|
|
p = Barycenter( f );
|
|
// ADEBUG: skip invalid samples
|
|
if( math::IsNAN(p[0]) || math::IsNAN(p[1]) || math::IsNAN(p[2]) )
|
|
continue;
|
|
gridAccell.pos2off( p, o ); // Convert faces in OBJ space (OK!)
|
|
|
|
// Initialize the exploration queue for this face: compute the distance value for the 8 corners of the voxel containing
|
|
// the face making sure to set the right distance when a corner is covered by more than one face. Note that we set the flag
|
|
// to 2 (FINISHED) so that these values will never be inserted again in the queue after initialization
|
|
for( int i=0; i<8; i++ ){
|
|
// Offset the origin and compute new point
|
|
newo = Point3i(o[0]+off[i][0], o[1]+off[i][1], o[2]+off[i][2]);
|
|
MyVoxel& v = grid.V(newo[0], newo[1], newo[2]);
|
|
off2pos(newo, newp); // Convert offset to position (OK!)
|
|
float sdist = vcg::SignedFacePointDistance(f, newp) ;
|
|
dist = fabs(sdist);
|
|
// If has never been touched... insert it
|
|
if( v.status == 0 ){
|
|
v.field = dist;
|
|
//if(math::IsNAN(v.sfield)) v.sfield = sdist;
|
|
v.index = band.size();
|
|
v.face = &f;
|
|
v.status = 2;
|
|
pq.push(dist, v.index);
|
|
band.push_back(newo);
|
|
}
|
|
// It has been inserted already, but an update is needed
|
|
else if( v.status == 2 && dist < v.field ){
|
|
v.field = dist;
|
|
//v.sfield = sdist;
|
|
v.face = &f;
|
|
pq.push(dist, v.index);
|
|
}
|
|
}
|
|
}
|
|
|
|
//--- EVOLUTION
|
|
// Expansion front, whenever a new voxel is met, If distance is small enough:
|
|
// 1) add it to the active band (thus inheriting its position as index)
|
|
// 2) add neighbors to queue or update their distances if has improved
|
|
Point3f neigh_p;
|
|
Point3i neigh_o;
|
|
float debd;
|
|
int indx;
|
|
while( !pq.empty() ){
|
|
//--- Retrieve current voxel index and pop it
|
|
debd = pq.top().first;
|
|
indx = pq.top().second;
|
|
if( indx>band.size() ){
|
|
qDebug("bindex %d, bandsz %d", indx, int(band.size()));
|
|
assert( indx < band.size() );
|
|
}
|
|
pq.pop();
|
|
|
|
//--- Retrieve voxel & mark it as visited, also compute the real signed distance
|
|
// and set it in the voxel (fast marching used unsigned distance)
|
|
newo = band.at( indx );
|
|
MyVoxel& v = grid.V(newo[0], newo[1], newo[2]);
|
|
v.status = 2; // Never visit it again
|
|
CFaceO& f = *(v.face); // Parent supporting face
|
|
off2pos(newo, newp);
|
|
v.field = fabs(vcg::SignedFacePointDistance(f, newp));
|
|
if(math::IsNAN(v.sfield)) v.sfield= vcg::SignedFacePointDistance(f, newp);
|
|
|
|
//--- Visit its neighbors and (update | add) them to queue
|
|
for( int i=0; i<26; i++ ){
|
|
// Neighbor offset
|
|
neigh_o = Point3i(newo[0]+off26[i][0], newo[1]+off26[i][1], newo[2]+off26[i][2]);
|
|
MyVoxel& neigh_v = grid.V(neigh_o[0], neigh_o[1], neigh_o[2]);
|
|
if( neigh_v.status == 2 ) continue; // skip popped areas
|
|
off2pos(neigh_o, neigh_p);
|
|
dist = fabs( vcg::SignedFacePointDistance(f, neigh_p ) );
|
|
|
|
// Never add samples that go beyond the distance value. Note that the padding
|
|
// shuld allow for the voxels within this distance to be reached without creating
|
|
// off2pos assertion error.
|
|
|
|
// Only if it has never been touched, also, Never add samples that go beyond the
|
|
// DELTA distance value. Note that the padding shuld allow for the voxels within
|
|
// this distance to be reached without creating off2pos assertion error.
|
|
if( neigh_v.status == 0 && dist < DELTA ){
|
|
neigh_v.field = dist; // set distance
|
|
neigh_v.face = &f; // save face reference
|
|
neigh_v.status = 1; // mark as inserted
|
|
neigh_v.index = band.size(); // compute index
|
|
pq.push(dist, neigh_v.index); // insert it
|
|
band.push_back(neigh_o); // add to active voxels
|
|
}
|
|
// It has been inserted already, but update is needed
|
|
else if( neigh_v.status == 1 && dist < neigh_v.field && dist < DELTA ){
|
|
neigh_v.field = dist; // set new distance
|
|
neigh_v.face = &f; // save face reference
|
|
pq.push(dist, neigh_v.index); // add to active voxels
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
void MyVolume::render(){
|
|
if( !this->isInit() ) return;
|
|
|
|
qDebug() << "Volume::render()";
|
|
Point3f p;
|
|
MyVoxel v;
|
|
glDisable(GL_LIGHTING);
|
|
int padsize = 0;
|
|
for(int i=padsize;i<size(0)-padsize;i++)
|
|
for(int j=padsize;j<size(1)-padsize;j++)
|
|
for(int k=padsize;k<size(2)-padsize;k++){
|
|
v = grid.cV(i,j,k);
|
|
off2pos(i,j,k,p);
|
|
switch( v.status ){
|
|
case 0: glColor3f(1.0, 0.0, 0.0); break;
|
|
case 1: glColor3f(0.0, 1.0, 0.0); break;
|
|
case 2: glColor3f(0.0, 0.0, 1.0); break;
|
|
}
|
|
vcg::drawBox(p, .95*delta);
|
|
}
|
|
glEnable(GL_LIGHTING);
|
|
}
|
|
QString MyVolume::toString(int dim, int a){
|
|
assert(dim==2); // code below only slices along z
|
|
|
|
QString ret;
|
|
ret.append("\n");
|
|
for(int j=size(1)-1; j>=0; j--){
|
|
QString column;
|
|
for(int i=0; i<size(0); i++){ //scan row first
|
|
QString num;
|
|
num.sprintf("%+10.2f ", grid.Val(i,j,a));
|
|
column.append(num);
|
|
}
|
|
ret.append(column);
|
|
ret.append("\n");
|
|
}
|
|
return ret;
|
|
}
|