X-Git-Url: https://git.sur5r.net/?p=fstl;a=blobdiff_plain;f=src%2Floader.cpp;h=93f5525320ce1f7ddc29dd9493b0d3b8a35852ca;hp=0d6b165ad696d436324a2961d71d6788c6c67c51;hb=refs%2Fheads%2Fupstream;hpb=967d178c4343c689c728571bb10d5c94ab8d9d13 diff --git a/src/loader.cpp b/src/loader.cpp index 0d6b165..38bd2dd 100644 --- a/src/loader.cpp +++ b/src/loader.cpp @@ -1,7 +1,10 @@ +#include + #include "loader.h" +#include "vertex.h" -Loader::Loader(QObject* parent, const QString& filename) - : QThread(parent), filename(filename) +Loader::Loader(QObject* parent, const QString& filename, bool is_reload) + : QThread(parent), filename(filename), is_reload(is_reload) { // Nothing to do here } @@ -11,51 +14,137 @@ void Loader::run() Mesh* mesh = load_stl(); if (mesh) { - emit got_mesh(mesh); - emit loaded_file(filename); + if (mesh->empty()) + { + emit error_empty_mesh(); + delete mesh; + } + else + { + emit got_mesh(mesh, is_reload); + emit loaded_file(filename); + } } } - //////////////////////////////////////////////////////////////////////////////// -struct Vec3 +void parallel_sort(Vertex* begin, Vertex* end, int threads) +{ + if (threads < 2 || end - begin < 2) + { + std::sort(begin, end); + } + else + { + const auto mid = begin + (end - begin) / 2; + if (threads == 2) + { + auto future = std::async(parallel_sort, begin, mid, threads / 2); + std::sort(mid, end); + future.wait(); + } + else + { + auto a = std::async(std::launch::async, parallel_sort, begin, mid, threads / 2); + auto b = std::async(std::launch::async, parallel_sort, mid, end, threads / 2); + a.wait(); + b.wait(); + } + std::inplace_merge(begin, mid, end); + } +} + +Mesh* mesh_from_verts(uint32_t tri_count, QVector& verts) { - GLfloat x, y, z; - bool operator!=(const Vec3& rhs) const + // Save indicies as the second element in the array + // (so that we can reconstruct triangle order after sorting) + for (size_t i=0; i < tri_count*3; ++i) { - return x != rhs.x || y != rhs.y || z != rhs.z; + verts[i].i = i; } - bool operator<(const Vec3& rhs) const + + // Check how many threads the hardware can safely support. This may return + // 0 if the property can't be read so we shoud check for that too. + auto threads = std::thread::hardware_concurrency(); + if (threads == 0) { - if (x != rhs.x) return x < rhs.x; - else if (y != rhs.y) return y < rhs.y; - else if (z != rhs.z) return z < rhs.z; - else return false; + threads = 8; } -}; -typedef std::pair Vec3i; + // Sort the set of vertices (to deduplicate) + parallel_sort(verts.begin(), verts.end(), threads); + + // This vector will store triangles as sets of 3 indices + std::vector indices(tri_count*3); + + // Go through the sorted vertex list, deduplicating and creating + // an indexed geometry representation for the triangles. + // Unique vertices are moved so that they occupy the first vertex_count + // positions in the verts array. + size_t vertex_count = 0; + for (auto v : verts) + { + if (!vertex_count || v != verts[vertex_count-1]) + { + verts[vertex_count++] = v; + } + indices[v.i] = vertex_count - 1; + } + verts.resize(vertex_count); + + std::vector flat_verts; + flat_verts.reserve(vertex_count*3); + for (auto v : verts) + { + flat_verts.push_back(v.x); + flat_verts.push_back(v.y); + flat_verts.push_back(v.z); + } + + return new Mesh(std::move(flat_verts), std::move(indices)); +} //////////////////////////////////////////////////////////////////////////////// Mesh* Loader::load_stl() { QFile file(filename); - file.open(QIODevice::ReadOnly); - if (file.read(5) == "solid") + if (!file.open(QIODevice::ReadOnly)) { - emit error_ascii_stl(); + emit error_missing_file(); return NULL; } - // Skip the rest of the header material - file.read(75); + // First, try to read the stl as an ASCII file + if (file.read(5) == "solid") + { + file.readLine(); // skip solid name + const auto line = file.readLine().trimmed(); + if (line.startsWith("facet") || + line.startsWith("endsolid")) + { + file.seek(0); + return read_stl_ascii(file); + } + // Otherwise, this STL is a binary stl but contains 'solid' as + // the first five characters. This is a bad life choice, but + // we can gracefully handle it by falling through to the binary + // STL reader below. + } + + file.seek(0); + return read_stl_binary(file); +} + +Mesh* Loader::read_stl_binary(QFile& file) +{ QDataStream data(&file); data.setByteOrder(QDataStream::LittleEndian); data.setFloatingPointPrecision(QDataStream::SinglePrecision); // Load the triangle count from the .stl file + file.seek(80); uint32_t tri_count; data >> tri_count; @@ -67,63 +156,81 @@ Mesh* Loader::load_stl() } // Extract vertices into an array of xyz, unsigned pairs - QVector verts(tri_count*3); + QVector verts(tri_count*3); // Dummy array, because readRawData is faster than skipRawData - char buffer[sizeof(float)*3]; + std::unique_ptr buffer(new uint8_t[tri_count * 50]); + data.readRawData((char*)buffer.get(), tri_count * 50); // Store vertices in the array, processing one triangle at a time. + auto b = buffer.get() + 3 * sizeof(float); for (auto v=verts.begin(); v != verts.end(); v += 3) { - // Skip face's normal vector - data.readRawData(buffer, 3*sizeof(float)); - // Load vertex data from .stl file into vertices - data >> v[0].first.x >> v[0].first.y >> v[0].first.z; - data >> v[1].first.x >> v[1].first.y >> v[1].first.z; - data >> v[2].first.x >> v[2].first.y >> v[2].first.z; - - // Skip face attribute - data.readRawData(buffer, sizeof(uint16_t)); - } + for (unsigned i=0; i < 3; ++i) + { + qFromLittleEndian(b, 3, &v[i]); + b += 3 * sizeof(float); + } - // Save indicies as the second element in the array - // (so that we can reconstruct triangle order after sorting) - for (size_t i=0; i < tri_count*3; ++i) - { - verts[i].second = i; + // Skip face attribute and next face's normal vector + b += 3 * sizeof(float) + sizeof(uint16_t); } - // Sort the set of vertices (to deduplicate) - std::sort(verts.begin(), verts.end()); + return mesh_from_verts(tri_count, verts); +} - // This vector will store triangles as sets of 3 indices - std::vector indices(tri_count*3); +Mesh* Loader::read_stl_ascii(QFile& file) +{ + file.readLine(); + uint32_t tri_count = 0; + QVector verts(tri_count*3); - // Go through the sorted vertex list, deduplicating and creating - // an indexed geometry representation for the triangles. - // Unique vertices are moved so that they occupy the first vertex_count - // positions in the verts array. - size_t vertex_count = 0; - for (auto v : verts) + bool okay = true; + while (!file.atEnd() && okay) { - if (!vertex_count || v.first != verts[vertex_count-1].first) + const auto line = file.readLine().simplified(); + if (line.startsWith("endsolid")) { - verts[vertex_count++] = v; + break; + } + else if (!line.startsWith("facet normal") || + !file.readLine().simplified().startsWith("outer loop")) + { + okay = false; + break; + } + + for (int i=0; i < 3; ++i) + { + auto line = file.readLine().simplified().split(' '); + if (line[0] != "vertex") + { + okay = false; + break; + } + const float x = line[1].toFloat(&okay); + const float y = line[2].toFloat(&okay); + const float z = line[3].toFloat(&okay); + verts.push_back(Vertex(x, y, z)); } - indices[v.second] = vertex_count - 1; + if (!file.readLine().trimmed().startsWith("endloop") || + !file.readLine().trimmed().startsWith("endfacet")) + { + okay = false; + break; + } + tri_count++; } - verts.resize(vertex_count); - std::vector flat_verts; - flat_verts.reserve(vertex_count*3); - for (auto v : verts) + if (okay) { - flat_verts.push_back(v.first.x); - flat_verts.push_back(v.first.y); - flat_verts.push_back(v.first.z); + return mesh_from_verts(tri_count, verts); + } + else + { + emit error_bad_stl(); + return NULL; } - - return new Mesh(flat_verts, indices); }