From 76d3823bd4eeb0ac60ae50abf2c6fded051a23e2 Mon Sep 17 00:00:00 2001 From: Stinkfist0 Date: Thu, 17 Mar 2016 00:25:33 +0200 Subject: [PATCH] Make UVs a property of IfcGeom::Representation::Triangulation. Implement writing of UVs for OBJ output. --- src/ifcconvert/ColladaSerializer.cpp | 67 +++++++---------------- src/ifcconvert/ColladaSerializer.h | 11 ++-- src/ifcconvert/IfcConvert.cpp | 21 ++++--- src/ifcconvert/WavefrontObjSerializer.cpp | 12 +++- src/ifcgeom/IfcGeomRepresentation.h | 38 +++++++++++++ 5 files changed, 85 insertions(+), 64 deletions(-) diff --git a/src/ifcconvert/ColladaSerializer.cpp b/src/ifcconvert/ColladaSerializer.cpp index 7ad6c8d656..ed3bb7dce7 100644 --- a/src/ifcconvert/ColladaSerializer.cpp +++ b/src/ifcconvert/ColladaSerializer.cpp @@ -38,35 +38,6 @@ static void collada_id(std::string &s) IfcUtil::escape_xml(s); } -/// @todo Very simple impl. Assumes that input vertices and normals match 1:1. -static std::vector box_project_uvs(const std::vector &vertices, const std::vector &normals) -{ - std::vector uvs; - uvs.resize(vertices.size() / 3 * 2); - for(size_t uv_idx = 0, v_idx = 0; - uv_idx < uvs.size() && v_idx < vertices.size() && v_idx < normals.size(); - uv_idx += 2, v_idx += 3) { - - real_t n_x = normals[v_idx], n_y = normals[v_idx+1], n_z = normals[v_idx+2]; - real_t v_x = vertices[v_idx], v_y = vertices[v_idx+1], v_z = vertices[v_idx+2]; - - if (std::abs(n_x) > std::abs(n_y) && std::abs(n_x) > std::abs(n_z)) { - uvs[uv_idx] = v_z; - uvs[uv_idx+1] = v_y; - } - if (std::abs(n_y) > std::abs(n_x) && std::abs(n_y) > std::abs(n_z)) { - uvs[uv_idx] = v_x; - uvs[uv_idx+1] = v_z; - } - if (std::abs(n_z) > std::abs(n_x) && std::abs(n_z) > std::abs(n_y)) { - uvs[uv_idx] = v_x; - uvs[uv_idx+1] = v_y; - } - } - - return uvs; -} - void ColladaSerializer::ColladaExporter::ColladaGeometries::addFloatSource(const std::string& mesh_id, const std::string& suffix, const std::vector& floats, const char* coords /* = "XYZ" */) { @@ -89,20 +60,21 @@ void ColladaSerializer::ColladaExporter::ColladaGeometries::addFloatSource(const void ColladaSerializer::ColladaExporter::ColladaGeometries::write( const std::string &mesh_id, const std::string& default_material_name, const std::vector& positions, const std::vector& normals, const std::vector& faces, const std::vector& edges, - const std::vector material_ids, const std::vector& materials) + const std::vector material_ids, const std::vector& materials, + const std::vector& uvs) { openMesh(mesh_id); // The normals vector can be empty for example when the WELD_VERTICES setting is used. // IfcOpenShell does not provide them with multiple face normals collapsed into a single vertex. const bool has_normals = !normals.empty(); - const bool generate_uvs = (has_normals && serializer->settings().get(IfcGeom::IteratorSettings::GENERATE_UVS)); + const bool has_uvs = !uvs.empty(); addFloatSource(mesh_id, COLLADASW::LibraryGeometries::POSITIONS_SOURCE_ID_SUFFIX, positions); if (has_normals) { addFloatSource(mesh_id, COLLADASW::LibraryGeometries::NORMALS_SOURCE_ID_SUFFIX, normals); - if (generate_uvs) { - addFloatSource(mesh_id, COLLADASW::LibraryGeometries::TEXCOORDS_SOURCE_ID_SUFFIX, box_project_uvs(positions, normals), "UV"); + if (has_uvs) { + addFloatSource(mesh_id, COLLADASW::LibraryGeometries::TEXCOORDS_SOURCE_ID_SUFFIX, uvs, "UV"); } } @@ -129,13 +101,13 @@ void ColladaSerializer::ColladaExporter::ColladaGeometries::write( if (has_normals) { triangles.getInputList().push_back(COLLADASW::Input(COLLADASW::InputSemantic::NORMAL,"#" + mesh_id + COLLADASW::LibraryGeometries::NORMALS_SOURCE_ID_SUFFIX, offset++)); } - if (generate_uvs) { + if (has_uvs) { triangles.getInputList().push_back(COLLADASW::Input(COLLADASW::InputSemantic::TEXCOORD,"#" + mesh_id + COLLADASW::LibraryGeometries::TEXCOORDS_SOURCE_ID_SUFFIX, offset++)); } triangles.prepareToAppendValues(); for (std::vector::const_iterator jt = index_range_start; jt != it; ++jt) { const int idx = *jt; - if (has_normals && generate_uvs) { + if (has_normals && has_uvs) { triangles.appendValues(idx, idx, idx); } else if(has_normals) { triangles.appendValues(idx, idx); @@ -318,13 +290,14 @@ void ColladaSerializer::ColladaExporter::startDocument(const std::string& unit_n asset.add(); } -void ColladaSerializer::ColladaExporter::write(const std::string& unique_id, const std::string& type, - const std::vector& matrix, const std::vector& vertices, const std::vector& normals, - const std::vector& faces, const std::vector& edges, const std::vector& material_ids, - const std::vector& _materials) +void ColladaSerializer::ColladaExporter::write(const IfcGeom::TriangulationElement* o) { + const IfcGeom::Representation::Triangulation& mesh = o->geometry(); + const std::string name = serializer->settings().get(IfcGeom::IteratorSettings::USE_ELEMENT_GUIDS) ? + o->guid() : (serializer->settings().get(IfcGeom::IteratorSettings::USE_ELEMENT_NAMES) ? o->name() : o->unique_id()); + std::vector material_references; - foreach(const IfcGeom::Material& material, _materials) { + foreach(const IfcGeom::Material& material, mesh.materials()) { if (!materials.contains(material)) { materials.add(material); } @@ -333,7 +306,11 @@ void ColladaSerializer::ColladaExporter::write(const std::string& unique_id, con collada_id(material_name); material_references.push_back(material_name); } - deferreds.push_back(DeferredObject(unique_id, type, matrix, vertices, normals, faces, edges, material_ids, _materials, material_references)); + + deferreds.push_back( + DeferredObject(name, o->type(), o->transformation().matrix().data(), mesh.verts(), mesh.normals(), + mesh.faces(), mesh.edges(), mesh.material_ids(), mesh.materials(), material_references, mesh.uvs()) + ); } void ColladaSerializer::ColladaExporter::endDocument() { @@ -342,7 +319,7 @@ void ColladaSerializer::ColladaExporter::endDocument() { materials.write(); for (std::vector::const_iterator it = deferreds.begin(); it != deferreds.end(); ++it) { const std::string object_name = it->unique_id + "-representation"; - geometries.write(object_name, it->type, it->vertices, it->normals, it->faces, it->edges, it->material_ids, it->materials); + geometries.write(object_name, it->type, it->vertices, it->normals, it->faces, it->edges, it->material_ids, it->materials, it->uvs); } geometries.close(); for (std::vector::const_iterator it = deferreds.begin(); it != deferreds.end(); ++it) { @@ -362,11 +339,7 @@ void ColladaSerializer::writeHeader() { } void ColladaSerializer::write(const IfcGeom::TriangulationElement* o) { - const IfcGeom::Representation::Triangulation& mesh = o->geometry(); - const std::string name = settings().get(IfcGeom::IteratorSettings::USE_ELEMENT_GUIDS) ? - o->guid() : (settings().get(IfcGeom::IteratorSettings::USE_ELEMENT_NAMES) ? o->name() : o->unique_id()); - exporter.write(name, o->type(), o->transformation().matrix().data(), mesh.verts(), - mesh.normals(), mesh.faces(), mesh.edges(), mesh.material_ids(), mesh.materials()); + exporter.write(o); } void ColladaSerializer::finalize() { diff --git a/src/ifcconvert/ColladaSerializer.h b/src/ifcconvert/ColladaSerializer.h index 740b20707a..97f8e8540c 100644 --- a/src/ifcconvert/ColladaSerializer.h +++ b/src/ifcconvert/ColladaSerializer.h @@ -60,7 +60,8 @@ private: void write(const std::string &mesh_id, const std::string& default_material_name, const std::vector& positions, const std::vector& normals, const std::vector& faces, const std::vector& edges, - const std::vector material_ids, const std::vector& materials); + const std::vector material_ids, const std::vector& materials, + const std::vector& uvs); void close(); ColladaSerializer *serializer; }; @@ -125,10 +126,11 @@ private: std::vector material_ids; std::vector materials; std::vector material_references; + std::vector uvs; DeferredObject(const std::string& unique_id, const std::string& type, const std::vector& matrix, const std::vector& vertices, const std::vector& normals, const std::vector& faces, const std::vector& edges, const std::vector& material_ids, const std::vector& materials, - const std::vector& material_references) + const std::vector& material_references, const std::vector& uvs) : unique_id(unique_id) , type(type) , matrix(matrix) @@ -139,6 +141,7 @@ private: , material_ids(material_ids) , materials(materials) , material_references(material_references) + , uvs(uvs) {} }; COLLADABU::NativeString filename; @@ -160,9 +163,7 @@ private: std::vector deferreds; virtual ~ColladaExporter() {} void startDocument(const std::string& unit_name, float unit_magnitude); - void write(const std::string& unique_id, const std::string& type, const std::vector& matrix, - const std::vector& vertices, const std::vector& normals, const std::vector& faces, - const std::vector& edges, const std::vector& material_ids, const std::vector& materials); + void write(const IfcGeom::TriangulationElement* o); void endDocument(); }; ColladaExporter exporter; diff --git a/src/ifcconvert/IfcConvert.cpp b/src/ifcconvert/IfcConvert.cpp index 4e695c503d..86ec41f731 100644 --- a/src/ifcconvert/IfcConvert.cpp +++ b/src/ifcconvert/IfcConvert.cpp @@ -177,7 +177,10 @@ int main(int argc, char** argv) { "in instances where you're going to recompute normals for the exported " "model in other modelling application in any case.") ("deflection-tolerance", boost::program_options::value(&deflection_tolerance), - "Sets the deflection tolerance of the mesher, 1e-3 by default if not specified."); + "Sets the deflection tolerance of the mesher, 1e-3 by default if not specified.") + ("generate-uvs", + "Generates UVs (texture coordinates) by using simple box projection. Requires normals. " + "Not guaranteed to work properly if used with --weld-vertices."); std::string bounds; boost::program_options::options_description serializer_options("Serialization options"); @@ -196,10 +199,7 @@ int main(int argc, char** argv) { "Applicable for OBJ and DAE output.") ("center-model", "Centers the models upon serialization by applying the center point of " - "the scene bounds as an offset. Applicable only for DAE output currently.") - ("generate-uvs", - "Generates UVs (texture coordinates) by using simple box projection. Requires normals. Not guaranteed to work " - "properly if used with --weld-vertices. Applicable only for DAE output currently."); + "the scene bounds as an offset. Applicable only for DAE output currently."); boost::program_options::options_description cmdline_options; cmdline_options.add(generic_options).add(fileio_options).add(geom_options).add(serializer_options); @@ -369,7 +369,7 @@ int main(int argc, char** argv) { static_cast(serializer)->setBoundingRectangle(*bounding_width, *bounding_height); } } else { - Logger::Message(Logger::LOG_ERROR, "Unknown output filename extension"); + Logger::Message(Logger::LOG_ERROR, "Unknown output filename extension '" + output_extension + "'"); write_log(); print_usage(); return 1; @@ -381,16 +381,15 @@ int main(int argc, char** argv) { settings.set(IfcGeom::IteratorSettings::CENTER_MODEL, false); center_model = false; } - if (generate_uvs) { - Logger::Message(Logger::LOG_NOTICE, "--generate-uvs setting ignored for non-DAE output"); - settings.set(IfcGeom::IteratorSettings::GENERATE_UVS, false); - } } if (!serializer->isTesselated()) { if (weld_vertices) { - Logger::Message(Logger::LOG_NOTICE, "Weld vertices setting ignored when writing STEP or IGES files"); + Logger::Message(Logger::LOG_NOTICE, "Weld vertices setting ignored when writing non-tesselated output"); } + if (generate_uvs) { + Logger::Message(Logger::LOG_NOTICE, "Generate UVs setting ignored when writing non-tesselated output"); + } settings.set(IfcGeom::IteratorSettings::DISABLE_TRIANGULATION, true); } diff --git a/src/ifcconvert/WavefrontObjSerializer.cpp b/src/ifcconvert/WavefrontObjSerializer.cpp index a51f36f98f..896edef76e 100644 --- a/src/ifcconvert/WavefrontObjSerializer.cpp +++ b/src/ifcconvert/WavefrontObjSerializer.cpp @@ -22,6 +22,7 @@ #include "../ifcgeom/IfcGeomRenderStyles.h" +#include #include bool WaveFrontOBJSerializer::ready() { @@ -96,9 +97,16 @@ void WaveFrontOBJSerializer::write(const IfcGeom::TriangulationElement* obj_stream << "vn " << x << " " << y << " " << z << "\n"; } + for (std::vector::const_iterator it = mesh.uvs().begin(); it != mesh.uvs().end();) { + const real_t u = *it++; + const real_t v = *it++; + obj_stream << "vt " << u << " " << v << "\n"; + } + int previous_material_id = -2; std::vector::const_iterator material_it = mesh.material_ids().begin(); + const bool has_uvs = !mesh.uvs().empty(); for ( std::vector::const_iterator it = mesh.faces().begin(); it != mesh.faces().end(); ) { const int material_id = *(material_it++); @@ -118,7 +126,9 @@ void WaveFrontOBJSerializer::write(const IfcGeom::TriangulationElement* const int v1 = *(it++)+vcount_total; const int v2 = *(it++)+vcount_total; const int v3 = *(it++)+vcount_total; - obj_stream << "f " << v1 << "//" << v1 << " " << v2 << "//" << v2 << " " << v3 << "//" << v3 << "\n"; + obj_stream << "f " << v1 << "/" << (has_uvs ? boost::lexical_cast(v1) : "") << "/" << v1 << " " + << v2 << "/" << (has_uvs ? boost::lexical_cast(v2) : "") << "/" << v2 << " " + << v3 << "/" << (has_uvs ? boost::lexical_cast(v3) : "") << "/" << v3 << "\n"; } diff --git a/src/ifcgeom/IfcGeomRepresentation.h b/src/ifcgeom/IfcGeomRepresentation.h index de46e1c93c..d921e80f93 100644 --- a/src/ifcgeom/IfcGeomRepresentation.h +++ b/src/ifcgeom/IfcGeomRepresentation.h @@ -102,6 +102,7 @@ namespace IfcGeom { std::vector _faces; std::vector _edges; std::vector

