說起STL模型,相信使用過CAD三維軟件的人都不陌生,
STL = STL文件,一種3D模型文件格式STL(STereo Lithography的縮寫)
STL文件格式是由3D SYSTEMS 公司於1988 年制定的一個接口協議,是一種為快速原型制造技術服務的三維圖形文件格式。STL 文件由多個三角形面片的定義組成,每個三角形面片的定義包括三角形各個定點的三維坐標及三角形面片的法矢量。三角形頂點的排列順序遵循右手法則。 STL 文件有2 種類型:文本文件(ASCII格式)和二進制文件(BINARY)。
在此文中我們對ASCII格式的STL文件進行解析,並且用opengl對其進行顯示。
STL的ASCII格式如下:
solid filenamestl //文件路徑及文件名
facet normal x y z // 三角面片法向量的3個分量值
outer loop
vertex x y z ∥三角面片第一個頂點的坐標
vertex x y z // 三角面片第二個頂點的坐標
vertex x y z ∥三角面片第三個頂點的坐標
endloop
endfacet // 第一個三角面片定義完畢
……
……
endsolid filenamestl ∥整個文件結束
outer loop
vertex x y z ∥三角面片第一個頂點的坐標
vertex x y z // 三角面片第二個頂點的坐標
vertex x y z ∥三角面片第三個頂點的坐標
endloop
endfacet // 第一個三角面片定義完畢
……
……
endsolid filenamestl ∥整個文件結束
下面來說說處理這個解析的思路,首先我們可以按行一行行讀來下,去掉第一行開始一直往下讀,每讀一行都檢查是不是到了最后一行,但是這樣顯然效率不高,不如我們一開始把三角面片個個數求出來,循環中對每個三角面片進行解析,可以觀察出每個三角面片所占的ASCII文件的行數為7行,那么把行數讀出來除以七所得的商就是三角面片的個數,那么我們再一一對每個三角面片進行解析,實際上這個工作並不是很難,關鍵是要知道STL的格式與OPENGLBATCH的接口,OPENGL的接口實際上是一串float的數組,這里面保存了verts即頂點坐標信息,以及norms即法向量信息,那么剛好和我們的stl文件相一致。
以下是我們的核心代碼;
#include "windows.h" #include <GLTools.h> // OpenGL toolkit #include <GLMatrixStack.h> #include <GLFrame.h> #include <GLFrustum.h> #include <GLGeometryTransform.h> #include <math.h> #ifdef __APPLE__ #include <glut/glut.h> #else #define FREEGLUT_STATIC #include <GL/glut.h> #endif GLFrame viewFrame; GLFrustum viewFrustum; GLBatch triangleBatch; GLMatrixStack modelViewMatix; GLMatrixStack projectionMatrix; GLGeometryTransform transformPipeline; GLShaderManager shaderManager; int num; float* verts; float* vnorms; void getstlmodel() { int max=0; bool isbegin=false; long size=0; int nlines=0; int count1=0; int count2=0; FILE* file=fopen("mystl.stl","r"); fseek(file,0L,SEEK_END); size=ftell(file); fclose(file); file=fopen("mystl.stl","r"); for (int i=0;i<size;i++) { if(getc(file)=='\n') { nlines++; } } num=nlines/7; rewind(file); while (getc(file) != '\n'); verts=new float[9*num]; vnorms=new float[9*num]; for (int i=0;i<num;i++) { char x[200]=""; char y[200]=""; char z[200]=""; if(3!=fscanf(file,"%*s %*s %80s %80s %80s\n",x,y,z)) { break; } vnorms[count1]=vnorms[count1+3]=vnorms[count1+6]=atof(x); count1++; vnorms[count1]=vnorms[count1+3]=vnorms[count1+6]=atof(y); count1++; vnorms[count1]=vnorms[count1+3]=vnorms[count1+6]=atof(z); count1+=7; fscanf(file,"%*s %*s"); if (3!=fscanf(file,"%*s %80s %80s %80s\n",x,y,z)) { break; } if (isbegin==false) { isbegin=true; max=atof(z); } verts[count2]=atof(x); count2++; verts[count2]=atof(y); count2++; verts[count2]=atof(z); count2++; if (3!=fscanf(file,"%*s %80s %80s %80s\n",x,y,z)) { break; } verts[count2]=atof(x); count2++; verts[count2]=atof(y); count2++; verts[count2]=atof(z); count2++; if (3!=fscanf(file,"%*s %80s %80s %80s\n",x,y,z)) { break; } verts[count2]=atof(x); count2++; verts[count2]=atof(y); count2++; verts[count2]=atof(z); count2++; fscanf(file,"%*s"); fscanf(file,"%*s"); } } void SetupRC() { // Black background glClearColor(0.3f, 0.3f, 0.3f, 1.0f ); shaderManager.InitializeStockShaders(); viewFrame.MoveForward(1000.0f); triangleBatch.Begin(GL_TRIANGLES, num*3); triangleBatch.CopyVertexData3f(verts); triangleBatch.CopyNormalDataf(vnorms); triangleBatch.End(); // Make the torus } void SpecialKeys(int key, int x, int y) { if(key == GLUT_KEY_UP) viewFrame.RotateWorld(m3dDegToRad(-5.0), 1.0f, 0.0f, 0.0f); if(key == GLUT_KEY_DOWN) viewFrame.RotateWorld(m3dDegToRad(5.0), 1.0f, 0.0f, 0.0f); if(key == GLUT_KEY_LEFT) viewFrame.RotateWorld(m3dDegToRad(-5.0), 0.0f, 1.0f, 0.0f); if(key == GLUT_KEY_RIGHT) viewFrame.RotateWorld(m3dDegToRad(5.0), 0.0f, 1.0f, 0.0f); // Refresh the Window glutPostRedisplay(); } void ChangeSize(int w, int h) { // Prevent a divide by zero if(h == 0) h = 1; // Set Viewport to window dimensions glViewport(0, 0, w, h); viewFrustum.SetPerspective(35.0f, float(w)/float(h), 1.0f, 2000.0f); projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix()); transformPipeline.SetMatrixStacks(modelViewMatix, projectionMatrix); } void RenderScene(void) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnable(GL_CULL_FACE); glEnable(GL_DEPTH_TEST); modelViewMatix.PushMatrix(viewFrame); GLfloat vRed[] = { 1.0f, 0.0f, 0.0f, 1.0f }; //shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vRed); shaderManager.UseStockShader(GLT_SHADER_DEFAULT_LIGHT, transformPipeline.GetModelViewMatrix(), transformPipeline.GetProjectionMatrix(), vRed); triangleBatch.Draw(); modelViewMatix.PopMatrix(); glutSwapBuffers(); } int main(int argc, char* argv[]) { getstlmodel(); gltSetWorkingDirectory(argv[0]); glutInit(&argc, argv); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL); glutInitWindowSize(800, 600); glutCreateWindow("Geometry Test Program"); glutReshapeFunc(ChangeSize); glutDisplayFunc(RenderScene); glutSpecialFunc(SpecialKeys); GLenum err = glewInit(); if (GLEW_OK != err) { fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err)); return 1; } SetupRC(); glutMainLoop(); return 0; }
以下是我們解析stl模型生成圖形的實例:
其實,程序中還有一個地方還有缺陷,就是一開始我們設置的
viewFrame.MoveForward(1000.0f);
即是在里原點1000米的地方進行觀測,然后
viewFrustum.SetPerspective(35.0f, float(w)/float(h), 1.0f, 2000.0f);
這里是對投影的平截頭體進行的設置,我們也設置的為角度35,深度為1到2000,但是stl模型不一定是落在這個區域內,那這個時候我們就不一定能觀測得到圖像,或者只能觀測到部分圖像。
關於stl解析並且顯示的部分,在OPENCASCADE中也有功能非常強大的代碼,其中還包括了對二進制STL模型的解析和顯示,如果想深究的童鞋可以看看這個博客:http://www.cppblog.com/eryar/archive/2013/05/01/199882.aspx