C++ -- STL文件解析

2023-05-16

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、代码处理

读取和保存需要的数据结构定义如下,代码文件Mesh.h

#pragma once
#include<iostream>
#include<math.h>
#include<vector>
#include<string>
#include<fstream>
#include<sstream>
#include<opencv2/opencv.hpp>

using namespace std;

// 数据结构----顶点、法向、三角形 
typedef cv::Point3f Vertex;
typedef cv::Point3f Normal;
struct Tri
{
	int v1;
	int v2;
	int v3;
};

struct VtxIdxSortItem {
	int i;
	Vertex value;
};

class Mesh
{
public:
	std::vector<Vertex> vtx;          /// 顶点
	std::vector<Tri> tris;            /// 三角面片
	std::vector<Normal> vtxNrm;       /// 顶点法向
	std::vector<Normal> faceNrm;      /// 面法向
	
	cv::Point3f center;               /// 模型中心
	float radius;                     /// 模型半径

	std::vector<Vertex> mkh_joints;
	std::vector<Vertex> scan_joints;
	std::string modelname;
	std::string scan_joint_path;

public:
	Mesh();
	~Mesh();

	// 解析STL模型
	bool readSTL_ASCII(std::string cfilename);
	bool saveSTL_ASCII(const char *filename, std::vector<Vertex>&vtx, std::vector<Tri> &tris, std::vector<Normal> &nor);
	void UnifyDuplicatedVertices(vector<Vertex> &vtx, vector<Tri> &tris);

	bool readSTL_Binary(std::string cfilename);
	bool Mesh::saveSTL_Binary(const char *filename, std::vector<Vertex>&vtx, std::vector<Tri> &tris, std::vector<Normal> &nor);
};

下面代码是读取和保存Binary格式的代码,代码文件Mesh.cpp

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::saveSTL_Binary(const char *filename, std::vector<Vertex>&vtx, std::vector<Tri> &tris, std::vector<Normal> &nor)
{
	ofstream fs(filename, ios::binary);
	if (!fs) { fs.close(); return false; }

	int intSize = sizeof(int);
	int floatSize = sizeof(float);
	// 文件头
	char* fileHead = " ";
	fs.write(fileHead, sizeof(char) * 3);
	
	// 附加信息
	char fileInfo[77];
	for (int i = 0; i<77; i++) 
		fileInfo[i] = ' ';
	fs.write(fileInfo, sizeof(char) * 77);
	
	// 面的个数
	int num_tris = int(tris.size());
	fs.write((char*)(&num_tris), intSize);
	
	// 点列表,面列表
	char a[2];
	streamsize a_size = sizeof(char) * 2;
	Normal tn;
	for (int i = 0; i<num_tris; i++)
	{
		int PIndex0 = tris[i].v1;
		int PIndex1 = tris[i].v2;
		int PIndex2 = tris[i].v3;

		Vertex P0 = vtx[PIndex0];
		Vertex P1 = vtx[PIndex1];
		Vertex P2 = vtx[PIndex2];

		Normal N0 = nor[i];
		//Normal N1 = faceNrm[i].y;
		//Normal N2 = faceNrm[i].z;

		//tn = N0 + N1 + N2;
		fs.write((char*)(&(N0.x)), floatSize);
		fs.write((char*)(&(N0.y)), floatSize);
		fs.write((char*)(&(N0.z)), floatSize);

		// 保存顶点
		fs.write((char*)(&(P0.x)), floatSize);
		fs.write((char*)(&(P0.y)), floatSize);
		fs.write((char*)(&(P0.z)), floatSize);

		fs.write((char*)(&(P1.x)), floatSize);
		fs.write((char*)(&(P1.y)), floatSize);
		fs.write((char*)(&(P1.z)), floatSize);

		fs.write((char*)(&(P2.x)), floatSize);
		fs.write((char*)(&(P2.y)), floatSize);
		fs.write((char*)(&(P2.z)), floatSize);
		fs.write(a, a_size);
	}
	fs.close();

	return true;
}

