今天写了一个三维网格保存ply格式的小工具函数,因为ply类型是顶点按索引排列的方式,不同于STL类型的按面片索引的方式。因此,准备借助于C++中的Map类型,来进行顶点的去重复和排序。因为对Map结构的不了解,竟然花了一天的时间去实现,中间走了不少歪路,也借鉴了不少其他的方法,最终实现了自己想要的结果。纸上得来终觉浅,绝知此事要躬行,很有必要记录一下,以此为戒。
首先定义STL类型的读取结构:ReadSTL.h
#pragma once
#include<d3d9.h>
#include<windows.h>
#include<vector>
using namespace std;
//-----------------------------顶点结构体----------------------------------
struct VertexT //顶点结构
{
VertexT() {}
VertexT(float x, float y, float z)
{
_x = x; _y = y; _z = z;
}
float _x, _y, _z;
static const DWORD FVF;
};
class Point3f
{
public:
Point3f() :x(0), y(0), z(0)
{
};
Point3f(float _x, float _y, float _z) :x(_x), y(_y), z(_z){};
int SetParam(float _x, float _y, float _z)
{
x = _x;
y = _y;
z = _z;
return 0;
};
inline VertexT IVertex()
{
return VertexT(x, y, z);
}
private:
float x, y, z;
};
class ReadSTLFile
{
public:
ReadSTLFile();
~ReadSTLFile();
bool ReadFile(const char *cfilename);
int NumTri();
vector<Point3f>& PointList();
vector<Point3f>& PointNormList();
private:
vector<Point3f> pointList;
vector<Point3f>pointNormList;
unsigned int unTriangles;
bool ReadASCII(const char *cfilename);
bool ReadBinary(const char *cfilename);
char* memwriter;
int cpyint(const char*& p);
float cpyfloat(const char*& p);
};
然后是STL类型数据的实现:ReadSTL.cpp
#include "ReadSTL.h"
#include <vector>
#include <fstream>
#include <iostream>
#include <string>
#include <sstream>
ReadSTLFile::ReadSTLFile()
{
}
ReadSTLFile::~ReadSTLFile()
{
}
bool ReadSTLFile::ReadFile(const char *cfilename)
{
FILE * pFile;
long lSize;
char* buffer;
size_t result;
/* 若要一个byte不漏地读入整个文件,只能采用二进制方式打开 */
fopen_s(&pFile, cfilename, "rb");
if (pFile == NULL)
{
fputs("File error", stderr);
exit(1);
}
/* 获取文件大小 */
fseek(pFile, 0, SEEK_END);
lSize = ftell(pFile);
rewind(pFile);
/* 分配内存存储整个文件 */
buffer = (char*)malloc(sizeof(char)*lSize);
if (buffer == NULL)
{
fputs("Memory error", stderr);
exit(2);
}
/* 将文件拷贝到buffer中 */
result = fread(buffer, 1, lSize, pFile);
if (result != lSize)
{
fputs("Reading error", stderr);
exit(3);
}
/* 结束演示,关闭文件并释放内存 */
fclose(pFile);
ios::sync_with_stdio(false);
if (buffer[79] != '\0')//判断格式
{
ReadASCII(buffer);
}
else
{
ReadBinary(buffer);
}
ios::sync_with_stdio(true);
free(buffer);
return true;
}
bool ReadSTLFile::ReadASCII(const char *buffer)
{
unTriangles = 0;
float x, y, z;
int i;
string name, useless;
stringstream ss(buffer);
ss >> name >> name >> name >> name;
ss.get();
do {
ss >> useless;
if (useless != "facet")
break;
ss >> useless >> x >> y >> z;
pointNormList.push_back(Point3f(x, y, z));
getline(ss, useless);
getline(ss, useless);
for (i = 0; i < 3; i++)
{
ss >> useless >> x >> y >> z;
pointList.push_back(Point3f(x, y, z));
}
unTriangles++;
getline(ss, useless);
getline(ss, useless);
getline(ss, useless);
} while (1);
return true;
}
bool ReadSTLFile::ReadBinary(const char *buffer)
{
const char* p = buffer;
char name[80];
int i, j;
memcpy(name, p, 80);
p += 80;
unTriangles = cpyint(p);
for (i = 0; i < unTriangles; i++)
{
// p += 12;//跳过头部法向量
pointNormList.push_back(Point3f(cpyfloat(p), cpyfloat(p), cpyfloat(p)));
for (j = 0; j < 3; j++)//读取三顶点
pointList.push_back(Point3f(cpyfloat(p), cpyfloat(p), cpyfloat(p)));
p += 2;//跳过尾部标志
}
return true;
}
int ReadSTLFile::NumTri()
{
return unTriangles;
}
vector<Point3f>& ReadSTLFile::PointList()
{
return pointList;
}
vector<Point3f>& ReadSTLFile::PointNormList()
{
return pointNormList;
}
int ReadSTLFile::cpyint(const char*& p)
{
int cpy;
memwriter = (char*)&cpy;
memcpy(memwriter, p, 4);
p += 4;
return cpy;
}
float ReadSTLFile::cpyfloat(const char*& p)
{
float cpy;
memwriter = (char*)&cpy;
memcpy(memwriter, p, 4);
p += 4;
return cpy;
}
最后是实现:
#include <iostream>
#include <fstream>
#include <vector>
#include <time.h>
#include <windows.h>
#include <stdio.h>
#include <vector>
#include<map>
#include <string>
#include"ReadSTL.h"
//自定义比较排序函数
class StlCmpVec
{
public:
bool operator()(const LVertex3f& _v0, const LVertex3f& _v1) const
{
if (_v0.x == _v1.x)
{
if (_v0.y == _v1.y)
{
if (_v0.z == _v1.z)
{
return (_v0.z < _v1.z);
}
else
{
return (_v0.z < _v1.z);
}
}
else return (_v0.y < _v1.y);
}
else return (_v0.x < _v1.x);
}
};
void saveBinaryPLY(char* oupathply, float* pts, float* nor, const int face_num)
{
//Create Vertex Index
int verts_num = 0;
std::vector<LVertex3f> outpts_(face_num * 3);
std::vector<LVertexIndex> facetIndex(face_num);
std::vector<LTexture2f> outtex_(face_num * 3);
//StlCmpVec comp(0.000001f);
std::map<LVertex3f, int, StlCmpVec> VertexIndexMap;
std::map<LVertex3f, int, StlCmpVec> ::iterator VertexIndexItr;
LTriangle trianglevertex;
LTexture triangletexture;
LVertexIndex triangleVertexIndex;
for (int i = 0; i < face_num; i++)
{
trianglevertex.vertex[0].x = pts[i * 9];
trianglevertex.vertex[0].y = pts[i * 9 + 1];
trianglevertex.vertex[0].z = pts[i * 9 + 2];
trianglevertex.vertex[1].x = pts[i * 9 + 3];
trianglevertex.vertex[1].y = pts[i * 9 + 4];
trianglevertex.vertex[1].z = pts[i * 9 + 5];
trianglevertex.vertex[2].x = pts[i * 9 + 6];
trianglevertex.vertex[2].y = pts[i * 9 + 7];
trianglevertex.vertex[2].z = pts[i * 9 + 8];
for (int j = 0; j < 3; j++)
{
VertexIndexItr = VertexIndexMap.find(trianglevertex.vertex[j]);
if (VertexIndexItr == VertexIndexMap.end())
{
outpts_[verts_num] = trianglevertex.vertex[j];//vertex
outtex_[verts_num] = triangletexture.texture[j];
triangleVertexIndex.index[j] = verts_num;
VertexIndexMap[trianglevertex.vertex[j]] = verts_num;
verts_num++;
}
else
triangleVertexIndex.index[j] = VertexIndexItr->second;
}
facetIndex[i] = triangleVertexIndex;//index
}
outpts_.resize(verts_num);
cout << "Ascii : verts_num = " << verts_num << endl;
cout << "Ascii : face_num = " << face_num << endl;
cout << "Ascii : VertexIndexMap size " << VertexIndexMap.size() << endl;
//write binary ply
FILE* fpply;
fopen_s(&fpply, oupathply, "wb");
fprintf_s(fpply, "%s\n", "ply");
fprintf_s(fpply, "%s %s\n", "format binary_little_endian ", "1.0");
fprintf_s(fpply, "%s\n", "comment Export generated by Fussen");
fprintf_s(fpply, "%s\n", "comment TextureFile texture.jpg");
fprintf_s(fpply, "%s %d\n", "element vertex", verts_num);
fprintf_s(fpply, "%s\n", "property float x");
fprintf_s(fpply, "%s\n", "property float y");
fprintf_s(fpply, "%s\n", "property float z");
fprintf_s(fpply, "%s %d\n", "element face", face_num);
fprintf_s(fpply, "%s\n", "property list uchar int vertex_indices");
fprintf_s(fpply, "%s\n", "end_header");
for (int i = 0; i < verts_num; ++i)
{
float temp[3];
temp[0] = outpts_[i].x;
temp[1] = outpts_[i].y;
temp[2] = outpts_[i].z;
fwrite((char const*)temp, sizeof(float), 3, fpply);
}
unsigned char value = (unsigned char)3;//ply format demand value must unsigned char and const char*
for (int i = 0; i < face_num; ++i)
{
fwrite(reinterpret_cast<const char*> (&value), sizeof(unsigned char), 1, fpply);
int temp[3];
temp[0] = facetIndex[i].index[0];
temp[1] = facetIndex[i].index[1];
temp[2] = facetIndex[i].index[2];
fwrite(reinterpret_cast<const char*> (temp), sizeof(int), 3, fpply);
}
fclose(fpply);
}
int main()
{
cout << "Test Start..." << endl;
//Test texture
char *readpath = "../Test.stl";
int vertpts_size = 0;
float *vertpts = new float[MAXTRIANGLENUM * 9]();//vertpts_size 是顶点个数;vertpts_的排列方式是每个面片三个顶点的xyz坐标依次排列
float *vertnor = new float[MAXTRIANGLENUM * 3]();
unsigned char *vertcolor = new unsigned char[MAXTRIANGLENUM * 3]();
ReadAsciiSTL(readpath, vertpts, vertnor, vertcolor, vertpts_size);
cout << "Read Date end" << endl;
char* oupathply = "Test.ply";
int face_num = vertpts_size / 9;
saveBinaryPLY(oupathply, vertpts, vertnor, face_num);
delete[]vertpts;
delete[]vertnor;
delete[]vertcolor;
cout << "Test Texture End." << endl;
getchar();
return 0;
}
在自定义比较函数StlCmpVec时,指定 StlCmpVec, Map建立时传入的是一个StlCmpVec类名,而不是一个函数或 functor 对象。这点要注意,然后里面的重载运算符(),只是用来做比较,返回值要符合Map的定义规则,不能直接返回true或者false。
另外借鉴了这篇文章:元素为自定义复合结构时 map,set 如何处理重复 key 及排序?