我的方法简单,在合理的时间内运行。问题是-它产生的OBJ文件在大小上是可笑的。我试图用SSD在一台非常受人尊敬的机器上将一个1GB文件加载到三维查看器中,但在某些情况下,当试图移动相机时延迟数秒钟,而在另一些情况下,它完全拒绝执行任何操作,而且实际上是软锁定的。
到目前为止我所做的:
显而易见的节省空间我不知道该怎么做:
更难,但可能有用的大空间节省:
因为这是必需的,这里有一些代码粗略地显示了正在发生的事情。这不是实际使用的代码。我不能发布这一点,而且它依赖于许多用户定义的类型,并且有大量的代码来处理边缘情况或额外的功能,所以不管怎么说,这里的设置都会非常混乱和冗长。
对这个问题来说唯一重要的是我的方法--逐体素,写出所有的8个顶点,然后写出6个边中任何一个不是相邻的主动体素。您只需相信我,它可以工作,虽然它确实产生大文件。
我的问题是,我可以用什么方法或方法来进一步缩小规模。例如,我怎么能不写出任何重复的顶点呢?
Assumptions:
Point只是一个大小为3的数组,带有像.x()这样的getterVector3D是std::vector的3D包装器,带有.at(x,y,z)方法writeObj之前就知道了。如果一个体素在任何位置都是活动的,那么提取是可能的和快速的。//Left, right, bottom, top, front, rear
static const std::vector<std::vector<uint8_t>> quads = {
{3, 0, 4, 7}, {1, 2, 6, 5}, {3, 2, 1, 0},
{4, 5, 6, 7}, {0, 1, 5, 4}, {2, 3, 7, 6}};
void writeOBJ(
std::string folder,
const std::string& filename,
const Vector3D<Voxel>& voxels,
const Point<unsigned> gridDim,
const Point<unsigned>& voxelCenterMinpoint,
const float voxelWidth)
{
unsigned numTris = 0;
std::ofstream filestream;
std::string filepath;
std::string extension;
ulong numVerticesWritten = 0;
// Make sure the folder ends with a '/'
if (folder.back() != '/')
{
folder.append("/");
}
filepath = folder + filename + ".obj";
filestream.open(filepath, std::ios::out);
// Remove the voxelization file if it already exists
std::remove(filepath.c_str());
Point<unsigned> voxelPos;
for (voxelPos[0] = 0; voxelPos[0] < gridDim.x(); voxelPos[0]++)
{
for (voxelPos[1] = 0; voxelPos[1] < gridDim.y(); voxelPos[1]++)
{
for (voxelPos[2] = 0; voxelPos[2] < gridDim.z(); voxelPos[2]++)
{
if (voxels.at(voxelPos))
{
writeVoxelToOBJ(
filestream, voxels, voxelPos, voxelCenterMinpoint, voxelWidth,
numVerticesWritten);
}
}
}
}
filestream.close();
}
void writeVoxelToOBJ(
std::ofstream& filestream,
const Vector3D<Voxel>& voxels,
const Point<unsigned>& voxelPos,
const Point<unsigned>& voxelCenterMinpoint,
const float voxelWidth,
ulong& numVerticesWritten)
{
std::vector<bool> neighborDrawable(6);
std::vector<Vecutils::Point<float>> corners(8);
unsigned numNeighborsDrawable = 0;
// Determine which neighbors are active and what the 8 corners of the
// voxel are
writeVoxelAux(
voxelPos, voxelCenterMinpoint, voxelWidth, neighborDrawable,
numNeighborsDrawable, corners);
// Normally, if all neighbors are active, there is no reason to write out this
// voxel. (All its faces are internal) If inverted, the opposite is true.
if (numNeighborsDrawable == 6)
{
return;
}
// Write out the vertices
for (const Vecutils::Point<float>& corner : corners)
{
std::string x = std::to_string(corner.x());
std::string y = std::to_string(corner.y());
std::string z = std::to_string(corner.z());
// Strip trailing zeros, they serve no prupose and bloat filesize
x.erase(x.find_last_not_of('0') + 1, std::string::npos);
y.erase(y.find_last_not_of('0') + 1, std::string::npos);
z.erase(z.find_last_not_of('0') + 1, std::string::npos);
filestream << "v " << x << " " << y << " " << z << "\n";
}
numVerticesWritten += 8;
// The 6 sides of the voxel
for (uint8_t i = 0; i < 6; i++)
{
// We only write them out if the neighbor in that direction
// is inactive
if (!neighborDrawable[i])
{
// The indices of the quad making up this face
const std::vector<uint8_t>& quad0 = quads[i];
ulong q0p0 = numVerticesWritten - 8 + quad0[0] + 1;
ulong q0p1 = numVerticesWritten - 8 + quad0[1] + 1;
ulong q0p2 = numVerticesWritten - 8 + quad0[2] + 1;
ulong q0p3 = numVerticesWritten - 8 + quad0[3] + 1;
// Wavefront object files are 1-indexed with regards to vertices
filestream << "f " << std::to_string(q0p0) << " "
<< std::to_string(q0p1) << " " << std::to_string(q0p2)
<< " " << std::to_string(q0p3) << "\n";
}
}
}
void writeVoxelAux(
const Point<unsigned>& voxelPos,
const Point<unsigned>& voxelCenterMinpoint,
const float voxelWidth,
std::vector<bool>& neighborsDrawable,
unsigned& numNeighborsDrawable,
std::vector<Point<float>>& corners)
{
// Which of the 6 immediate neighbors of the voxel are active?
for (ulong i = 0; i < 6; i++)
{
neighborsDrawable[i] = isNeighborDrawable(voxelPos.cast<int>() + off[i]);
numNeighborsDrawable += neighborsDrawable[i];
}
// Coordinates of the center of the voxel
Vecutils::Point<float> center =
voxelCenterMinpoint + (voxelPos.cast<float>() * voxelWidth);
// From this center, we can get the 8 corners of the triangle
for (ushort i = 0; i < 8; i++)
{
corners[i] = center + (crnoff[i] * (voxelWidth / 2));
}
}增编:
虽然我最终还是做了类似@τ建议的事情,但有一个关键的区别--比较操作符。
对于由3个浮点数表示的点,<和==是不够的。即使在这两种情况下都使用公差,它也不能一致地工作,并且在我的调试和发布模式之间也有差异。
我有一个新的方法,我会张贴在这里,当我可以,尽管它不是100%万无一失。
发布于 2020-03-26 11:06:13
如果您定义了这样的自定义比较器:
struct PointCompare
{
bool operator() (const Point<float>& lhs, const Point<float>& rhs) const
{
if (lhs.x() < rhs.x()) // x position is most significant (arbitrary)
return true;
else if (lhs.x() == rhs.x()) {
if (lhs.y() < rhs.y())
return true;
else if (lhs.y() == lhs.y())
return lhs.z() < rhs.z();
}
}
};然后,您可以在向量中从点到其索引绘制一张地图,每当在脸上使用顶点时,检查它是否已经存在:
std::vector<Point> vertices;
std::map<Point, unsigned, PointCompare> indices;
unsigned getVertexIndex(Point<float>& p) {
auto it = indices.find(p);
if (it != indices.end()) // known vertex
return it->second;
else { // new vertex, store in list
unsigned pos = vertices.size();
vertices.push_back(p);
indices[p] = pos;
return pos;
}
}使用此方法计算所有faces,然后将vertices写入文件,然后编写faces。
优化地组合体素面确实比这要复杂一些,但是如果您想要尝试,看看这个。
或者,如果您只处理几个网格,那么您可能希望为自己省去优化代码的麻烦,并使用免费的MeshLab,它可以删除重复的顶点,合并面并导出到各种(更有效)格式,只需点击几下。
顺便说一句:只有当体素非常稀疏时,才能有效地将体素存储在列表中;在大多数情况下,使用bool[][][]将更有效,并真正简化了算法(例如,查找邻居)。
发布于 2020-03-26 11:08:51
显而易见的节省空间我不知道该怎么做:
这是我过去为OpenGL缓冲区准备网格(在加载的几何图形之外)时所做的事情。为此,我打算删除顶点中的重复项,因为索引缓冲区已经计划好了。
这就是我所做的:在一个std::set中插入所有顶点,这将消除重复。
为了降低内存消耗,我使用了一个索引类型的std::set (例如,size_t或unsigned)和一个自定义谓词,该谓词对索引坐标进行比较。
自定义减谓词:
// functor for less predicate comparing indexed values
template <typename VALUE, typename INDEX>
struct LessValueT {
VALUE *values;
LessValueT(std::vector<VALUE> &values): values(values.data()) { }
bool operator()(INDEX i1, INDEX i2) const { return values[i1] < values[i2]; }
};和带有此谓词的std::set:
// an index table (sorting indices by indexed values)
template <typename VALUE, typename INDEX>
using LookUpTableT = std::set<INDEX, LessValueT<VALUE, INDEX>>;使用上述坐标(或法线),这些坐标(或法线)存储如下
template <typename VALUE>
struct Vec3T { VALUE x, y, z; };因此,有必要使较少的操作符超载,这是我以最幼稚的方式对此示例所做的:
template <typename VALUE>
bool operator<(const Vec3T<VALUE> &vec1, const Vec3T<VALUE> &vec2)
{
return vec1.x < vec2.x ? true : vec1.x > vec2.x ? false
: vec1.y < vec2.y ? true : vec1.y > vec2.y ? false
: vec1.z < vec2.z;
}因此,没有必要考虑这个谓词所产生的顺序的意义或非意义。它必须符合std::set的要求,以区分和排序具有不同组件的向量值。
为了演示这一点,我使用了一个tetrix海绵