下面代码是读取和保存ASCII格式的代码,代码文件Mesh.cpp

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 j = 3, t = 0;

	while (!ifs.eof())  // ifs.good()
	{
		string temp2;
		ifs >> temp2;
		//cout << "temp2: " << temp2 << endl;
		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;

			{
				Tri tri;
				tri.v1 = t * 3 + 0;
				tri.v2 = t * 3 + 1;
				tri.v3 = t * 3 + 2;
				tris.push_back(tri);
				t = t + 1;
			}
		}

		cout << "end string " << temp2 << endl;
	}
	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;
}
bool Mesh::saveSTL_ASCII(const char *filename, std::vector<Vertex>&vtx, std::vector<Tri> &tris, std::vector<Normal> &nor)
{
	// vtx:模型顶点
	// tris:三角面片
	// nor:法线
	ofstream fs(filename);
	if (!fs) { fs.close(); return false; }

	int i=0;
	int intSize = sizeof(int);
	int floatSize = sizeof(float);
		
	int num_tris = tris.size();
	cout << nor.size() << endl;
	cout << num_tris << endl;

	fs << "solid WRAP" << endl;
	for (i = 0; i<num_tris; i++)
	{
		fs << "facet normal " << nor[i].x << " " << nor[i].y << " " << nor[i].z << endl;
		fs << "outer loop" << endl;
		fs << "vertex " << vtx[tris[i].v1].x << " " << vtx[tris[i].v1].y << " " << vtx[tris[i].v1].z << endl;
		fs << "vertex " << vtx[tris[i].v2].x << " " << vtx[tris[i].v2].y << " " << vtx[tris[i].v2].z << endl;
		fs << "vertex " << vtx[tris[i].v3].x << " " << vtx[tris[i].v3].y << " " << vtx[tris[i].v3].z << endl;
		fs << "end loop" << endl;
		fs << "end facet" << endl;
	}
	fs << "endsolid WRAP";
	fs.close();

	return true;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++ -- STL文件解析 的相关文章

  • 带有自定义分配器的 std::string

    所以我目前正在编写一个内存调试器 为此我需要 stl 容器对象来使用未跟踪的分配器 我的整个代码库中都散布了 std string 因此我将其键入以使用未跟踪的分配器 typedef std basic string
  • std::string 在 Visual Studio 上的具体行为?

    我有一个项目需要读取 写入大文件 我决定使用 ifstream read 将这些文件一次性放入内存中 放入 std string 中 这似乎是在 C 中执行此操作的最快方法 http insanecoding blogspot com 20
  • std::类似向量的类经过优化以容纳少量项目[重复]

    这个问题在这里已经有答案了 在程序的一个时间关键部分中 有一个类成员如下所示 std vector m vLinks 在分析过程中 我注意到该向量大约 99 98 的执行仅包含 0 或 1 个项目 然而 在极少数情况下 它可能会容纳更多 根
  • 可能的 std::async 实现错误 Windows

    看来 std async 的 Windows 实现存在错误 在重负载下 大约每秒启动 1000 个异步线程 异步任务永远不会被调度 并且等待返回的 future 会导致死锁 请参阅这段代码 使用延迟启动策略而不是异步进行修改 Bundlin
  • std::list::clear 是否会使 std::list::end 迭代器无效?

    检查这个代码 include stdafx h include
  • std::map 和二叉搜索树

    我读过 std map 是使用二叉搜索树数据结构实现的 BST 是一种顺序数据结构 类似于数组中的元素 它将元素存储在 BST 节点中并按其顺序维护元素 例如如果元素小于节点 则将其存储在节点的左侧 如果元素大于节点 则将其存储在节点的右侧
  • STL之类的容器typedef快捷方式?

    STL 容器的常见模式是这样的 map
  • 对 STL 容器的安全并行只读访问

    我想要访问基于 STL 的容器只读 from parallel运行线程 无需使用任何用户实现的锁定 以下代码的基础是 C 11 并正确实现了该标准 http gcc gnu org onlinedocs libstdc manual usi
  • const_iterators 更快吗?

    我们的编码指南更喜欢const iterator 因为它们比正常的要快一点iterator 当您使用时 编译器似乎会优化代码const iterator 这真的正确吗 如果是的话 内部到底发生了什么使得const iterator快点 编辑
  • STL 映射和集合中的排序顺序

    用户定义的对象在map和set中是如何排序的 据我所知 映射 集合是排序关联容器 插入的元素根据它所持有的键进行排序 但map和set内部使用operator gt 对它们的元素进行排序 从 SGI 网站上 我有以下示例 struct lt
  • 关于 C++ 中的 STL 容器的问题

    std multimap 和 std unordered multimap 多久洗一次条目 我这么问是因为我的代码传递引用来区分具有相同哈希的条目 并且我想知道何时对它们运行引用重定向功能 如果我这样做会发生什么 std multimap
  • 通过保留和复制来复制向量,还是通过创建和交换来复制向量更有效? [复制]

    这个问题在这里已经有答案了 我正在尝试有效地复制向量 我看到两种可能的方法 std vector
  • is_integral 与 is_integer:其中之一是多余的吗?

    是积分 http en cppreference com w cpp types is integral and 是整数 http en cppreference com w cpp types numeric limits is inte
  • 无效的模板相关成员函数模板推导 - 认为我正在尝试使用 std::set

    我有一个继承自基类模板的类模板 基类模板有一个数据成员和一个成员函数模板 我想从我的超类中调用它 我知道为了消除对成员函数模板的调用的歧义 我必须使用template关键字 我必须明确引用this在超级班里 this gt base mem
  • EASTL 与 STL 相比,std::vector::operator[] 怎么会有这么大的性能差异

    根据http www open std org jtc1 sc22 wg21 docs papers 2007 n2271 html http www open std org jtc1 sc22 wg21 docs papers 2007
  • 如何估计 std::map 的内存使用情况?

    例如 我有一个已知 sizeof A 和 sizeof B 的 std map 而 map 内部有 N 个条目 您如何估计其内存使用情况 我想说这就像 sizeof A sizeof B N factor 但到底是什么因素呢 也许不同的公式
  • 从 STL 列表中删除项目

    我想创建一个函数 如果符合特定条件 则将项目从一个 STL 列表移动到另一个列表 这段代码不是这样做的方法 迭代器很可能会被擦除 函数失效并导致问题 for std list
  • std::copy/memcpy/memmove 优化

    我查看了 GCC STL 4 6 1 并看到std copy 使用优化版本以防内置 is trivial 评估为true 自从std copy and std reverse copy 模板对于复制数组中的元素非常有用 我想使用它们 但是
  • std::vector 与 std::stack

    有什么区别std vector and std stack 显然 向量可以删除集合中的项目 尽管比列表慢得多 而堆栈被构建为仅后进先出的集合 然而 堆栈对于最终物品操作是否更快 它是链表还是动态重新分配的数组 我找不到关于堆栈的太多信息 但
  • C++ STL 映射,std::pair 作为键

    这就是我通过地图定义的方式 std map

随机推荐