void write_ply(const std::string &filename, const MatrixXu &F, const MatrixXf &V, const MatrixXf &N, const MatrixXf &Nf, const MatrixXf &UV, const MatrixXf &C, const ProgressCallback &progress) { auto message_cb = [](p_ply ply, const char *msg) { cerr << "rply: " << msg << endl; }; Timer<> timer; cout << "Writing \"" << filename << "\" (V=" << V.cols() << ", F=" << F.cols() << ") .. "; cout.flush(); if (N.size() > 0 && Nf.size() > 0) throw std::runtime_error("Please specify either face or vertex normals but not both!"); p_ply ply = ply_create(filename.c_str(), PLY_DEFAULT, message_cb, 0, nullptr); if (!ply) throw std::runtime_error("Unable to write PLY file!"); ply_add_comment(ply, "Generated by Instant Meshes"); ply_add_element(ply, "vertex", V.cols()); ply_add_scalar_property(ply, "x", PLY_FLOAT); ply_add_scalar_property(ply, "y", PLY_FLOAT); ply_add_scalar_property(ply, "z", PLY_FLOAT); if (N.size() > 0) { ply_add_scalar_property(ply, "nx", PLY_FLOAT); ply_add_scalar_property(ply, "ny", PLY_FLOAT); ply_add_scalar_property(ply, "nz", PLY_FLOAT); if (N.cols() != V.cols() || N.rows() != 3) throw std::runtime_error("Vertex normal matrix has incorrect size"); } if (UV.size() > 0) { ply_add_scalar_property(ply, "u", PLY_FLOAT); ply_add_scalar_property(ply, "v", PLY_FLOAT); if (UV.cols() != V.cols() || UV.rows() != 2) throw std::runtime_error("Texture coordinate matrix has incorrect size"); } if (C.size() > 0) { ply_add_scalar_property(ply, "red", PLY_FLOAT); ply_add_scalar_property(ply, "green", PLY_FLOAT); ply_add_scalar_property(ply, "blue", PLY_FLOAT); if (C.cols() != V.cols() || (C.rows() != 3 && C.rows() != 4)) throw std::runtime_error("Color matrix has incorrect size"); } /* Check for irregular faces */ std::map<uint32_t, std::pair<uint32_t, std::map<uint32_t, uint32_t>>> irregular; size_t nIrregular = 0; if (F.rows() == 4) { for (uint32_t f=0; f<F.cols(); ++f) { if (F(2, f) == F(3, f)) { nIrregular++; auto &value = irregular[F(2, f)]; value.first = f; value.second[F(0, f)] = F(1, f); } } } ply_add_element(ply, "face", F.cols() - nIrregular + irregular.size()); ply_add_list_property(ply, "vertex_indices", PLY_UINT8, PLY_INT); if (Nf.size() > 0) { ply_add_scalar_property(ply, "nx", PLY_FLOAT); ply_add_scalar_property(ply, "ny", PLY_FLOAT); ply_add_scalar_property(ply, "nz", PLY_FLOAT); if (Nf.cols() != F.cols() || Nf.rows() != 3) throw std::runtime_error("Face normal matrix has incorrect size"); } ply_write_header(ply); for (uint32_t j=0; j<V.cols(); ++j) { for (uint32_t i=0; i<V.rows(); ++i) ply_write(ply, V(i, j)); if (N.size() > 0) { for (uint32_t i=0; i<N.rows(); ++i) ply_write(ply, N(i, j)); } if (UV.size() > 0) { for (uint32_t i=0; i<UV.rows(); ++i) ply_write(ply, UV(i, j)); } if (C.size() > 0) { for (uint32_t i=0; i<std::min(3u, (uint32_t) C.rows()); ++i) ply_write(ply, C(i, j)); } if (progress && j % 500000 == 0) progress("Writing vertex data", j / (Float) V.cols()); } for (uint32_t f=0; f<F.cols(); ++f) { if (F.rows() == 4 && F(2, f) == F(3, f)) continue; ply_write(ply, F.rows()); for (uint32_t i=0; i<F.rows(); ++i) ply_write(ply, F(i, f)); if (Nf.size() > 0) { for (uint32_t i=0; i<Nf.rows(); ++i) ply_write(ply, Nf(i, f)); } if (progress && f % 500000 == 0) progress("Writing face data", f / (Float) F.cols()); } for (auto item : irregular) { auto face = item.second; uint32_t v = face.second.begin()->first, first = v; ply_write(ply, face.second.size()); while (true) { ply_write(ply, v); v = face.second[v]; if (v == first) break; } if (Nf.size() > 0) { for (uint32_t i=0; i<Nf.rows(); ++i) ply_write(ply, Nf(i, face.first)); } } ply_close(ply); cout << "done. ("; if (irregular.size() > 0) cout << irregular.size() << " irregular faces, "; cout << "took " << timeString(timer.value()) << ")" << endl; }
void load_ply(const std::string &filename, MatrixXu &F, MatrixXf &V, bool load_faces, const ProgressCallback &progress) { auto message_cb = [](p_ply ply, const char *msg) { cerr << "rply: " << msg << endl; }; Timer<> timer; cout << "Loading \"" << filename << "\" .. "; cout.flush(); p_ply ply = ply_open(filename.c_str(), message_cb, 0, nullptr); if (!ply) throw std::runtime_error("Unable to open PLY file \"" + filename + "\"!"); if (!ply_read_header(ply)) { ply_close(ply); throw std::runtime_error("Unable to open PLY header of \"" + filename + "\"!"); } p_ply_element element = nullptr; uint32_t vertexCount = 0, faceCount = 0; /* Inspect the structure of the PLY file */ while ((element = ply_get_next_element(ply, element)) != nullptr) { const char *name; long nInstances; ply_get_element_info(element, &name, &nInstances); if (!strcmp(name, "vertex")) vertexCount = (uint32_t) nInstances; else if (!strcmp(name, "face")) faceCount = (uint32_t) nInstances; } if (vertexCount == 0 && faceCount == 0) throw std::runtime_error("PLY file \"" + filename + "\" is invalid! No face/vertex/elements found!"); if (load_faces) F.resize(3, faceCount); V.resize(3, vertexCount); struct VertexCallbackData { MatrixXf &V; const ProgressCallback &progress; VertexCallbackData(MatrixXf &V, const ProgressCallback &progress) : V(V), progress(progress) {} }; struct FaceCallbackData { MatrixXu &F; const ProgressCallback &progress; FaceCallbackData(MatrixXu &F, const ProgressCallback &progress) : F(F), progress(progress) {} }; auto rply_vertex_cb = [](p_ply_argument argument) -> int { VertexCallbackData *data; long index, coord; ply_get_argument_user_data(argument, (void **) &data, &coord); ply_get_argument_element(argument, nullptr, &index); data->V(coord, index) = (Float) ply_get_argument_value(argument); if (data->progress && coord == 0 && index % 500000 == 0) data->progress("Loading vertex data", index / (Float) data->V.cols()); return 1; }; auto rply_index_cb = [](p_ply_argument argument) -> int { FaceCallbackData *data; long length, value_index, index; ply_get_argument_property(argument, nullptr, &length, &value_index); if (length != 3) throw std::runtime_error("Only triangle faces are supported!"); ply_get_argument_user_data(argument, (void **) &data, nullptr); ply_get_argument_element(argument, nullptr, &index); if (value_index >= 0) data->F(value_index, index) = (uint32_t) ply_get_argument_value(argument); if (data->progress && value_index == 0 && index % 500000 == 0) data->progress("Loading face data", index / (Float) data->F.cols()); return 1; }; VertexCallbackData vcbData(V, progress); FaceCallbackData fcbData(F, progress); if (!ply_set_read_cb(ply, "vertex", "x", rply_vertex_cb, &vcbData, 0) || !ply_set_read_cb(ply, "vertex", "y", rply_vertex_cb, &vcbData, 1) || !ply_set_read_cb(ply, "vertex", "z", rply_vertex_cb, &vcbData, 2)) { ply_close(ply); throw std::runtime_error("PLY file \"" + filename + "\" does not contain vertex position data!"); } if (load_faces) { if (!ply_set_read_cb(ply, "face", "vertex_indices", rply_index_cb, &fcbData, 0)) { ply_close(ply); throw std::runtime_error("PLY file \"" + filename + "\" does not contain vertex indices!"); } } if (!ply_read(ply)) { ply_close(ply); throw std::runtime_error("Error while loading PLY data from \"" + filename + "\"!"); } ply_close(ply); cout << "done. (V=" << vertexCount; if (load_faces) cout << ", F=" << faceCount; cout << ", took " << timeString(timer.value()) << ")" << endl; }
void build_dedge(const MatrixXu &F, const MatrixXf &V, VectorXu &V2E, VectorXu &E2E, VectorXb &boundary, VectorXb &nonManifold, const ProgressCallback &progress, bool quiet) { if (!quiet) { cout << "Building a directed edge data structure .. "; cout.flush(); } Timer<> timer; if (progress && !quiet) progress("Building directed edge data structure", 0.0f); V2E.resize(V.cols()); V2E.setConstant(INVALID); uint32_t deg = F.rows(); std::vector<std::pair<uint32_t, uint32_t>> tmp(F.size()); tbb::parallel_for( tbb::blocked_range<uint32_t>(0u, (uint32_t) F.cols(), GRAIN_SIZE), [&](const tbb::blocked_range<uint32_t> &range) { for (uint32_t f = range.begin(); f != range.end(); ++f) { for (uint32_t i = 0; i < deg; ++i) { uint32_t idx_cur = F(i, f), idx_next = F((i+1)%deg, f), edge_id = deg * f + i; if (idx_cur >= V.cols() || idx_next >= V.cols()) throw std::runtime_error("Mesh data contains an out-of-bounds vertex reference!"); if (idx_cur == idx_next) continue; tmp[edge_id] = std::make_pair(idx_next, INVALID); if (!atomicCompareAndExchange(&V2E[idx_cur], edge_id, INVALID)) { uint32_t idx = V2E[idx_cur]; while (!atomicCompareAndExchange(&tmp[idx].second, edge_id, INVALID)) idx = tmp[idx].second; } } } if (!quiet) SHOW_PROGRESS_RANGE(range, F.cols(), "Building directed edge data structure (1/3)"); } ); nonManifold.resize(V.cols()); nonManifold.setConstant(false); E2E.resize(F.cols() * deg); E2E.setConstant(INVALID); tbb::parallel_for( tbb::blocked_range<uint32_t>(0u, (uint32_t) F.cols(), GRAIN_SIZE), [&](const tbb::blocked_range<uint32_t> &range) { for (uint32_t f = range.begin(); f != range.end(); ++f) { for (uint32_t i = 0; i < deg; ++i) { uint32_t idx_cur = F(i, f), idx_next = F((i+1)%deg, f), edge_id_cur = deg * f + i; if (idx_cur == idx_next) continue; uint32_t it = V2E[idx_next], edge_id_opp = INVALID; while (it != INVALID) { if (tmp[it].first == idx_cur) { if (edge_id_opp == INVALID) { edge_id_opp = it; } else { nonManifold[idx_cur] = true; nonManifold[idx_next] = true; edge_id_opp = INVALID; break; } } it = tmp[it].second; } if (edge_id_opp != INVALID && edge_id_cur < edge_id_opp) { E2E[edge_id_cur] = edge_id_opp; E2E[edge_id_opp] = edge_id_cur; } } } if (!quiet) SHOW_PROGRESS_RANGE(range, F.cols(), "Building directed edge data structure (2/3)"); } ); std::atomic<uint32_t> nonManifoldCounter(0), boundaryCounter(0), isolatedCounter(0); boundary.resize(V.cols()); boundary.setConstant(false); /* Detect boundary regions of the mesh and adjust vertex->edge pointers*/ tbb::parallel_for( tbb::blocked_range<uint32_t>(0u, (uint32_t) V.cols(), GRAIN_SIZE), [&](const tbb::blocked_range<uint32_t> &range) { for (uint32_t i = range.begin(); i != range.end(); ++i) { uint32_t edge = V2E[i]; if (edge == INVALID) { isolatedCounter++; continue; } if (nonManifold[i]) { nonManifoldCounter++; V2E[i] = INVALID; continue; } /* Walk backwards to the first boundary edge (if any) */ uint32_t start = edge, v2e = INVALID; do { v2e = std::min(v2e, edge); uint32_t prevEdge = E2E[dedge_prev(edge, deg)]; if (prevEdge == INVALID) { /* Reached boundary -- update the vertex->edge link */ v2e = edge; boundary[i] = true; boundaryCounter++; break; } edge = prevEdge; } while (edge != start); V2E[i] = v2e; } if (!quiet) SHOW_PROGRESS_RANGE(range, V.cols(), "Building directed edge data structure (3/3)"); } ); if (!quiet) { cout << "done. ("; if (nonManifoldCounter) cout << nonManifoldCounter << " non-manifold vertices, "; if (boundaryCounter) cout << boundaryCounter << " boundary vertices, "; if (isolatedCounter) cout << isolatedCounter << " isolated vertices, "; cout << "took " << timeString(timer.value()) << ")" << endl; } }