marchingcube是一个比较经典而古老的算法,也是面绘制中应用比较多的算法,Marchingcube发展到今天也遇到了几何拓扑、一致性的问题仍待改善。本文研究的就是经典的marchingcube(Paul,1997).
关于MarchingCube原理(含代码),重点推荐如下:
1.《医学图像三维重建和可视化:VC++实现实例》(张俊华)
2.Bourke P. Polygonising a scalar field[J]. 1997.
3.论文+源代码+交互界面
算法设计步骤:
1.二维断层图像顺序地读入内存并构成三位数据场;
2.依次扫描相邻两层数据,逐个构造立方体;
3.将立方体每个顶点灰度和给定等值面阈值进行比较,计算索引值;
4.对于含有等值面的立方体,通过灰度差分计算立方体各顶点的梯度;
5.根据索引值查找边索引表,获得和等值面有交点的当前立方体的相交边;
6.根据相交边的两个顶点坐标及其法向量,利用差值(中值)计算等值点的坐标与法向量;
7.根据索引值查找三角片索引表,确定当前立方体内构成三角片的等值点的组合方式;
8.由各个立方体内的三角面片构成等值面。
#include
#include #include #include using namespace std; static int edgeTable[256]={ }; //根据索引值确定立方体和等值面交点情况 static int triTable[256][16] ={ };//根据索引值确定三角面片的个数以及位置 typedef struct { double x,y,z; } XYZ; typedef struct { XYZ p[8]; //顶点坐标 XYZ n[8]; //法向量 double val[8]; //顶点对应的灰度值 } GRIDCELL; //立方体 typedef struct { XYZ p[3]; //顶点坐标 XYZ c; //几何中心 XYZ n[3]; //法向量 } TRIANGLE; #define ABS(x) (x < 0 ? -(x) : (x)) //声明 int PolygoniseCube(GRIDCELL , double , TRIANGLE * ); XYZ VertexInterp(double , XYZ , XYZ , double, double ); //3D数据场大小 #define NX 200 #define NY 160 #define NZ 160 int main( int argc, char **argv) { short int*** data = NULL; short int isolevel = 128, themax = 0, themin = 255; //等值面 数据场的范围 GRIDCELL grid; TRIANGLE triangles[10]; TRIANGLE* tri = NULL; //保存的是所有三角形面片的顶点坐标 int ntri = 0; FILE* fptr = NULL; //打开并阅读原始文件 fprintf(stderr, "Reading data...\n"); errno_t err; err = fopen_s(&fptr,"mri.raw", "rb"); if( err != NULL ) { fprintf(stderr, "File open failed\n"); exit(-1); } //为三维数据场分配空间 data = (short int***) malloc( NX * sizeof(short int **) ); for (int i=0;i themax ) themax = str; if( str < themin ) themin = str; } } } fclose(fptr); fprintf(stderr,"Volumetric data range: %d -> %d\n",themin,themax); //3D数据场处理 fprintf(stderr, "polygonising data...\n"); for (int i=0; i 4--------5 *---4----* /| /| /| /| / | / | 7 | 5 | / | / | / 8 / 9 7--------6 | *----6---* | | | | | | | | | | 0----|---1 | *---0|---* | / | / 11 / 10 / | / | / | 3 | 1 |/ |/ |/ |/ 3--------2 *---2----* ////////////////////////////////////////////////////////////////////////////*/ int PolygoniseCube(GRIDCELL grid, double isolevel, TRIANGLE* tri) { int numOftriangle = 0; int cubeIndex = 0; XYZ vertlist[12]; //保存等值面与立方体各边相交的坐标 //确定那个顶点位于等值面内部 if (grid.val[0] < isolevel) cubeIndex |= 1; if (grid.val[1] < isolevel) cubeIndex |= 2; if (grid.val[2] < isolevel) cubeIndex |= 4; if (grid.val[3] < isolevel) cubeIndex |= 8; if (grid.val[4] < isolevel) cubeIndex |= 16; if (grid.val[5] < isolevel) cubeIndex |= 32; if (grid.val[6] < isolevel) cubeIndex |= 64; if (grid.val[7] < isolevel) cubeIndex |= 128; //异常:立方体所有顶点都在或者都不在等值面内部 if ( edgeTable[cubeIndex] == 0 ) return 0; //确定等值面与立方体交点坐标 if (edgeTable[cubeIndex] & 1) { vertlist[0] = VertexInterp(isolevel,grid.p[0],grid.p[1],grid.val[0],grid.val[1]); } if (edgeTable[cubeIndex] & 2) { vertlist[1] = VertexInterp(isolevel,grid.p[1],grid.p[2],grid.val[1],grid.val[2]); } if (edgeTable[cubeIndex] & 4) { vertlist[2] = VertexInterp(isolevel,grid.p[2],grid.p[3],grid.val[2],grid.val[3]); } if (edgeTable[cubeIndex] & 8) { vertlist[3] = VertexInterp(isolevel,grid.p[3],grid.p[0],grid.val[3],grid.val[0]); } if (edgeTable[cubeIndex] & 16) { vertlist[4] = VertexInterp(isolevel,grid.p[4],grid.p[5],grid.val[4],grid.val[5]); } if (edgeTable[cubeIndex] & 32) { vertlist[5] = VertexInterp(isolevel,grid.p[5],grid.p[6],grid.val[5],grid.val[6]); } if (edgeTable[cubeIndex] & 64) { vertlist[6] = VertexInterp(isolevel,grid.p[6],grid.p[7],grid.val[6],grid.val[7]); } if (edgeTable[cubeIndex] & 128) { vertlist[7] = VertexInterp(isolevel,grid.p[7],grid.p[4],grid.val[7],grid.val[4]); } if (edgeTable[cubeIndex] & 256) { vertlist[8] = VertexInterp(isolevel,grid.p[0],grid.p[4],grid.val[0],grid.val[4]); } if (edgeTable[cubeIndex] & 512) { vertlist[9] = VertexInterp(isolevel,grid.p[1],grid.p[5],grid.val[1],grid.val[5]); } if (edgeTable[cubeIndex] & 1024) { vertlist[10] = VertexInterp(isolevel,grid.p[2],grid.p[6],grid.val[2],grid.val[6]); } if (edgeTable[cubeIndex] & 2048) { vertlist[11] = VertexInterp(isolevel,grid.p[3],grid.p[7],grid.val[3],grid.val[7]); } //根据交点坐标确定三角形面片,并进行保存 for (int i=0; triTable[cubeIndex][i] != -1; i+=3) { tri[numOftriangle].p[0] = vertlist[ triTable[cubeIndex][i ] ]; tri[numOftriangle].p[1] = vertlist[ triTable[cubeIndex][i+1] ]; tri[numOftriangle].p[2] = vertlist[ triTable[cubeIndex][i+2] ]; numOftriangle++; } return (numOftriangle); } //等值面与立方体交点坐标 XYZ VertexInterp(double isolevel, XYZ p1, XYZ p2, double valp1, double valp2) { XYZ p; if (ABS(isolevel-valp1) < 0.00001) return(p1); if (ABS(isolevel-valp2) < 0.00001) return(p2); if (ABS(valp1-valp2) < 0.00001) return(p1); double coef = (isolevel - valp1 ) / (valp2 - valp1); p.x = p1.x + coef * (p2.x - p1.x); p.y = p1.y + coef * (p2.y - p1.y); p.z = p1.z + coef * (p2.z - p1.z); return (p); }
1.学会定义结构体,例如三维空间的立方体,需要先定义一个坐标XYZ结构体,然后再定义一个立方体结构体,而他们之间恰恰是相互依赖的。
2.如何动态为三位数据场分配空间?typedef struct { double x,y,z; } XYZ; typedef struct { XYZ p[8]; //顶点坐标 XYZ n[8]; //法向量 double val[8]; //顶点对应的灰度值 } GRIDCELL; //立方体
我们要注意到,利用malloc()函数进行内存申请之后返回来的是void*型的指针,我们需要进行指针的强制类型转换。该段代码也给我一个很深的感悟就是"指针确实是一门艺术"。data = (short int***) malloc( NX * sizeof(short int **) ); for (int i=0;i
3.读取文件以及数据元素读取
读取文件可以采用fopen或fopen_s两个函数,不同之处在于前者需要提供两个参数,并返回FILE*类型;而后者需要提供三个参数,返回的是errno_t( 实质就是int的别名)类型。相比较而言,fopen_s更安全。errno_t err; err = fopen_s(&fptr,"mri.raw", "rb"); if( err != NULL ) { fprintf(stderr, "File open failed\n"); exit(-1); }
fgetc()顺序读取数据。int str; //从文件中读取字符 for (int k=0; k