使用不同数量的三角形(取决于细分级别)构建起来很容易,并且非常类似于IMHO --我对OPs数据所做的假设:
完整的示例代码testCollectVtcs.cc
#include <cassert>
#include <cmath>
#include <chrono>
#include <fstream>
#include <functional>
#include <iostream>
#include <numeric>
#include <set>
#include <string>
#include <vector>
namespace Compress {
// functor for less predicate comparing indexed values
template <typename VALUE, typename INDEX>
struct LessValueT {
VALUE *values;
LessValueT(std::vector<VALUE> &values): values(values.data()) { }
bool operator()(INDEX i1, INDEX i2) const { return values[i1] < values[i2]; }
};
// an index table (sorting indices by indexed values)
template <typename VALUE, typename INDEX>
using LookUpTableT = std::set<INDEX, LessValueT<VALUE, INDEX>>;
} // namespace Compress
// the compress function - modifies the values vector
template <typename VALUE, typename INDEX = size_t>
std::vector<INDEX> compress(std::vector<VALUE> &values)
{
typedef Compress::LessValueT<VALUE, INDEX> LessValue;
typedef Compress::LookUpTableT<VALUE, INDEX> LookUpTable;
// collect indices and remove duplicate values
std::vector<INDEX> idcs; idcs.reserve(values.size());
LookUpTable lookUp((LessValue(values)));
INDEX iIn = 0, nOut = 0;
for (const INDEX n = values.size(); iIn < n; ++iIn) {
values[nOut] = values[iIn];
std::pair<LookUpTable::iterator, bool> ret = lookUp.insert(nOut);
if (ret.second) { // new index added?
++nOut; // remark value as stored
}
idcs.push_back(*ret.first); // store index
}
// discard all obsolete values
values.resize(nOut);
// done
return idcs;
}
// instrumentation to take times
typedef std::chrono::high_resolution_clock Clock;
typedef std::chrono::microseconds USecs;
typedef decltype(std::chrono::duration_cast<USecs>(Clock::now() - Clock::now())) Time;
Time duration(const Clock::time_point &t0)
{
return std::chrono::duration_cast<USecs>(Clock::now() - t0);
}
Time stopWatch(std::function<void()> func)
{
const Clock::time_point t0 = Clock::now();
func();
return duration(t0);
}
// a minimal linear algebra tool set
template <typename VALUE>
struct Vec3T { VALUE x, y, z; };
template <typename VALUE>
Vec3T<VALUE> operator*(const Vec3T<VALUE> &vec, VALUE s) { return { vec.x * s, vec.y * s, vec.z * s }; }
template <typename VALUE>
Vec3T<VALUE> operator*(VALUE s, const Vec3T<VALUE> &vec) { return { s * vec.x, s * vec.y, s * vec.z }; }
template <typename VALUE>
Vec3T<VALUE> operator+(const Vec3T<VALUE> &vec1, const Vec3T<VALUE> &vec2)
{
return { vec1.x + vec2.x, vec1.y + vec2.y, vec1.z + vec2.z };
}
template <typename VALUE>
Vec3T<VALUE> operator-(const Vec3T<VALUE> &vec1, const Vec3T<VALUE> &vec2)
{
return { vec1.x - vec2.x, vec1.y - vec2.y, vec1.z - vec2.z };
}
template <typename VALUE>
VALUE length(const Vec3T<VALUE> &vec)
{
return std::sqrt(vec.x * vec.x + vec.y * vec.y + vec.z * vec.z);
}
template <typename VALUE>
VALUE dot(const Vec3T<VALUE> &vec1, const Vec3T<VALUE> &vec2)
{
return vec1.x * vec2.x + vec1.y * vec2.y + vec1.z * vec2.z;
}
template <typename VALUE>
Vec3T<VALUE> cross(const Vec3T<VALUE> &vec1, const Vec3T<VALUE> &vec2)
{
return {
vec1.y * vec2.z - vec1.z * vec2.y,
vec1.z * vec2.x - vec1.x * vec2.z,
vec1.x * vec2.y - vec1.y * vec2.x
};
}
template <typename VALUE>
Vec3T<VALUE> normalize(const Vec3T<VALUE> &vec) { return (VALUE)1 / length(vec) * vec; }
// build sample - a tetraeder sponge
template <typename VALUE>
using StoreTriFuncT = std::function<void(const Vec3T<VALUE>&, const Vec3T<VALUE>&, const Vec3T<VALUE>&)>;
namespace TetraSponge {
template <typename VALUE>
void makeTetrix(
const Vec3T<VALUE> &p0, const Vec3T<VALUE> &p1,
const Vec3T<VALUE> &p2, const Vec3T<VALUE> &p3,
StoreTriFuncT<VALUE> &storeTri)
{
storeTri(p0, p1, p2);
storeTri(p0, p2, p3);
storeTri(p0, p3, p1);
storeTri(p1, p3, p2);
}
template <typename VALUE>
void subDivide(
unsigned depth,
const Vec3T<VALUE> &p0, const Vec3T<VALUE> &p1,
const Vec3T<VALUE> &p2, const Vec3T<VALUE> &p3,
StoreTriFuncT<VALUE> &storeTri)
{
if (!depth) { // build the 4 triangles
makeTetrix(p0, p1, p2, p3, storeTri);
} else {
--depth;
auto middle = [](const Vec3T<VALUE> &p0, const Vec3T<VALUE> &p1)
{
return 0.5f * p0 + 0.5f * p1;
};
const Vec3T<VALUE> p01 = middle(p0, p1);
const Vec3T<VALUE> p02 = middle(p0, p2);
const Vec3T<VALUE> p03 = middle(p0, p3);
const Vec3T<VALUE> p12 = middle(p1, p2);
const Vec3T<VALUE> p13 = middle(p1, p3);
const Vec3T<VALUE> p23 = middle(p2, p3);
subDivide(depth, p0, p01, p02, p03, storeTri);
subDivide(depth, p01, p1, p12, p13, storeTri);
subDivide(depth, p02, p12, p2, p23, storeTri);
subDivide(depth, p03, p13, p23, p3, storeTri);
}
}
} // namespace TetraSponge
template <typename VALUE>
void makeTetraSponge(
unsigned depth, // recursion depth (values 0 ... 9 recommended)
StoreTriFuncT<VALUE> &storeTri)
{
TetraSponge::subDivide(depth,
{ -1, -1, -1 },
{ +1, +1, -1 },
{ +1, -1, +1 },
{ -1, +1, +1 },
storeTri);
}
// minimal obj file writer
template <typename VALUE, typename INDEX>
void writeObjFile(
std::ostream &out,
const std::vector<Vec3T<VALUE>> &coords, const std::vector<INDEX> &idcsCoords,
const std::vector<Vec3T<VALUE>> &normals, const std::vector<INDEX> &idcsNormals)
{
assert(idcsCoords.size() == idcsNormals.size());
out
<< "# Wavefront OBJ file\n"
<< "\n"
<< "# " << coords.size() << " coordinates\n";
for (const Vec3 &coord : coords) {
out << "v " << coord.x << " " << coord.y << " " << coord.z << '\n';
}
out
<< "# " << normals.size() << " normals\n";
for (const Vec3 &normal : normals) {
out << "vn " << normal.x << " " << normal.y << " " << normal.z << '\n';
}
out
<< "\n"
<< "g faces\n"
<< "# " << idcsCoords.size() / 3 << " triangles\n";
for (size_t i = 0, n = idcsCoords.size(); i < n; i += 3) {
out << "f "
<< idcsCoords[i + 0] + 1 << "//" << idcsNormals[i + 0] + 1 << ' '
<< idcsCoords[i + 1] + 1 << "//" << idcsNormals[i + 1] + 1 << ' '
<< idcsCoords[i + 2] + 1 << "//" << idcsNormals[i + 2] + 1 << '\n';
}
}
template <typename VALUE, typename INDEX = size_t>
void writeObjFile(
std::ostream &out,
const std::vector<Vec3T<VALUE>> &coords, const std::vector<Vec3T<VALUE>> &normals)
{
assert(coords.size() == normals.size());
std::vector<INDEX> idcsCoords(coords.size());
std::iota(idcsCoords.begin(), idcsCoords.end(), 0);
std::vector<INDEX> idcsNormals(normals.size());
std::iota(idcsNormals.begin(), idcsNormals.end(), 0);
writeObjFile(out, coords, idcsCoords, normals, idcsNormals);
}
// main program (experiment)
template <typename VALUE>
bool operator<(const Vec3T<VALUE> &vec1, const Vec3T<VALUE> &vec2)
{
return vec1.x < vec2.x ? true : vec1.x > vec2.x ? false
: vec1.y < vec2.y ? true : vec1.y > vec2.y ? false
: vec1.z < vec2.z;
}
using Vec3 = Vec3T<float>;
using StoreTriFunc = StoreTriFuncT<float>;
int main(int argc, char **argv)
{
// read command line options
if (argc <= 2) {
std::cerr
<< "Usage:\n"
<< "> testCollectVtcs DEPTH FILE\n";
return 1;
}
const unsigned depth = std::stoi(argv[1]);
const std::string file = argv[2];
std::cout << "Build sample...\n";
std::vector<Vec3> coords, normals;
{ const Time t = stopWatch([&]() {
StoreTriFunc storeTri = [&](const Vec3 &p0, const Vec3 &p1, const Vec3 &p2) {
coords.push_back(p0); coords.push_back(p1); coords.push_back(p2);
const Vec3 n = normalize(cross(p0 - p2, p1 - p2));
normals.push_back(n); normals.push_back(n); normals.push_back(n);
};
makeTetraSponge(depth, storeTri);
});
std::cout << "Done after " << t.count() << " us.\n";
}
std::cout << "coords: " << coords.size() << ", normals: " << normals.size() << '\n';
const std::string fileUncompr = file + ".uncompressed.obj";
std::cout << "Write uncompressed OBJ file '" << fileUncompr << "'...\n";
{ const Time t = stopWatch([&]() {
std::ofstream fOut(fileUncompr.c_str(), std::ios::binary);
/* std::ios::binary -> force Unix line-endings on Windows
* to win some extra bytes
*/
writeObjFile(fOut, coords, normals);
fOut.close();
if (!fOut.good()) {
std::cerr << "Writing of '" << fileUncompr << "' failed!\n";
throw std::ios::failure("Failed to complete writing of file!");
}
});
std::cout << "Done after " << t.count() << " us.\n";
}
std::cout << "Compress coordinates and normals...\n";
std::vector<size_t> idcsCoords, idcsNormals;
{ const Time t = stopWatch([&]() {
idcsCoords = compress(coords);
idcsNormals = compress(normals);
});
std::cout << "Done after " << t.count() << " us.\n";
}
std::cout
<< "coords: " << coords.size() << ", normals: " << normals.size() << '\n'
<< "coord idcs: " << idcsCoords.size() << ", normals: " << normals.size() << '\n';
const std::string fileCompr = file + ".compressed.obj";
std::cout << "Write compressed OBJ file'" << fileCompr << "'...\n";
{ const Time t = stopWatch([&]() {
std::ofstream fOut(fileCompr.c_str(), std::ios::binary);
/* std::ios::binary -> force Unix line-endings on Windows
* to win some extra bytes
*/
writeObjFile(fOut, coords, idcsCoords, normals, idcsNormals);
fOut.close();
if (!fOut.good()) {
std::cerr << "Writing of '" << fileCompr << "' failed!\n";
throw std::ios::failure("Failed to complete writing of file!");
}
});
std::cout << "Done after " << t.count() << " us.\n";
}
std::cout << "Done.\n";
}第一次检查:
> testCollectVtcs
Usage:
> testCollectVtcs DEPTH FILE
> testCollectVtcs 1 test1
Build sample...
Done after 34 us.
coords: 48, normals: 48
Write uncompressed OBJ file 'test1.uncompressed.obj'...
Done after 1432 us.
Compress coordinates and normals...
Done after 12 us.
coords: 10, normals: 4
coord idcs: 48, normals: 4
Write compressed OBJ file'test1.compressed.obj'...
Done after 1033 us.
Done.这产生了两个文件:
$ ls test1.*.obj
-rw-r--r-- 1 Scheff 1049089 553 Mar 26 11:46 test1.compressed.obj
-rw-r--r-- 1 Scheff 1049089 2214 Mar 26 11:46 test1.uncompressed.obj
$$ cat test1.uncompressed.obj
# Wavefront OBJ file
# 48 coordinates
v -1 -1 -1
v 0 0 -1
v 0 -1 0
v -1 -1 -1
v 0 -1 0
v -1 0 0
v -1 -1 -1
v -1 0 0
v 0 0 -1
v 0 0 -1
v -1 0 0
v 0 -1 0
v 0 0 -1
v 1 1 -1
v 1 0 0
v 0 0 -1
v 1 0 0
v 0 1 0
v 0 0 -1
v 0 1 0
v 1 1 -1
v 1 1 -1
v 0 1 0
v 1 0 0
v 0 -1 0
v 1 0 0
v 1 -1 1
v 0 -1 0
v 1 -1 1
v 0 0 1
v 0 -1 0
v 0 0 1
v 1 0 0
v 1 0 0
v 0 0 1
v 1 -1 1
v -1 0 0
v 0 1 0
v 0 0 1
v -1 0 0
v 0 0 1
v -1 1 1
v -1 0 0
v -1 1 1
v 0 1 0
v 0 1 0
v -1 1 1
v 0 0 1
# 48 normals
vn 0.57735 -0.57735 -0.57735
vn 0.57735 -0.57735 -0.57735
vn 0.57735 -0.57735 -0.57735
vn -0.57735 -0.57735 0.57735
vn -0.57735 -0.57735 0.57735
vn -0.57735 -0.57735 0.57735
vn -0.57735 0.57735 -0.57735
vn -0.57735 0.57735 -0.57735
vn -0.57735 0.57735 -0.57735
vn 0.57735 0.57735 0.57735
vn 0.57735 0.57735 0.57735
vn 0.57735 0.57735 0.57735
vn 0.57735 -0.57735 -0.57735
vn 0.57735 -0.57735 -0.57735
vn 0.57735 -0.57735 -0.57735
vn -0.57735 -0.57735 0.57735
vn -0.57735 -0.57735 0.57735
vn -0.57735 -0.57735 0.57735
vn -0.57735 0.57735 -0.57735
vn -0.57735 0.57735 -0.57735
vn -0.57735 0.57735 -0.57735
vn 0.57735 0.57735 0.57735
vn 0.57735 0.57735 0.57735
vn 0.57735 0.57735 0.57735
vn 0.57735 -0.57735 -0.57735
vn 0.57735 -0.57735 -0.57735
vn 0.57735 -0.57735 -0.57735
vn -0.57735 -0.57735 0.57735
vn -0.57735 -0.57735 0.57735
vn -0.57735 -0.57735 0.57735
vn -0.57735 0.57735 -0.57735
vn -0.57735 0.57735 -0.57735
vn -0.57735 0.57735 -0.57735
vn 0.57735 0.57735 0.57735
vn 0.57735 0.57735 0.57735
vn 0.57735 0.57735 0.57735
vn 0.57735 -0.57735 -0.57735
vn 0.57735 -0.57735 -0.57735
vn 0.57735 -0.57735 -0.57735
vn -0.57735 -0.57735 0.57735
vn -0.57735 -0.57735 0.57735
vn -0.57735 -0.57735 0.57735
vn -0.57735 0.57735 -0.57735
vn -0.57735 0.57735 -0.57735
vn -0.57735 0.57735 -0.57735
vn 0.57735 0.57735 0.57735
vn 0.57735 0.57735 0.57735
vn 0.57735 0.57735 0.57735
g faces
# 16 triangles
f 1//1 2//2 3//3
f 4//4 5//5 6//6
f 7//7 8//8 9//9
f 10//10 11//11 12//12
f 13//13 14//14 15//15
f 16//16 17//17 18//18
f 19//19 20//20 21//21
f 22//22 23//23 24//24
f 25//25 26//26 27//27
f 28//28 29//29 30//30
f 31//31 32//32 33//33
f 34//34 35//35 36//36
f 37//37 38//38 39//39
f 40//40 41//41 42//42
f 43//43 44//44 45//45
f 46//46 47//47 48//48
$$ cat test1.compressed.obj
# Wavefront OBJ file
# 10 coordinates
v -1 -1 -1
v 0 0 -1
v 0 -1 0
v -1 0 0
v 1 1 -1
v 1 0 0
v 0 1 0
v 1 -1 1
v 0 0 1
v -1 1 1
# 4 normals
vn 0.57735 -0.57735 -0.57735
vn -0.57735 -0.57735 0.57735
vn -0.57735 0.57735 -0.57735
vn 0.57735 0.57735 0.57735
g faces
# 16 triangles
f 1//1 2//1 3//1
f 1//2 3//2 4//2
f 1//3 4//3 2//3
f 2//4 4//4 3//4
f 2//1 5//1 6//1
f 2//2 6//2 7//2
f 2//3 7//3 5//3
f 5//4 7//4 6//4
f 3//1 6//1 8//1
f 3//2 8//2 9//2
f 3//3 9//3 6//3
f 6//4 9//4 8//4
f 4//1 7//1 9//1
f 4//2 9//2 10//2
f 4//3 10//3 7//3
f 7//4 10//4 9//4
$所以,这就是我说出来的
这是,这个样子:

(我看不出test1.compressed.obj有什么视觉差异。)
关于停止观察的时间,我不会太相信他们。因此,样本太小了。
因此,另一个具有更多几何学(更多)的测试:
> testCollectVtcs 8 test8
Build sample...
Done after 40298 us.
coords: 786432, normals: 786432
Write uncompressed OBJ file 'test8.uncompressed.obj'...
Done after 6200571 us.
Compress coordinates and normals...
Done after 115817 us.
coords: 131074, normals: 4
coord idcs: 786432, normals: 4
Write compressed OBJ file'test8.compressed.obj'...
Done after 1513216 us.
Done.
>这两个档案:
$ ls -l test8.*.obj
-rw-r--r-- 1 ds32737 1049089 11540967 Mar 26 11:56 test8.compressed.obj
-rw-r--r-- 1 ds32737 1049089 57424470 Mar 26 11:56 test8.uncompressed.obj
$概括地说:

https://stackoverflow.com/questions/60857532
复制相似问题