_normals; + std::vector

uvs_; std::vector _material_ids; std::vector _materials; VertexKeyMap welds; @@ -112,8 +113,10 @@ namespace IfcGeom { const std::vector& faces() const { return _faces; } const std::vector& edges() const { return _edges; } const std::vector

& normals() const { return _normals; } + const std::vector

& uvs() const { return uvs_; } const std::vector& material_ids() const { return _material_ids; } const std::vector& materials() const { return _materials; } + Triangulation(const BRep& shape_model) : Representation(shape_model.settings()) , _id(shape_model.getId()) @@ -246,6 +249,10 @@ namespace IfcGeom { } } + if (!_normals.empty() && settings().get(IfcGeom::IteratorSettings::GENERATE_UVS)) { + uvs_ = box_project_uvs(_verts, _normals); + } + if (num_faces == 0) { // Edges are only emitted if there are no faces. A mixed representation of faces // and loose edges is discouraged by the standard. An alternative would be to use @@ -276,6 +283,37 @@ namespace IfcGeom { } } virtual ~Triangulation() {} + + /// Generates UVs for a single mesh using box projection. + /// @todo Very simple impl. Assumes that input vertices and normals match 1:1. + static std::vector

box_project_uvs(const std::vector

&vertices, const std::vector

&normals) + { + std::vector

uvs; + uvs.resize(vertices.size() / 3 * 2); + for (size_t uv_idx = 0, v_idx = 0; + uv_idx < uvs.size() && v_idx < vertices.size() && v_idx < normals.size(); + uv_idx += 2, v_idx += 3) { + + P n_x = normals[v_idx], n_y = normals[v_idx + 1], n_z = normals[v_idx + 2]; + P v_x = vertices[v_idx], v_y = vertices[v_idx + 1], v_z = vertices[v_idx + 2]; + + if (std::abs(n_x) > std::abs(n_y) && std::abs(n_x) > std::abs(n_z)) { + uvs[uv_idx] = v_z; + uvs[uv_idx + 1] = v_y; + } + if (std::abs(n_y) > std::abs(n_x) && std::abs(n_y) > std::abs(n_z)) { + uvs[uv_idx] = v_x; + uvs[uv_idx + 1] = v_z; + } + if (std::abs(n_z) > std::abs(n_x) && std::abs(n_z) > std::abs(n_y)) { + uvs[uv_idx] = v_x; + uvs[uv_idx + 1] = v_y; + } + } + + return uvs; + } + private: // Welds vertices that belong to different faces int addVertex(int material_index, const gp_XYZ& p) {