C++ -- STL文件解析
1、 STL文件格式
STL文件是一种用许多空间小三角形面片逼近三维实体表面的3D模型。STL模型给出了组成三角形法向量的3个分量(用于确定三角面片的正反方向)及三角形的3个顶点坐标。一个完整的STL文件记录了组成实体模型的所有三角形面片的法向量数据和顶点坐标数据信息。STL文件格式包括二进制文件(BINARY)和文本文件(ASCII)两种。
1.1、STL的二进制格式
二进制STL文件用固定的字节数给出三角面片的几何信息。文件起始的80个字节是文件头,用于存储文件名;紧接着用4个字节的整数来描述模型的三角面片个数,后面逐个给出每个三角面片的几何信息。每个三角面片占用固定的50个字节,依次是3个4字节浮点数(面片的法向量),3个4字节浮点数(第1个顶点的坐标),3个4字节浮点数(第2个顶点的坐标),3个4字节浮点数(第3个顶点的坐标),最后2个字节用来描述三角面片的属性信息。一个完整二进制STL文件的大小为三角形面片数乘以50再加上84个字节,总共134个字节。
1.2、STL的ASCII文件格式
ASCII码格式的STL文件逐行给出三角面片的几何信息,每一行以1个或2个关键字开头。在STL文件中的三角面片的信息单元facet 是一个带矢量方向的三角面片,STL三维模型就是由一系列这样的三角面片构成。整个STL文件的首行给出了文件路径或文件名。在一个STL文件中,每一个 facet 由7行数据组成,facet normal 是三角面片指向实体外部的法矢量坐标,outer loop说明随后的3行数据分别是三角面片的3个顶点坐标,3顶点沿指向实体外部的法矢量方向逆时针排列。
ASCII格式的STL文件结构如下:
通过对STL两种文件格式的分析可知,二进制格式文件较小(通常是ASCII码格式的1/5),节省文件存储空间,而ASCII码格式的文件可读性更强,更容易进行进一步的数据处理。
2、代码处理
bool Mesh::readSTL_Binary(std::string fileName)
{
ifstream ifs(fileName.c_str(), ios::binary);
if (!ifs)
{
ifs.close();
cout << "read stl error" << endl;
return false;
}
vtx.clear();
tris.clear();
int intSize = sizeof(int);
int floatSize = sizeof(float);
ifs.ignore(80);
// 面的个数
int num_tris;
ifs.read((char*)(&num_tris), intSize);
cout << "面片数量:" << num_tris << endl;
float tn0, tn1, tn2;
float v0, v1, v2;
float cx = 0.0, cy = 0.0, cz = 0.0;
for (int i = 0; i < num_tris; i++)
{
ifs.read((char*)(&tn0), floatSize);
ifs.read((char*)(&tn1), floatSize);
ifs.read((char*)(&tn2), floatSize);
//如果模型进行坐标变换,需要重新计算法向量
// faceNrm.push_back(Normal(tn0, tn1, -tn2));
// 01-STL model
ifs.read((char*)(&v0), floatSize);
ifs.read((char*)(&v1), floatSize);
ifs.read((char*)(&v2), floatSize);
vtx.push_back(Vertex(v0, v1, v2));
cx += v0; cy += v1; cz += v2;
ifs.read((char*)(&v0), floatSize);
ifs.read((char*)(&v1), floatSize);
ifs.read((char*)(&v2), floatSize);
vtx.push_back(Vertex(v0, v1, v2));
cx += v0; cy += v1; cz += v2;
ifs.read((char*)(&v0), floatSize);
ifs.read((char*)(&v1), floatSize);
ifs.read((char*)(&v2), floatSize);
vtx.push_back(Vertex(v0, v1, v2));
cx += v0; cy += v1; cz += v2;
// 建立面片索引,确定顶点顺序
Tri tri;
tri.v1 = i * 3 + 0;
tri.v2 = i * 3 + 1;
tri.v3 = i * 3 + 2;
tris.push_back(tri);
ifs.ignore(2);
}
ifs.close();
// 重新计算面片法向量
if (0 == vtxNrm.size())
{
for (int i = 0; i < tris.size(); i++)
{
Vertex v12, v23;
v12 = vtx[tris[i].v2] - vtx[tris[i].v1];
v23 = vtx[tris[i].v3] - vtx[tris[i].v2];
Normal faceN;
faceN = v12.cross(v23);
faceN /= sqrt(faceN.dot(faceN));
faceNrm.push_back(faceN);
}
}
else
{
for (int i = 0; i < tris.size(); i++)
{
Normal faceN;
faceN = vtxNrm[tris[i].v1] + vtxNrm[tris[i].v2] + vtxNrm[tris[i].v3];
faceN /= sqrt(faceN.dot(faceN));
faceNrm.push_back(faceN);
}
}
// 计算中心位置
center.x = cx / (num_tris * 3);
center.y = cy / (num_tris * 3);
center.z = cz / (num_tris * 3);
//计算半径
radius = 0;
for (int i = 0; i < vtx.size(); i++)
{
vtx[i] = vtx[i] - center;
float lens;
lens = sqrt((vtx[i]).dot(vtx[i]));
if (lens > radius)
{
radius = lens;
}
}
center.x = 0;
center.y = 0;
center.z = 0;
return true;
}
bool Mesh::readSTL_ASCII(std::string fileName)
{
ifstream ifs(fileName.c_str(), ios::binary);
if (!ifs)
{
ifs.close();
cout << "read stl error" << endl;
return false;
}
vtx.clear();
tris.clear();
int intSize = sizeof(int);
int floatSize = sizeof(float);
float tn0, tn1, tn2;
float v0, v1, v2;
float cx = 0.0, cy = 0.0, cz = 0.0;
string name1, name2;
ifs >> name1 >> name2;
//cout << "name: " << name1 << " " << name2 << endl;
int t=0;
while(!ifs.eof())
{
string temp2;
ifs >> temp2;
if (temp2 == "facet")
{
string temp_normal;
ifs >> temp_normal;
ifs >> tn0 >> tn1 >> tn2;
//cout << "normal: " << tn0 << " " << tn1 << " " << tn2 << endl;
Normal faceN;
faceN.x = tn0;
faceN.y = tn1;
faceN.z = tn2;
faceNrm.push_back(faceN);
ifs.ignore(11);
string temp;
ifs >> temp;
//cout << "SSS:" << temp << endl;
while (temp == "vertex")
{
//cout << "=========================" << endl;
ifs >> v0 >> v1 >> v2;
//cout << "vertex: " << v0 << " " << v1 << " " << v2 << endl;
vtx.push_back(Vertex(v0, v1, v2));
cx += v0;
cy += v1;
cz += v2;
ifs >> temp;
//cout << "temp: " << temp << endl;
}
//cout << "end1: " << temp << endl;
ifs >> temp;
//cout << "end2: " << temp << endl;
}
if (temp2 != "endsolid")
{
Tri tri;
tri.v1 = t * 3 + 0;
tri.v2 = t * 3 + 1;
tri.v3 = t * 3 + 2;
tris.push_back(tri);
t = t + 1;
}
}
ifs.close();
// 计算中心位置
center.x = cx / (tris.size() * 3);
center.y = cy / (tris.size() * 3);
center.z = cz / (tris.size() * 3);
//计算半径
radius = 0;
for (int i = 0; i < vtx.size(); i++)
{
vtx[i] = vtx[i] - center;
float lens;
lens = sqrt((vtx[i]).dot(vtx[i]));
if (lens > radius)
{
radius = lens;
}
}
// 模型中心位置归零
center.x = 0;
center.y = 0;
center.z = 0;
return true;
}