From ba9a2c3963c068403463a21baaa7e65a7fdd1a29 Mon Sep 17 00:00:00 2001 From: Squareys Date: Wed, 22 Jun 2016 18:14:29 +0200 Subject: [PATCH 1/6] WIP --- src/MagnumPlugins/ObjImporter/ObjImporter.cpp | 250 ++++++------------ src/MagnumPlugins/ObjImporter/ObjImporter.h | 5 +- 2 files changed, 92 insertions(+), 163 deletions(-) diff --git a/src/MagnumPlugins/ObjImporter/ObjImporter.cpp b/src/MagnumPlugins/ObjImporter/ObjImporter.cpp index 9470ca177..0848af68e 100644 --- a/src/MagnumPlugins/ObjImporter/ObjImporter.cpp +++ b/src/MagnumPlugins/ObjImporter/ObjImporter.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -38,42 +39,76 @@ #include "Magnum/Math/Color.h" #include "Magnum/Trade/MeshData3D.h" +#if defined(CORRADE_TARGET_NACL_NEWLIB) || defined(CORRADE_TARGET_ANDROID) +#include +#endif + +using namespace Corrade::Containers; + namespace Magnum { namespace Trade { -struct ObjImporter::File { +struct ImporterState { std::unordered_map meshesForName; std::vector meshNames; std::vector> meshes; - std::unique_ptr in; + + std::optional primitive; + std::vector positions; + std::vector> textureCoordinates; + std::vector> normals; + std::vector positionIndices; + std::vector textureCoordinateIndices; + std::vector normalIndices; }; namespace { -void ignoreLine(std::istream& in) { - in.ignore(std::numeric_limits::max(), '\n'); +int findNext(const ArrayView& pos, char c) { + for(int i = 0; i < pos.size(); ++i) { + if(pos[i] == c) { + return i; + } + } + return -1; } -template Math::Vector extractFloatData(const std::string& str, Float* extra = nullptr) { - std::vector data = Utility::String::splitWithoutEmptyParts(str, ' '); - if(data.size() < size || data.size() > size + (extra ? 1 : 0)) { - Error() << "Trade::ObjImporter::mesh3D(): invalid float array size"; - throw 0; +ArrayView skipWhitespaces(const ArrayView& pos) { + for(int i = 0; i < pos.size(); ++i) { + if(pos[i] != ' ' && pos[i] != '\t') { + return pos.suffix(i); + } } + return ArrayView{}; +} - Math::Vector output; - - for(std::size_t i = 0; i != size; ++i) - output[i] = std::stof(data[i]); +ArrayView ignoreLine(const ArrayView& pos) { + return pos.suffix(findNext(pos, '\n')); +} - if(data.size() == size+1) { - /* This should be obvious from the first if, but add this just to make - Clang Analyzer happy */ - CORRADE_INTERNAL_ASSERT(extra); +std::tuple> nextLine(const ArrayView& pos) { + int i = 0; + for(; i < pos.size(); ++i) { + if(pos[i] == '\n') { + break; + } + } + std::string str = pos.prefix(i).data(); + return std::make_tuple(str, pos.suffix(i+1)); +} - *extra = std::stof(data.back()); +std::tuple> nextWord(const ArrayView& pos) { + int i = 0; + for(; i < pos.size(); ++i) { + if(pos[i] == ' ') { + break; + } } + std::string str = pos.prefix(i).data(); + return std::make_tuple(str, pos.suffix(i+1)); +} + +std::tuple> asFloat(const ArrayView& pos) { - return output; } template void reindex(const std::vector& indices, std::vector& data) { @@ -96,26 +131,14 @@ ObjImporter::~ObjImporter() = default; auto ObjImporter::doFeatures() const -> Features { return Feature::OpenData; } -void ObjImporter::doClose() { _file.reset(); } - -bool ObjImporter::doIsOpened() const { return !!_file; } - -void ObjImporter::doOpenFile(const std::string& filename) { - std::unique_ptr in{new std::ifstream{filename, std::ios::binary}}; - if(!in->good()) { - Error() << "Trade::ObjImporter::openFile(): cannot open file" << filename; - return; - } +bool ObjImporter::doIsOpened() const { return _in; } - _file.reset(new File); - _file->in = std::move(in); - parseMeshNames(); -} +void ObjImporter::doClose() { _in = nullptr; } void ObjImporter::doOpenData(Containers::ArrayView data) { - _file.reset(new File); - _file->in.reset(new std::istringstream{{data.begin(), data.size()}}); - + _in = Containers::Array{data.size()}; + std::copy(data.begin(), data.end(), _in.begin()); + _state.reset(new ImporterState); parseMeshNames(); } @@ -125,144 +148,33 @@ void ObjImporter::parseMeshNames() { UnsignedInt positionIndexOffset = 1; UnsignedInt normalIndexOffset = 1; UnsignedInt textureCoordinateIndexOffset = 1; - _file->meshes.emplace_back(0, 0, positionIndexOffset, normalIndexOffset, textureCoordinateIndexOffset); + _state->meshes.emplace_back(0, 0, positionIndexOffset, normalIndexOffset, textureCoordinateIndexOffset); - /* The first mesh doesn't have name by default but we might find it later, - so we need to track whether there are any data before first name */ - bool thisIsFirstMeshAndItHasNoData = true; - _file->meshNames.emplace_back(); + _state->meshNames.emplace_back(); - while(_file->in->good()) { - /* The previous object might end at the beginning of this line */ - const std::streampos end = _file->in->tellg(); + ArrayView pos = _in; + while(!pos.empty()) { /* Comment line */ - if(_file->in->peek() == '#') { - ignoreLine(*_file->in); + if(pos[0] == '#') { + pos = ignoreLine(pos); continue; } /* Parse the keyword */ std::string keyword; - *_file->in >> keyword; + std::tie(keyword, pos) = nextWord(pos); /* Mesh name */ if(keyword == "o") { - std::string name; - std::getline(*_file->in, name); - name = Utility::String::trim(name); - - /* This is the name of first mesh */ - if(thisIsFirstMeshAndItHasNoData) { - thisIsFirstMeshAndItHasNoData = false; - - /* Update its name and add it to name map */ - if(!name.empty()) - _file->meshesForName.emplace(name, _file->meshes.size() - 1); - _file->meshNames.back() = std::move(name); - - /* Update its begin offset to be more precise */ - std::get<0>(_file->meshes.back()) = _file->in->tellg(); - - /* Otherwise this is a name of new mesh */ - } else { - /* Set end of the previous one */ - std::get<1>(_file->meshes.back()) = end; - - /* Save name and offset of the new one. The end offset will be - updated later. */ - if(!name.empty()) - _file->meshesForName.emplace(name, _file->meshes.size()); - _file->meshNames.emplace_back(std::move(name)); - _file->meshes.emplace_back(_file->in->tellg(), 0, positionIndexOffset, textureCoordinateIndexOffset, normalIndexOffset); - } - - continue; - - /* If there are any data/indices before the first name, it means that - the first object is unnamed. We need to check for them. */ - - /* Vertex data, update index offset for the following meshes */ - } else if(keyword == "v") { - ++positionIndexOffset; - thisIsFirstMeshAndItHasNoData = false; - } else if(keyword == "vt") { - ++textureCoordinateIndexOffset; - thisIsFirstMeshAndItHasNoData = false; - } else if(keyword == "vn") { - ++normalIndexOffset; - thisIsFirstMeshAndItHasNoData = false; - - /* Index data, just mark that we found something for first unnamed - object */ - } else if(thisIsFirstMeshAndItHasNoData) for(const std::string& data: {"p", "l", "f"}) { - if(keyword == data) { - thisIsFirstMeshAndItHasNoData = false; - break; - } - } - - /* Ignore the rest of the line */ - ignoreLine(*_file->in); - } - - /* Set end of the last object */ - _file->in->clear(); - _file->in->seekg(0, std::ios::end); - std::get<1>(_file->meshes.back()) = _file->in->tellg(); -} - -UnsignedInt ObjImporter::doMesh3DCount() const { return _file->meshes.size(); } - -Int ObjImporter::doMesh3DForName(const std::string& name) { - const auto it = _file->meshesForName.find(name); - return it == _file->meshesForName.end() ? -1 : it->second; -} - -std::string ObjImporter::doMesh3DName(UnsignedInt id) { - return _file->meshNames[id]; -} - -Containers::Optional ObjImporter::doMesh3D(UnsignedInt id) { - /* Seek the file, set mesh parsing parameters */ - std::streampos begin, end; - UnsignedInt positionIndexOffset, textureCoordinateIndexOffset, normalIndexOffset; - std::tie(begin, end, positionIndexOffset, textureCoordinateIndexOffset, normalIndexOffset) = _file->meshes[id]; - _file->in->seekg(begin); - - Containers::Optional primitive; - std::vector positions; - std::vector> textureCoordinates; - std::vector> normals; - std::vector positionIndices; - std::vector textureCoordinateIndices; - std::vector normalIndices; - - try { while(_file->in->good() && _file->in->tellg() < end) { - /* Ignore comments */ - if(_file->in->peek() == '#') { - ignoreLine(*_file->in); + // TODO continue; } - /* Get the line */ - std::string line; - std::getline(*_file->in, line); - line = Utility::String::trim(line); - - /* Ignore empty lines */ - if(line.empty()) continue; - - /* Split the line into keyword and contents */ - const std::size_t keywordEnd = line.find(' '); - const std::string keyword = line.substr(0, keywordEnd); - const std::string contents = keywordEnd != std::string::npos ? - Utility::String::ltrim(line.substr(keywordEnd+1)) : ""; - /* Vertex position */ if(keyword == "v") { Float extra{1.0f}; - const Vector3 data = extractFloatData<3>(contents, &extra); + const Vector3 data = extractFloatData<3>(pos, &extra); if(!Math::TypeTraits::equals(extra, 1.0f)) { Error() << "Trade::ObjImporter::mesh3D(): homogeneous coordinates are not supported"; return Containers::NullOpt; @@ -374,13 +286,27 @@ Containers::Optional ObjImporter::doMesh3D(UnsignedInt id) { return Containers::NullOpt; } - }} catch(std::exception) { - Error() << "Trade::ObjImporter::mesh3D(): error while converting numeric data"; - return Containers::NullOpt; - } catch(...) { - /* Error message already printed */ - return Containers::NullOpt; + /* Ignore the rest of the line */ + pos = ignoreLine(pos); } +} + +UnsignedInt ObjImporter::doMesh3DCount() const { return _state->meshes.size(); } + +Int ObjImporter::doMesh3DForName(const std::string& name) { + const auto it = _state->meshesForName.find(name); + return it == _state->meshesForName.end() ? -1 : it->second; +} + +std::string ObjImporter::doMesh3DName(UnsignedInt id) { + return _state->meshNames[id]; +} + +std::optional ObjImporter::doMesh3D(UnsignedInt id) { + /* Seek the file, set mesh parsing parameters */ + std::streampos begin, end; + UnsignedInt positionIndexOffset, textureCoordinateIndexOffset, normalIndexOffset; + std::tie(begin, end, positionIndexOffset, textureCoordinateIndexOffset, normalIndexOffset) = _state->meshes[id]; /* There should be at least indexed position data */ if(positions.empty() || positionIndices.empty()) { diff --git a/src/MagnumPlugins/ObjImporter/ObjImporter.h b/src/MagnumPlugins/ObjImporter/ObjImporter.h index 81ec77dce..f1c1b1fa4 100644 --- a/src/MagnumPlugins/ObjImporter/ObjImporter.h +++ b/src/MagnumPlugins/ObjImporter/ObjImporter.h @@ -33,6 +33,8 @@ #include "MagnumPlugins/ObjImporter/configure.h" +#include + #ifndef DOXYGEN_GENERATING_OUTPUT #ifndef MAGNUM_OBJIMPORTER_BUILD_STATIC #if defined(ObjImporter_EXPORTS) || defined(ObjImporterObjects_EXPORTS) @@ -100,7 +102,8 @@ class MAGNUM_OBJIMPORTER_EXPORT ObjImporter: public AbstractImporter { MAGNUM_OBJIMPORTER_LOCAL void parseMeshNames(); - std::unique_ptr _file; + Containers::Array _in; + std::unique_ptr _state; }; }} From f79d002475e49c6d1ebd822188a4cc5e0ae0f1e4 Mon Sep 17 00:00:00 2001 From: Squareys Date: Fri, 24 Jun 2016 11:56:20 +0200 Subject: [PATCH 2/6] SCRAP: Add super-rudimentary objimporter rework + multiMaterial test Signed-off-by: Squareys --- src/MagnumPlugins/ObjImporter/ObjImporter.cpp | 651 ++++++++++++------ src/MagnumPlugins/ObjImporter/ObjImporter.h | 10 +- src/MagnumPlugins/ObjImporter/Test/Test.cpp | 29 + .../ObjImporter/Test/multiMaterial.mtl | 8 + .../ObjImporter/Test/multiMaterial.obj | 60 ++ 5 files changed, 551 insertions(+), 207 deletions(-) create mode 100644 src/MagnumPlugins/ObjImporter/Test/multiMaterial.mtl create mode 100644 src/MagnumPlugins/ObjImporter/Test/multiMaterial.obj diff --git a/src/MagnumPlugins/ObjImporter/ObjImporter.cpp b/src/MagnumPlugins/ObjImporter/ObjImporter.cpp index 0848af68e..fc46ae83a 100644 --- a/src/MagnumPlugins/ObjImporter/ObjImporter.cpp +++ b/src/MagnumPlugins/ObjImporter/ObjImporter.cpp @@ -32,12 +32,18 @@ #include #include #include +#include #include "Magnum/Mesh.h" #include "Magnum/MeshTools/CombineIndexedArrays.h" #include "Magnum/MeshTools/Duplicate.h" +#include "Magnum/Math/Vector3.h" #include "Magnum/Math/Color.h" #include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/ImageData.h" +#include "Magnum/Trade/PhongMaterialData.h" + +#include "MagnumPlugins/TgaImporter/TgaImporter.h" #if defined(CORRADE_TARGET_NACL_NEWLIB) || defined(CORRADE_TARGET_ANDROID) #include @@ -47,78 +53,154 @@ using namespace Corrade::Containers; namespace Magnum { namespace Trade { -struct ImporterState { - std::unordered_map meshesForName; - std::vector meshNames; - std::vector> meshes; - std::optional primitive; +// Not using PhongMaterialData, since we may want to set color and texture to +// later decide which flags we set. We do not know whether we have a texture +// or color beforehand. +struct ObjMaterial { + std::string name; + Color3 ambient; + Color3 diffuse; + Color3 specular; + Float specularity; + UnsignedInt ambientTexture = -1; + UnsignedInt diffuseTexture = -1; + UnsignedInt specularTexture = -1; +}; + +// Semantic object for a set of indices belonging to a mesh. +struct ObjMesh { + std::vector> indices; +}; + +// Kinda misleading: *Not* similar to the OpenGexImporter Importer State. +// This just holds all the members of the importer to 1. avoid huge includes +// in the header and 2. not have to switch file when adding new ones (I needed +// to be done quickly ;) ) +struct ImporterState { std::vector positions; - std::vector> textureCoordinates; - std::vector> normals; - std::vector positionIndices; - std::vector textureCoordinateIndices; - std::vector normalIndices; + std::vector textureCoordinates; + std::vector normals; + std::vector materials; + std::vector meshMaterials; + std::unordered_map materialIndices; + std::vector> meshes; + std::vector textures; + std::unordered_map textureIndices; }; namespace { -int findNext(const ArrayView& pos, char c) { +// entire contents of data in a std::string +std::string arrayToString(const Containers::ArrayView data) { + /* Propagate errors */ + if(!data) return ""; + + if(data.empty() ) { + return ""; + } + + std::string out; + + for(const char* i = data; i != data.end(); ++i) { + const char c = *i; + out += c; + } + + return out; +} + +// find first index of c in pos, cancel search at newline or whitespace if flag set. +int findNext(const ArrayView& pos, char c, bool termByNewline=false, bool termByWhitespace=false) { for(int i = 0; i < pos.size(); ++i) { if(pos[i] == c) { return i; } + + if((pos[i] == '\n' && termByNewline) || (pos[i] == ' ' && termByWhitespace)) { + return -1; + } } return -1; } -ArrayView skipWhitespaces(const ArrayView& pos) { +// find next non-whitespace char and return suffix at that point. +// param strict if "true", '\n' and '\r' are not considered whitespace +ArrayView skipWhitespaces(const ArrayView& pos, bool strict=false) { for(int i = 0; i < pos.size(); ++i) { - if(pos[i] != ' ' && pos[i] != '\t') { + const char c = pos[i]; + if(c != ' ' && c != '\t' && (strict || (c != '\n' && c != '\r'))) { return pos.suffix(i); } } return ArrayView{}; } +// returns suffix from after next '\n' ArrayView ignoreLine(const ArrayView& pos) { - return pos.suffix(findNext(pos, '\n')); + return pos.suffix(findNext(pos, '\n') + 1); } +// same as ignoreLine, but also returns the content "ignored". +// Different description: Get string until next '\n' and return ArrayView of data +// after it. std::tuple> nextLine(const ArrayView& pos) { - int i = 0; - for(; i < pos.size(); ++i) { - if(pos[i] == '\n') { - break; - } + int i = Math::min(findNext(pos, '\n'), findNext(pos, '\r')); + if(i == -1) { + i = pos.size(); + std::string str = arrayToString(pos.prefix(i)); + return std::make_tuple(str, pos.suffix(i)); } - std::string str = pos.prefix(i).data(); + std::string str = arrayToString(pos.prefix(i)); return std::make_tuple(str, pos.suffix(i+1)); } -std::tuple> nextWord(const ArrayView& pos) { - int i = 0; - for(; i < pos.size(); ++i) { - if(pos[i] == ' ') { - break; +// Parse a "v/n/t" string to indices +// Warning: I'm not handing cases like "v/n", where the normal is not terminated by '/', but ' '! +// Same for "v", where even the normal is omitted. +std::tuple, ArrayView> parseVertex(const ArrayView& pos) { + std::array indices{-1, -1, -1}; + + int i = findNext(pos, '/'); + indices[0] = stoi(arrayToString(pos.prefix(i))); + auto endpos = pos.suffix(i+1); + + i = findNext(endpos, '/', true, true); + if(i != -1) { // there may not be a normal! Eg. "1//2" + auto prefix = endpos.prefix(i); + if(!prefix.empty()) { + indices[1] = stoi(arrayToString(prefix)); + endpos = endpos.suffix(i+1); } } - std::string str = pos.prefix(i).data(); - return std::make_tuple(str, pos.suffix(i+1)); -} -std::tuple> asFloat(const ArrayView& pos) { + i = findNext(endpos, ' ', true, true); // texture coordinates are not terminated by '/', but ' ' or newline + if(i == -1) + i = findNext(endpos, '\r', true, true); + if(i == -1) + i = findNext(endpos, '\n', true, true); + + if(i != -1) { // there may not be a texCoord! Eg. "1//" + auto prefix = endpos.prefix(i); + if(!prefix.empty()) { + indices[2] = stoi(arrayToString(prefix)); + endpos = endpos.suffix(i+1); + } + } + return std::make_tuple(indices, endpos); } -template void reindex(const std::vector& indices, std::vector& data) { - /* Check that indices are in range */ - for(UnsignedInt i: indices) if(i >= data.size()) { - Error() << "Trade::ObjImporter::mesh3D(): index out of range"; - throw 0; +// get string of content until next ' ' and also return ArrayView for data after the word. +std::tuple> nextWord(const ArrayView& pos) { + int i = 0; + for(; i < pos.size(); ++i) { + if(pos[i] == ' ' || pos[i] == '\r' || pos[i] == '\n') { + break; + } } - - data = MeshTools::duplicate(indices, data); + std::string str = arrayToString(pos.prefix(i)); + return std::make_tuple(str, pos.suffix(i+1)); } } @@ -135,29 +217,30 @@ bool ObjImporter::doIsOpened() const { return _in; } void ObjImporter::doClose() { _in = nullptr; } +void ObjImporter::doOpenFile(const std::string& filename) { + _fileRoot = filename.substr(0, filename.find_last_of('/')+1); + AbstractImporter::doOpenFile(filename); +} + void ObjImporter::doOpenData(Containers::ArrayView data) { _in = Containers::Array{data.size()}; std::copy(data.begin(), data.end(), _in.begin()); _state.reset(new ImporterState); - parseMeshNames(); + parse(); } -void ObjImporter::parseMeshNames() { - /* First mesh starts at the beginning, its indices start from 1. The end - offset will be updated to proper value later. */ - UnsignedInt positionIndexOffset = 1; - UnsignedInt normalIndexOffset = 1; - UnsignedInt textureCoordinateIndexOffset = 1; - _state->meshes.emplace_back(0, 0, positionIndexOffset, normalIndexOffset, textureCoordinateIndexOffset); +void ObjImporter::parse() { + ArrayView pos = _in; + ObjMesh* mesh = new ObjMesh; - _state->meshNames.emplace_back(); + int currentMaterialIndex = -1; - ArrayView pos = _in; while(!pos.empty()) { /* Comment line */ if(pos[0] == '#') { pos = ignoreLine(pos); + pos = skipWhitespaces(pos); continue; } @@ -165,208 +248,364 @@ void ObjImporter::parseMeshNames() { std::string keyword; std::tie(keyword, pos) = nextWord(pos); - /* Mesh name */ - if(keyword == "o") { - // TODO - continue; - } + pos = skipWhitespaces(pos); /* Vertex position */ if(keyword == "v") { - Float extra{1.0f}; - const Vector3 data = extractFloatData<3>(pos, &extra); - if(!Math::TypeTraits::equals(extra, 1.0f)) { - Error() << "Trade::ObjImporter::mesh3D(): homogeneous coordinates are not supported"; - return Containers::NullOpt; - } + std::string word; + + // All of this code should be abstracted away in a function. + // Currently copy pasted to vt, vn, Ka, Ks, Kd... + std::tie(word, pos) = nextWord(pos); + const float x = std::stof(word); + + std::tie(word, pos) = nextWord(pos); + const float y = std::stof(word); - positions.push_back(data); + std::tie(word, pos) = nextWord(pos); + const float z = std::stof(word); + _state->positions.push_back(Vector3{x, y, z}); /* Texture coordinate */ } else if(keyword == "vt") { - Float extra{0.0f}; - const auto data = extractFloatData<2>(contents, &extra); - if(!Math::TypeTraits::equals(extra, 0.0f)) { - Error() << "Trade::ObjImporter::mesh3D(): 3D texture coordinates are not supported"; - return Containers::NullOpt; - } + std::string word; + + std::tie(word, pos) = nextWord(pos); + const float x = std::stof(word); + + std::tie(word, pos) = nextWord(pos); + const float y = std::stof(word); - if(textureCoordinates.empty()) textureCoordinates.emplace_back(); - textureCoordinates.front().emplace_back(data); + _state->textureCoordinates.push_back(Vector2{x, y}); /* Normal */ } else if(keyword == "vn") { - if(normals.empty()) normals.emplace_back(); - normals.front().emplace_back(extractFloatData<3>(contents)); + std::string word; + + std::tie(word, pos) = nextWord(pos); + const float x = std::stof(word); + + std::tie(word, pos) = nextWord(pos); + const float y = std::stof(word); + + std::tie(word, pos) = nextWord(pos); + const float z = std::stof(word); + + _state->normals.push_back(Vector3{x, y, z}); /* Indices */ - } else if(keyword == "p" || keyword == "l" || keyword == "f") { - const std::vector indexTuples = Utility::String::splitWithoutEmptyParts(contents, ' '); - - /* Points */ - if(keyword == "p") { - /* Check that we don't mix the primitives in one mesh */ - if(primitive && primitive != MeshPrimitive::Points) { - Error() << "Trade::ObjImporter::mesh3D(): mixed primitive" << *primitive << "and" << MeshPrimitive::Points; - return Containers::NullOpt; - } - - /* Check vertex count per primitive */ - if(indexTuples.size() != 1) { - Error() << "Trade::ObjImporter::mesh3D(): wrong index count for point"; - return Containers::NullOpt; - } - - primitive = MeshPrimitive::Points; - - /* Lines */ - } else if(keyword == "l") { - /* Check that we don't mix the primitives in one mesh */ - if(primitive && primitive != MeshPrimitive::Lines) { - Error() << "Trade::ObjImporter::mesh3D(): mixed primitive" << *primitive << "and" << MeshPrimitive::Lines; - return Containers::NullOpt; - } - - /* Check vertex count per primitive */ - if(indexTuples.size() != 2) { - Error() << "Trade::ObjImporter::mesh3D(): wrong index count for line"; - return Containers::NullOpt; - } - - primitive = MeshPrimitive::Lines; - - /* Faces */ - } else if(keyword == "f") { - /* Check that we don't mix the primitives in one mesh */ - if(primitive && primitive != MeshPrimitive::Triangles) { - Error() << "Trade::ObjImporter::mesh3D(): mixed primitive" << *primitive << "and" << MeshPrimitive::Triangles; - return Containers::NullOpt; - } - - /* Check vertex count per primitive */ - if(indexTuples.size() < 3) { - Error() << "Trade::ObjImporter::mesh3D(): wrong index count for triangle"; - return Containers::NullOpt; - } else if(indexTuples.size() != 3) { - Error() << "Trade::ObjImporter::mesh3D(): polygons are not supported"; - return Containers::NullOpt; - } - - primitive = MeshPrimitive::Triangles; - - } else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ - - for(const std::string& indexTuple: indexTuples) { - std::vector indices = Utility::String::split(indexTuple, '/'); - if(indices.size() > 3) { - Error() << "Trade::ObjImporter::mesh3D(): invalid index data"; - return Containers::NullOpt; - } - - /* Position indices */ - positionIndices.push_back(std::stoul(indices[0]) - positionIndexOffset); - - /* Texture coordinates */ - if(indices.size() == 2 || (indices.size() == 3 && !indices[1].empty())) - textureCoordinateIndices.push_back(std::stoul(indices[1]) - textureCoordinateIndexOffset); - - /* Normal indices */ - if(indices.size() == 3) - normalIndices.push_back(std::stoul(indices[2]) - normalIndexOffset); + } else if(keyword == "f") { + // Not handling case that there are more than three vertices! + for(int i = 0; i < 3; ++i) { + std::array indices; + std::tie(indices, pos) = parseVertex(pos); + mesh->indices.push_back(indices); + pos = skipWhitespaces(pos, true); + } + /* Load a material library */ + } else if(keyword == "mtllib") { + std::string word; + pos = skipWhitespaces(pos); + std::tie(word, pos) = nextWord(pos); + + parseMaterialLibrary(word); + + /* Set current material and add a new mesh for it */ + } else if(keyword == "usemtl") { + std::string word; + pos = skipWhitespaces(pos); + std::tie(word, pos) = nextWord(pos); + + int materialIndex = _state->materialIndices[word]; + if(materialIndex != currentMaterialIndex) { + currentMaterialIndex = materialIndex; + /* switching the material here, need to create a new mesh */ + _state->meshMaterials.push_back(materialIndex); + + if(!mesh->indices.empty()) { + _state->meshes.push_back(std::unique_ptr(mesh)); + } /* empty meshes are skipped */ + mesh = new ObjMesh; + } // else: usemtl did not result in material switch, no need to create new mesh + + /* Ignore unsupported keywords, error out on unknown keywords */ + } else { + Warning() << "Trade::ObjImporter::parse(): unknown keyword:" << keyword; + } + + /* Ignore the rest of the line */ + pos = skipWhitespaces(ignoreLine(pos)); + } + + if(!mesh->indices.empty()) { + _state->meshes.push_back(std::unique_ptr(mesh)); + } +} + +void ObjImporter::parseMaterialLibrary(std::string libname) { + std::string filename = _fileRoot + libname; + + /* Open file */ + if(!Utility::Directory::fileExists(filename)) { + Error() << "Trade::AbstractImporter::parseMaterialLibrary(): cannot open file" << filename; + return; + } + + Containers::Array contents = Utility::Directory::read(filename); + + ArrayView pos = contents; + ObjMaterial* mat = nullptr; + + while(!pos.empty()) { + + /* Comment line */ + if(pos[0] == '#') { + pos = ignoreLine(pos); + pos = skipWhitespaces(pos); + continue; + } + + /* Parse the keyword */ + std::string keyword; + std::tie(keyword, pos) = nextWord(pos); + + if(keyword.empty()) { + continue; + } + + pos = skipWhitespaces(pos); + + if(keyword == "newmtl") { + if(mat != nullptr) { + _state->materials.push_back(*mat); + _state->materialIndices.insert( + std::make_pair(mat->name, _state->materials.size()-1)); + delete mat; + } + mat = new ObjMaterial; + std::tie(mat->name, pos) = nextWord(pos); + continue; + } else if (mat == nullptr) { + Error() << "Expected newmtl keyword, got" << keyword; + } + + /* Ambient color */ + if(keyword == "Ka") { + std::string word; + + // again, this code is duplicated alot. + std::tie(word, pos) = nextWord(pos); + const float r = std::stof(word); + + std::tie(word, pos) = nextWord(pos); + const float g = std::stof(word); + + std::tie(word, pos) = nextWord(pos); + const float b = std::stof(word); + + mat->ambient = Color3(r, g, b); + /* Diffuse color */ + } else if(keyword == "Kd") { + std::string word; + + std::tie(word, pos) = nextWord(pos); + const float r = std::stof(word); + + std::tie(word, pos) = nextWord(pos); + const float g = std::stof(word); + + std::tie(word, pos) = nextWord(pos); + const float b = std::stof(word); + + mat->diffuse = Color3(r, g, b); + + /* Specular color */ + } else if(keyword == "Ks") { + std::string word; + + std::tie(word, pos) = nextWord(pos); + const float r = std::stof(word); + + std::tie(word, pos) = nextWord(pos); + const float g = std::stof(word); + + std::tie(word, pos) = nextWord(pos); + const float b = std::stof(word); + + mat->specular = Color3(r, g, b); + + /* Specularity */ + } else if(keyword == "Ns") { + std::string word; + + std::tie(word, pos) = nextWord(pos); + const float f = std::stof(word); + + mat->specularity = f; + /* Ambient texture */ + } else if(keyword == "map_Ka") { + std::string texture; + std::tie(texture, pos) = nextLine(pos); + + // This is also very similar code for every "map_*" + if(_state->textureIndices.find(texture) == _state->textureIndices.end()) { + /* new texture, create it */ + int index = _state->textures.size(); + _state->textures.push_back(texture); + + _state->textureIndices[texture] = index; + mat->ambientTexture = index; + } else { + mat->ambientTexture = _state->textureIndices[texture]; + } + + /* Diffuse texture */ + } else if(keyword == "map_Kd") { + std::string texture; + std::tie(texture, pos) = nextLine(pos); + + if(_state->textureIndices.find(texture) == _state->textureIndices.end()) { + /* new texture, create it */ + int index = _state->textures.size(); + _state->textures.push_back(texture); + + _state->textureIndices[texture] = index; + mat->diffuseTexture = index; + } else { + mat->diffuseTexture = _state->textureIndices[texture]; + } + + /* Specular texture */ + } else if(keyword == "map_Ks") { + std::string texture; + std::tie(texture, pos) = nextLine(pos); + + if(_state->textureIndices.find(texture) == _state->textureIndices.end()) { + /* new texture, create it */ + int index = _state->textures.size(); + _state->textures.push_back(texture); + + _state->textureIndices[texture] = index; + mat->specularTexture = index; + } else { + mat->specularTexture = _state->textureIndices[texture]; } /* Ignore unsupported keywords, error out on unknown keywords */ - } else if(![&keyword](){ - /* Using lambda to emulate for-else construct like in Python */ - for(const std::string expected: {"mtllib", "usemtl", "g", "s"}) - if(keyword == expected) return true; - return false; - }()) { - Error() << "Trade::ObjImporter::mesh3D(): unknown keyword" << keyword; - return Containers::NullOpt; + } else { + Warning() << "Trade::ObjImporter::parseMaterialLibrary(): unknown keyword:" << keyword; } /* Ignore the rest of the line */ - pos = ignoreLine(pos); + pos = skipWhitespaces(ignoreLine(pos)); + } + + // add last currently active material to materials vector. Usually added in "newmtl" keyword handling + if(mat != nullptr) { + _state->materials.push_back(*mat); + _state->materialIndices.insert( + std::make_pair(mat->name, _state->materials.size()-1)); + delete mat; } } UnsignedInt ObjImporter::doMesh3DCount() const { return _state->meshes.size(); } +UnsignedInt ObjImporter::doMaterialCount() const { return _state->meshMaterials.size(); } + + +UnsignedInt ObjImporter::doImage2DCount() const { return _state->textures.size(); } + Int ObjImporter::doMesh3DForName(const std::string& name) { - const auto it = _state->meshesForName.find(name); - return it == _state->meshesForName.end() ? -1 : it->second; + return 0; } std::string ObjImporter::doMesh3DName(UnsignedInt id) { - return _state->meshNames[id]; + return ""; } std::optional ObjImporter::doMesh3D(UnsignedInt id) { - /* Seek the file, set mesh parsing parameters */ - std::streampos begin, end; - UnsignedInt positionIndexOffset, textureCoordinateIndexOffset, normalIndexOffset; - std::tie(begin, end, positionIndexOffset, textureCoordinateIndexOffset, normalIndexOffset) = _state->meshes[id]; - - /* There should be at least indexed position data */ - if(positions.empty() || positionIndices.empty()) { - Error() << "Trade::ObjImporter::mesh3D(): incomplete position data"; - return Containers::NullOpt; + ObjMesh& mesh = *_state->meshes[id]; + + std::vector positions, normals; + std::vector textureCoords; + + Debug() << "Have" << mesh.indices.size() << "vertices"; // + + // resolve indices... probably use combineIndexArrays instead? + positions.reserve(mesh.indices.size()); + normals.reserve(mesh.indices.size()); + textureCoords.reserve(mesh.indices.size()); + + for(std::array& indexArray : mesh.indices) { + positions.push_back(_state->positions[indexArray[0]-1]); + + if(indexArray[1] != -1) // even though this looks like it's handling the case, it's not, this is utterly useless. + // may result in differently sized arrays, which is not valid. + normals.push_back(_state->normals[indexArray[1]-1]); + if(indexArray[2] != -1) + textureCoords.push_back(_state->textureCoordinates[indexArray[2]-1]); } - /* If there are index data, there should be also vertex data (and also the other way) */ - if(normals.empty() != normalIndices.empty()) { - Error() << "Trade::ObjImporter::mesh3D(): incomplete normal data"; - return Containers::NullOpt; + return MeshData3D( + MeshPrimitive::Triangles, + {}, + {positions}, + {normals}, + {textureCoords}); +} + +std::unique_ptr ObjImporter::doMaterial(UnsignedInt id) { + ObjMaterial& objMat = _state->materials[_state->meshMaterials[id]]; + PhongMaterialData::Flags flags; + + if(objMat.ambientTexture != -1) { + flags |= PhongMaterialData::Flag::AmbientTexture; } - if(textureCoordinates.empty() != textureCoordinateIndices.empty()) { - Error() << "Trade::ObjImporter::mesh3D(): incomplete texture coordinate data"; - return Containers::NullOpt; + if(objMat.diffuseTexture != -1) { + flags |= PhongMaterialData::Flag::DiffuseTexture; } - - /* All index arrays should have the same length */ - if(!normalIndices.empty() && normalIndices.size() != positionIndices.size()) { - CORRADE_INTERNAL_ASSERT(normalIndices.size() < positionIndices.size()); - Error() << "Trade::ObjImporter::mesh3D(): some normal indices are missing"; - return Containers::NullOpt; + if(objMat.specularTexture != -1) { + flags |= PhongMaterialData::Flag::SpecularTexture; } - if(!textureCoordinates.empty() && textureCoordinateIndices.size() != positionIndices.size()) { - CORRADE_INTERNAL_ASSERT(textureCoordinateIndices.size() < positionIndices.size()); - Error() << "Trade::ObjImporter::mesh3D(): some texture coordinate indices are missing"; - return Containers::NullOpt; + + PhongMaterialData* mat = new PhongMaterialData{ + flags, + objMat.specularity}; + + if(objMat.ambientTexture == -1) { + mat->ambientColor() = objMat.ambient; + } else { + mat->ambientTexture() = objMat.ambientTexture; } - /* Merge index arrays, if there aren't just the positions */ - std::vector indices; - if(!normalIndices.empty() || !textureCoordinateIndices.empty()) { - std::vector>> arrays; - arrays.reserve(3); - arrays.emplace_back(positionIndices); - if(!normalIndices.empty()) arrays.emplace_back(normalIndices); - if(!textureCoordinateIndices.empty()) arrays.emplace_back(textureCoordinateIndices); - indices = MeshTools::combineIndexArrays(arrays); - - /* Reindex data arrays */ - try { - reindex(positionIndices, positions); - if(!normalIndices.empty()) reindex(normalIndices, normals.front()); - if(!textureCoordinateIndices.empty()) reindex(textureCoordinateIndices, textureCoordinates.front()); - } catch(...) { - /* Error message already printed */ - return Containers::NullOpt; - } + if(objMat.diffuseTexture == -1) { + mat->diffuseColor() = objMat.diffuse; + } else { + mat->diffuseTexture() = objMat.diffuseTexture; + } - /* Otherwise just use the original position index array. Don't forget to - check range */ + if(objMat.specularTexture == -1) { + mat->specularColor() = objMat.specular; } else { - indices = std::move(positionIndices); - for(UnsignedInt i: indices) if(i >= positions.size()) { - Error() << "Trade::ObjImporter::mesh3D(): index out of range"; - return Containers::NullOpt; - } + mat->specularTexture() = objMat.specularTexture; + } + + return std::unique_ptr(mat); +} + +std::optional ObjImporter::doImage2D(UnsignedInt id) { + CORRADE_ASSERT(manager(), "Trade::ObjImporter::image2D(): the plugin must be instantiated with access to plugin manager in order to open image files", {}); + + std::unique_ptr imageImporter = manager()->loadAndInstantiate("TgaImporter"); // probably AnyImageImporter would be the way to go here... + if(!imageImporter->openFile(_fileRoot + _state->textures[id])) { + return std::nullopt; } - return MeshData3D{*primitive, std::move(indices), {std::move(positions)}, std::move(normals), std::move(textureCoordinates), {}, nullptr}; + Debug() << "Loading image2D" << _fileRoot + _state->textures[id]; + + return imageImporter->image2D(0); } + }} diff --git a/src/MagnumPlugins/ObjImporter/ObjImporter.h b/src/MagnumPlugins/ObjImporter/ObjImporter.h index f1c1b1fa4..a3cca3ba1 100644 --- a/src/MagnumPlugins/ObjImporter/ObjImporter.h +++ b/src/MagnumPlugins/ObjImporter/ObjImporter.h @@ -100,10 +100,18 @@ class MAGNUM_OBJIMPORTER_EXPORT ObjImporter: public AbstractImporter { MAGNUM_OBJIMPORTER_LOCAL std::string doMesh3DName(UnsignedInt id) override; MAGNUM_OBJIMPORTER_LOCAL Containers::Optional doMesh3D(UnsignedInt id) override; - MAGNUM_OBJIMPORTER_LOCAL void parseMeshNames(); + MAGNUM_OBJIMPORTER_LOCAL UnsignedInt doMaterialCount() const override; + MAGNUM_OBJIMPORTER_LOCAL std::unique_ptr doMaterial(UnsignedInt id) override; + + MAGNUM_OBJIMPORTER_LOCAL UnsignedInt doImage2DCount() const; + MAGNUM_OBJIMPORTER_LOCAL std::optional doImage2D(UnsignedInt id); + + MAGNUM_OBJIMPORTER_LOCAL void parse(); + MAGNUM_OBJIMPORTER_LOCAL void parseMaterialLibrary(std::string libname); Containers::Array _in; std::unique_ptr _state; + std::string _fileRoot; }; }} diff --git a/src/MagnumPlugins/ObjImporter/Test/Test.cpp b/src/MagnumPlugins/ObjImporter/Test/Test.cpp index ac39dec0d..d82b2afca 100644 --- a/src/MagnumPlugins/ObjImporter/Test/Test.cpp +++ b/src/MagnumPlugins/ObjImporter/Test/Test.cpp @@ -83,6 +83,8 @@ struct ObjImporterTest: TestSuite::Tester { void missingNormalIndices(); void missingTextureCoordinateIndices(); + void multiMaterialObject(); + void wrongTextureCoordinateIndexCount(); void wrongNormalIndexCount(); @@ -134,6 +136,8 @@ ObjImporterTest::ObjImporterTest() { &ObjImporterTest::missingNormalIndices, &ObjImporterTest::missingTextureCoordinateIndices, + &ObjImporterTest::multiMaterialObject, + &ObjImporterTest::wrongTextureCoordinateIndexCount, &ObjImporterTest::wrongNormalIndexCount, @@ -702,6 +706,31 @@ void ObjImporterTest::wrongTextureCoordinateIndexCount() { CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): some texture coordinate indices are missing\n"); } +void ObjImporterTest::multiMaterialObject() { + ObjImporter importer; + CORRADE_VERIFY(importer.openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "multiMaterial.obj"))); + CORRADE_VERIFY(importer.mesh3DCount() == 2); + CORRADE_VERIFY(importer.materialCount() == 2); + + /* Everything should be parsed properly */ + std::optional data = importer.mesh3D(0); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->primitive(), MeshPrimitive::Triangles); + CORRADE_COMPARE(data->positionArrayCount(), 1); + CORRADE_COMPARE(data->positions(0), (std::vector{ + {Vector3(1.72414, 18.9233, -3.20162), + Vector3(2.74428, -0.499733, -3.50576), + Vector3(-1.92235, -0.846268, 2.9722), + Vector3(1.72414, 18.9233, -3.20162), + Vector3(-1.92235, -0.846268, 2.9722), + Vector3(2.43556, 18.8755, 2.23745)} + })); + + data = importer.mesh3D(1); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->primitive(), MeshPrimitive::Triangles); +} + void ObjImporterTest::unsupportedKeyword() { ObjImporter importer; CORRADE_VERIFY(importer.openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "keywords.obj"))); diff --git a/src/MagnumPlugins/ObjImporter/Test/multiMaterial.mtl b/src/MagnumPlugins/ObjImporter/Test/multiMaterial.mtl new file mode 100644 index 000000000..fcb923d86 --- /dev/null +++ b/src/MagnumPlugins/ObjImporter/Test/multiMaterial.mtl @@ -0,0 +1,8 @@ +newmtl mat_0 +Ka 1 1 1 +map_Ka Textures/mat_0.tga + + +newmtl mat_1 +Ka 1 1 1 +map_Ka Textures/mat_1.tga \ No newline at end of file diff --git a/src/MagnumPlugins/ObjImporter/Test/multiMaterial.obj b/src/MagnumPlugins/ObjImporter/Test/multiMaterial.obj new file mode 100644 index 000000000..85b14bcbc --- /dev/null +++ b/src/MagnumPlugins/ObjImporter/Test/multiMaterial.obj @@ -0,0 +1,60 @@ +mtllib multiMaterial.mtl + + + +# Billboard + +# positions +v -2.94249 18.5768 3.27633 +v 1.72414 18.9233 -3.20162 +v 2.74428 -0.499733 -3.50576 +v -1.92235 -0.846268 2.9722 + + +# normals +vn 0.810085 0.0333816 0.585361 +vn 0.810085 0.0333816 0.585361 +vn 0.810085 0.0333816 0.585361 +vn 0.810085 0.0333816 0.585361 + + +# texture coords +vt 0 1 +vt 0 0 +vt 1 0 +vt 1 1 + +usemtl mat_0 + +# faces +f 1/1/1 2/2/2 3/3/3 +f 1/1/1 3/3/3 4/4/4 + + +# Billboard + +# positions +v 2.43556 18.8755 2.23745 +v -3.6539 18.6246 -2.16274 +v -2.63377 -0.798466 -2.46688 +v 3.4557 -0.547535 1.93331 + + +# normals +vn 0.583961 0.0433639 -0.810622 +vn 0.583961 0.0433639 -0.810622 +vn 0.583961 0.0433639 -0.810622 +vn 0.583961 0.0433639 -0.810622 + + +# texture coords +vt 0 1 +vt 0 0 +vt 1 0 +vt 1 1 + +usemtl mat_1 + +# faces +f 5/5/5 6/6/6 7/7/7 +f 5/5/5 7/7/7 8/8/8 From e87960b62386efff1f52b28ec56da6a580b88d2e Mon Sep 17 00:00:00 2001 From: Squareys Date: Tue, 5 Jul 2016 20:07:23 +0200 Subject: [PATCH 3/6] Remove some debugging code. Signed-off-by: Squareys --- src/MagnumPlugins/ObjImporter/ObjImporter.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/MagnumPlugins/ObjImporter/ObjImporter.cpp b/src/MagnumPlugins/ObjImporter/ObjImporter.cpp index fc46ae83a..97b2d0e63 100644 --- a/src/MagnumPlugins/ObjImporter/ObjImporter.cpp +++ b/src/MagnumPlugins/ObjImporter/ObjImporter.cpp @@ -530,8 +530,6 @@ std::optional ObjImporter::doMesh3D(UnsignedInt id) { std::vector positions, normals; std::vector textureCoords; - Debug() << "Have" << mesh.indices.size() << "vertices"; // - // resolve indices... probably use combineIndexArrays instead? positions.reserve(mesh.indices.size()); normals.reserve(mesh.indices.size()); @@ -602,8 +600,6 @@ std::optional ObjImporter::doImage2D(UnsignedInt id) { return std::nullopt; } - Debug() << "Loading image2D" << _fileRoot + _state->textures[id]; - return imageImporter->image2D(0); } From 77de8df2bc17e7dfb76858d047bef7184184e4a3 Mon Sep 17 00:00:00 2001 From: Squareys Date: Wed, 7 Jun 2017 17:11:26 +0200 Subject: [PATCH 4/6] WIP Signed-off-by: Squareys --- src/MagnumPlugins/ObjImporter/ObjImporter.cpp | 841 ++++++++++++------ src/MagnumPlugins/ObjImporter/ObjImporter.h | 7 +- src/MagnumPlugins/ObjImporter/Test/Test.cpp | 80 +- .../ObjImporter/Test/moreMeshes.obj | 4 +- .../ObjImporter/Test/multiMaterial.obj | 18 +- .../ObjImporter/Test/normals.obj | 8 +- .../Test/textureCoordinatesNormals.obj | 9 +- .../ObjImporter/Test/unnamedFirstMesh.obj | 2 + 8 files changed, 664 insertions(+), 305 deletions(-) diff --git a/src/MagnumPlugins/ObjImporter/ObjImporter.cpp b/src/MagnumPlugins/ObjImporter/ObjImporter.cpp index 97b2d0e63..ff7dede1c 100644 --- a/src/MagnumPlugins/ObjImporter/ObjImporter.cpp +++ b/src/MagnumPlugins/ObjImporter/ObjImporter.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -40,79 +41,166 @@ #include "Magnum/Math/Vector3.h" #include "Magnum/Math/Color.h" #include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshObjectData3D.h" #include "Magnum/Trade/ImageData.h" #include "Magnum/Trade/PhongMaterialData.h" #include "MagnumPlugins/TgaImporter/TgaImporter.h" -#if defined(CORRADE_TARGET_NACL_NEWLIB) || defined(CORRADE_TARGET_ANDROID) -#include -#endif - using namespace Corrade::Containers; namespace Magnum { namespace Trade { -// Not using PhongMaterialData, since we may want to set color and texture to -// later decide which flags we set. We do not know whether we have a texture -// or color beforehand. +/* Not using PhongMaterialData, since we may want to set color and texture to + * later decide which flags we set. We do not know whether we have a texture + * or color beforehand. */ struct ObjMaterial { std::string name; Color3 ambient; Color3 diffuse; Color3 specular; Float specularity; - UnsignedInt ambientTexture = -1; - UnsignedInt diffuseTexture = -1; - UnsignedInt specularTexture = -1; + Int ambientTexture = -1; + Int diffuseTexture = -1; + Int specularTexture = -1; +}; + +struct ObjObject; +struct ObjGroup; +struct ObjMesh; + +struct ObjMeshData { + ObjGroup& group; + int materialId; /* from 'usemtl' keyword */ + + ObjMesh* points = nullptr; + ObjMesh* lines = nullptr; + ObjMesh* faces = nullptr; + + ObjMeshData()=delete; + ObjMeshData(ObjGroup& g, int matId): group(g), materialId(matId) {} }; -// Semantic object for a set of indices belonging to a mesh. +struct ObjGroup { + ObjObject& object; + std::string name; /* from 'g' keyword */ + + std::vector> meshes; + std::unordered_map meshPerMaterial; + + ObjGroup()=delete; + ObjGroup(ObjObject& o): object(o) {} + + /* Create or get mesh for given material id */ + ObjMeshData& meshDataForMaterial(int materialId) { + if(meshPerMaterial.find(materialId) != meshPerMaterial.end()) { + return *meshes[meshPerMaterial[materialId]]; + } else { + meshPerMaterial[materialId] = meshes.size(); + meshes.emplace_back(new ObjMeshData{*this, materialId}); + ObjMeshData& mesh = *meshes.back(); + return mesh; + } + } +}; + +struct ObjObject { + std::string name; /* from 'o' keyword */ + std::vector> groups; + + ObjObject(): name{""} {} + ObjObject(ArrayView name): name{name.data(), name.size()} {} +}; + +/* An intermediate object representing a mesh and it's properties as well as + * where to find the data associated to it */ struct ObjMesh { - std::vector> indices; + ObjMeshData& data; /* parent object containing data shared meshes for different primitives */ + MeshPrimitive primitive; + + /* Sections of the file belonging to this mesh */ + std::vector> sections; + + int minPrimitives = 0; /* For smarter vector memory allocation later */ + + ObjMesh(ObjMeshData& d, MeshPrimitive p): data(d), primitive(p) {} + + std::string name() { + std::string name = data.group.object.name; + + if(!data.group.name.empty()) { + name += ":" + data.group.name; + } + + if(data.materialId != -1) { + name += "$" + std::to_string(data.materialId); + } + + const int numPrimitiveTypes = + ((data.points) ? 1 : 0) + + ((data.lines) ? 1 : 0) + + ((data.faces) ? 1 : 0); + if(numPrimitiveTypes > 1) { + std::ostringstream out; + Debug(&out) << primitive; + name += "%" + out.str(); + } + + return name; + } }; -// Kinda misleading: *Not* similar to the OpenGexImporter Importer State. -// This just holds all the members of the importer to 1. avoid huge includes -// in the header and 2. not have to switch file when adding new ones (I needed -// to be done quickly ;) ) + +/* The state of the imported generated by openData() */ struct ImporterState { - std::vector positions; - std::vector textureCoordinates; - std::vector normals; std::vector materials; - std::vector meshMaterials; - std::unordered_map materialIndices; - std::vector> meshes; + std::unordered_map materialIds; + std::vector textures; - std::unordered_map textureIndices; + std::unordered_map textureIds; + + std::vector> objects; + + std::vector> meshes; + std::unordered_map meshIds; + + std::vector meshlessObjects; + std::unordered_map meshlessObjectIds; + + std::vector positions; + std::vector texCoords; + std::vector normals; }; namespace { -// entire contents of data in a std::string -std::string arrayToString(const Containers::ArrayView data) { - /* Propagate errors */ - if(!data) return ""; - - if(data.empty() ) { - return ""; +int strToInt(const ArrayView str) { + char* err; + const int i = int(strtol(str.data(), &err, 10)); + if(err == nullptr) { + Error() << "Trade::ObjImporter::mesh3D(): error while converting numeric data"; + return 0; } - std::string out; + return i; +} - for(const char* i = data; i != data.end(); ++i) { - const char c = *i; - out += c; +float strToFloat(const ArrayView str) { + char* err; + const float f = strtof(str.data(), &err); + if(err == nullptr) { + Error() << "Trade::ObjImporter::mesh3D(): error while converting numeric data"; + return 0; } - return out; + return f; } // find first index of c in pos, cancel search at newline or whitespace if flag set. -int findNext(const ArrayView& pos, char c, bool termByNewline=false, bool termByWhitespace=false) { - for(int i = 0; i < pos.size(); ++i) { +int findNext(const ArrayView pos, char c, bool termByNewline=false, bool termByWhitespace=false) { + const int size = pos.size(); + for(int i = 0; i < size; ++i) { if(pos[i] == c) { return i; } @@ -125,56 +213,116 @@ int findNext(const ArrayView& pos, char c, bool termByNewline=false, bool } // find next non-whitespace char and return suffix at that point. -// param strict if "true", '\n' and '\r' are not considered whitespace -ArrayView skipWhitespaces(const ArrayView& pos, bool strict=false) { - for(int i = 0; i < pos.size(); ++i) { +// param newlineIsWhitespace if "true", '\n' and '\r' are skipped also +ArrayView skipWhitespaces(const ArrayView pos, bool newlineIsWhitespace=true) { + const int size = pos.size(); + for(int i = 0; i < size; ++i) { const char c = pos[i]; - if(c != ' ' && c != '\t' && (strict || (c != '\n' && c != '\r'))) { + if(c != ' ' && c != '\t' && c != '\0' && (!newlineIsWhitespace || (c != '\n' && c != '\r'))) { return pos.suffix(i); } } - return ArrayView{}; + return {}; } -// returns suffix from after next '\n' -ArrayView ignoreLine(const ArrayView& pos) { +// returns suffix after next '\n' +ArrayView ignoreLine(const ArrayView pos) { return pos.suffix(findNext(pos, '\n') + 1); } // same as ignoreLine, but also returns the content "ignored". // Different description: Get string until next '\n' and return ArrayView of data // after it. -std::tuple> nextLine(const ArrayView& pos) { +std::tuple, ArrayView> nextLine(const ArrayView& pos) { int i = Math::min(findNext(pos, '\n'), findNext(pos, '\r')); - if(i == -1) { + if(i == -1) { i = pos.size(); - std::string str = arrayToString(pos.prefix(i)); - return std::make_tuple(str, pos.suffix(i)); + return std::make_tuple(pos.prefix(i), pos.suffix(i)); + } + return std::make_tuple(pos.prefix(i), pos.suffix(i+1)); +} + +bool atEndOfLine(const ArrayView pos) { + int i = 0; + while(pos[i] == ' ') ++i; + + return (pos[i] == '\n' || pos[i] == '\r'); +} + +// get string of content until next ' ' and also return ArrayView for data after the word. +std::tuple, ArrayView> nextWord(const ArrayView pos) { + int i = 0; + const int size = pos.size(); + for(; i < size; ++i) { + if(pos[i] == ' ' || pos[i] == '\r' || pos[i] == '\n' || pos[i] == '\0') { + break; + } } - std::string str = arrayToString(pos.prefix(i)); - return std::make_tuple(str, pos.suffix(i+1)); + return std::make_tuple(pos.prefix(i), pos.suffix(i)); +} + +std::tuple, ArrayView> parseLine(const ArrayView pos) { + std::array indices{0, 0}; + ArrayView endpos{}; + + int i = findNext(pos, '/', true, true); + if(i == -1) { + /* v1 v2 rather than v1/t1 v2/t2 or v1/ v2/ */ + ArrayView word; + std::tie(word, endpos) = nextWord(pos); + indices[0] = strToInt(word); + return std::make_tuple(indices, endpos); + } + + indices[0] = strToInt(pos.prefix(i)); + endpos = pos.suffix(i+1); + + i = findNext(endpos, ' ', true, true); /* Texture coordinates are not terminated by '/', but ' ' or newline */ + if(i == -1) + i = findNext(endpos, '\r', true, true); + if(i == -1) + i = findNext(endpos, '\n', true, true); + + if(i != -1) { /* There may not be a texCoord! Eg. "1/" */ + auto prefix = endpos.prefix(i); + if(!prefix.empty()) { + indices[1] = strToInt(prefix); + endpos = endpos.suffix(i); + } + } + + return std::make_tuple(indices, endpos); } // Parse a "v/n/t" string to indices // Warning: I'm not handing cases like "v/n", where the normal is not terminated by '/', but ' '! // Same for "v", where even the normal is omitted. -std::tuple, ArrayView> parseVertex(const ArrayView& pos) { - std::array indices{-1, -1, -1}; +std::tuple, ArrayView> parseVertex(const ArrayView pos) { + std::array indices{0, 0, 0}; + ArrayView endpos{}; + + int i = findNext(pos, '/', true, true); + if(i == -1) { + /* v1 v2 rather than v1/t1 v2/t2 or v1/ v2/ */ + ArrayView word; + std::tie(word, endpos) = nextWord(pos); + indices[0] = strToInt(word); + return std::make_tuple(indices, endpos); + } - int i = findNext(pos, '/'); - indices[0] = stoi(arrayToString(pos.prefix(i))); - auto endpos = pos.suffix(i+1); + indices[0] = strToInt(pos.prefix(i)); + endpos = pos.suffix(i+1); i = findNext(endpos, '/', true, true); - if(i != -1) { // there may not be a normal! Eg. "1//2" + if(i != -1) { /* there may not be a normal! Eg. "1//2", in which case the indices of the / are 1 apart */ auto prefix = endpos.prefix(i); if(!prefix.empty()) { - indices[1] = stoi(arrayToString(prefix)); - endpos = endpos.suffix(i+1); + indices[1] = strToInt(prefix); } + endpos = endpos.suffix(i+1); } - i = findNext(endpos, ' ', true, true); // texture coordinates are not terminated by '/', but ' ' or newline + i = findNext(endpos, ' ', true, true); /* texture coordinates are not terminated by '/', but ' ' or newline */ if(i == -1) i = findNext(endpos, '\r', true, true); if(i == -1) @@ -183,24 +331,35 @@ std::tuple, ArrayView> parseVertex(const ArrayView> nextWord(const ArrayView& pos) { - int i = 0; - for(; i < pos.size(); ++i) { - if(pos[i] == ' ' || pos[i] == '\r' || pos[i] == '\n') { - break; - } +template +ArrayView getVector(ArrayView pos, Math::Vector& v) { + ArrayView word; + + for(int i = 0; i < int(D); ++i) { + pos = skipWhitespaces(pos); + std::tie(word, pos) = nextWord(pos); + v[i] = strToFloat(word); + } + + return pos; +} + +template std::vector reindex(const std::vector& indices, std::vector& data) { + /* Check that indices are in range */ + for(UnsignedInt i: indices) if(i >= data.size()) { + Error() << "Trade::ObjImporter::mesh3D(): index out of range"; + throw 0; } - std::string str = arrayToString(pos.prefix(i)); - return std::make_tuple(str, pos.suffix(i+1)); + + return MeshTools::duplicate(indices, data); } } @@ -230,81 +389,185 @@ void ObjImporter::doOpenData(Containers::ArrayView data) { } void ObjImporter::parse() { - ArrayView pos = _in; - ObjMesh* mesh = new ObjMesh; + ArrayView line = _in; /* points to beginning of current line */ + ArrayView pos = _in; /* points to current character in line */ + + ObjObject* object = nullptr; + ObjGroup* group = nullptr; + ObjMeshData* meshData = nullptr; + + ArrayView section{nullptr}; + int minSectionPrimitives = 0; + char sectionPrimitive = '?'; + + /* Set index 0 of data to default value */ + _state->positions.emplace_back(); + _state->normals.emplace_back(); // TODO Default normal? + _state->texCoords.emplace_back(); + + /* Create 'object' if not created by 'o' keyword */ + auto ensureObject = [&]{ + if(object == nullptr) { + // TODO: C++ 17 + _state->objects.emplace_back(new ObjObject); + object = _state->objects.back().get(); + } + }; + + /* Create 'group' if not created by 'g' keyword */ + auto ensureGroup = [&]{ + if(group == nullptr) { + ensureObject(); + + // TODO: C++ 17 + object->groups.emplace_back(new ObjGroup{*object}); + group = object->groups.back().get(); + } + }; + + /* Create 'meshData' if not created by 'usemtl' keyword */ + auto ensureMeshData = [&](){ + if(meshData == nullptr) { + ensureGroup(); + meshData = &group->meshDataForMaterial(-1); + } + }; + + /* Close a section and add it to the current meshData */ + auto finishSection = [&]{ + if(section.data() == nullptr) { + /* No open section */ + return; + } + + ensureMeshData(); + + ObjMesh* mesh; + if(sectionPrimitive == 'p') { + if(!meshData->points) { + // TODO: C++ 17 + _state->meshes.emplace_back(new ObjMesh{*meshData, MeshPrimitive::Points}); + meshData->points = _state->meshes.back().get(); + } + mesh = meshData->points; + } else if(sectionPrimitive == 'l') { + if(!meshData->lines) { + // TODO: C++ 17 + _state->meshes.emplace_back(new ObjMesh{*meshData, MeshPrimitive::Lines}); + meshData->lines = _state->meshes.back().get(); + } + mesh = meshData->lines; + } else { /* sectionPrimitive == 'f' */ + if(!meshData->faces) { + // TODO: C++ 17 + _state->meshes.emplace_back(new ObjMesh{*meshData, MeshPrimitive::Triangles}); + meshData->faces = _state->meshes.back().get(); + } + mesh = meshData->faces; + } + if(line.data() == nullptr) { + /* Usually for the last line */ + mesh->sections.push_back(section); + } else { + mesh->sections.emplace_back(section.data(), size_t(line.data()-section.data())); + } + mesh->minPrimitives += minSectionPrimitives; - int currentMaterialIndex = -1; + section = {nullptr}; + minSectionPrimitives = 0; + }; + + auto finishObject = [&]{ + if(object && object->groups.empty()) { + /* Create dummy mesh for this object so that it gets loaded as ObjectData */ + _state->meshlessObjects.push_back(object->name); + } + }; while(!pos.empty()) { /* Comment line */ if(pos[0] == '#') { - pos = ignoreLine(pos); - pos = skipWhitespaces(pos); + finishSection(); + line = pos = skipWhitespaces(ignoreLine(pos)); continue; } /* Parse the keyword */ std::string keyword; - std::tie(keyword, pos) = nextWord(pos); + ArrayView word; + std::tie(word, pos) = nextWord(pos); + keyword = std::string(word.data(), word.size()); pos = skipWhitespaces(pos); + bool sectionEnd = true; /* Vertex position */ if(keyword == "v") { - std::string word; - - // All of this code should be abstracted away in a function. - // Currently copy pasted to vt, vn, Ka, Ks, Kd... - std::tie(word, pos) = nextWord(pos); - const float x = std::stof(word); + // TODO C++17: Could be one slick line with emplace_back ref return. + Vector3 v; + pos = getVector<3>(pos, v); + _state->positions.push_back(v); + /* Texture coordinate */ + } else if(keyword == "vt") { + // TODO C++17: Could be one slick line with emplace_back ref return. + Vector2 tc; + pos = getVector<2>(pos, tc); + _state->texCoords.push_back(tc); - std::tie(word, pos) = nextWord(pos); - const float y = std::stof(word); + /* Normal */ + } else if(keyword == "vn") { + // TODO C++17: Could be one slick line with emplace_back ref return. + Vector3 n; + pos = getVector<3>(pos, n); + _state->normals.push_back(n); - std::tie(word, pos) = nextWord(pos); - const float z = std::stof(word); + /* Indices */ + } else if(keyword == "f" || keyword == "l" || keyword == "p") { + sectionEnd = false; - _state->positions.push_back(Vector3{x, y, z}); - /* Texture coordinate */ - } else if(keyword == "vt") { - std::string word; + if(sectionPrimitive != keyword[0]) { + /* Create new section, mixed primitives! */ + finishSection(); + } - std::tie(word, pos) = nextWord(pos); - const float x = std::stof(word); + if(section.data() == nullptr) { + /* Create new section */ + section = line; + sectionPrimitive = keyword[0]; + } - std::tie(word, pos) = nextWord(pos); - const float y = std::stof(word); + ++minSectionPrimitives; - _state->textureCoordinates.push_back(Vector2{x, y}); + /* Object name */ + } else if(keyword == "o") { + finishSection(); + finishObject(); - /* Normal */ - } else if(keyword == "vn") { - std::string word; + ArrayView name; + std::tie(name, pos) = nextWord(pos); - std::tie(word, pos) = nextWord(pos); - const float x = std::stof(word); + _state->objects.emplace_back(new ObjObject{name}); + object = _state->objects.back().get(); + group = nullptr; + meshData = nullptr; - std::tie(word, pos) = nextWord(pos); - const float y = std::stof(word); + /* Object group */ + } else if(keyword == "g") { + //TODO: Handle geometry shared by multiple groups, e.g. g group1 group2 + ensureObject(); + object->groups.emplace_back(new ObjGroup{*object}); + group = object->groups.back().get(); - std::tie(word, pos) = nextWord(pos); - const float z = std::stof(word); + meshData = nullptr; - _state->normals.push_back(Vector3{x, y, z}); + ArrayView name; + std::tie(name, pos) = nextWord(pos); + group->name = std::string{name.data(), name.size()}; - /* Indices */ - } else if(keyword == "f") { - // Not handling case that there are more than three vertices! - for(int i = 0; i < 3; ++i) { - std::array indices; - std::tie(indices, pos) = parseVertex(pos); - mesh->indices.push_back(indices); - pos = skipWhitespaces(pos, true); - } /* Load a material library */ } else if(keyword == "mtllib") { - std::string word; + ArrayView word; pos = skipWhitespaces(pos); std::tie(word, pos) = nextWord(pos); @@ -312,20 +575,16 @@ void ObjImporter::parse() { /* Set current material and add a new mesh for it */ } else if(keyword == "usemtl") { - std::string word; - pos = skipWhitespaces(pos); + ArrayView word; + pos = skipWhitespaces(pos); std::tie(word, pos) = nextWord(pos); - int materialIndex = _state->materialIndices[word]; - if(materialIndex != currentMaterialIndex) { - currentMaterialIndex = materialIndex; - /* switching the material here, need to create a new mesh */ - _state->meshMaterials.push_back(materialIndex); - - if(!mesh->indices.empty()) { - _state->meshes.push_back(std::unique_ptr(mesh)); - } /* empty meshes are skipped */ - mesh = new ObjMesh; + const int materialIndex = _state->materialIds[std::string{word.data(), word.size()}]; + if(meshData == nullptr || materialIndex != meshData->materialId) { + /* Switching the material here, need to create a new mesh */ + //TODO C++17 + ensureGroup(); + meshData = &group->meshDataForMaterial(materialIndex); } // else: usemtl did not result in material switch, no need to create new mesh /* Ignore unsupported keywords, error out on unknown keywords */ @@ -333,17 +592,25 @@ void ObjImporter::parse() { Warning() << "Trade::ObjImporter::parse(): unknown keyword:" << keyword; } + if(sectionEnd) { + finishSection(); + } + /* Ignore the rest of the line */ - pos = skipWhitespaces(ignoreLine(pos)); + line = pos = skipWhitespaces(ignoreLine(pos)); } - if(!mesh->indices.empty()) { - _state->meshes.push_back(std::unique_ptr(mesh)); + finishSection(); + finishObject(); + + int i = _state->meshes.size(); + for(auto name : _state->meshlessObjects) { + _state->meshlessObjectIds[name] = i++; } } -void ObjImporter::parseMaterialLibrary(std::string libname) { - std::string filename = _fileRoot + libname; +void ObjImporter::parseMaterialLibrary(const ArrayView libname) { + std::string filename = _fileRoot + std::string(libname.data(), libname.size()); /* Open file */ if(!Utility::Directory::fileExists(filename)) { @@ -352,8 +619,8 @@ void ObjImporter::parseMaterialLibrary(std::string libname) { } Containers::Array contents = Utility::Directory::read(filename); + ArrayView pos = contents; /* points to current character in line */ - ArrayView pos = contents; ObjMaterial* mat = nullptr; while(!pos.empty()) { @@ -366,24 +633,25 @@ void ObjImporter::parseMaterialLibrary(std::string libname) { } /* Parse the keyword */ - std::string keyword; - std::tie(keyword, pos) = nextWord(pos); + ArrayView word; + std::tie(word, pos) = nextWord(pos); + std::string keyword{word.data(), word.size()}; if(keyword.empty()) { + pos = skipWhitespaces(pos); continue; } pos = skipWhitespaces(pos); if(keyword == "newmtl") { - if(mat != nullptr) { - _state->materials.push_back(*mat); - _state->materialIndices.insert( - std::make_pair(mat->name, _state->materials.size()-1)); - delete mat; - } - mat = new ObjMaterial; - std::tie(mat->name, pos) = nextWord(pos); + std::tie(word, pos) = nextWord(pos); + + _state->materials.emplace_back(); + mat = &_state->materials.back(); + mat->name = std::string{word.data(), word.size()}; + + _state->materialIds[mat->name] = _state->materials.size()-1; continue; } else if (mat == nullptr) { Error() << "Expected newmtl keyword, got" << keyword; @@ -391,104 +659,66 @@ void ObjImporter::parseMaterialLibrary(std::string libname) { /* Ambient color */ if(keyword == "Ka") { - std::string word; - - // again, this code is duplicated alot. - std::tie(word, pos) = nextWord(pos); - const float r = std::stof(word); - - std::tie(word, pos) = nextWord(pos); - const float g = std::stof(word); + ArrayView word; - std::tie(word, pos) = nextWord(pos); - const float b = std::stof(word); + for(int i : {0, 1, 2}) { + std::tie(word, pos) = nextWord(pos); + mat->ambient[i] = strToFloat(word); + } - mat->ambient = Color3(r, g, b); /* Diffuse color */ } else if(keyword == "Kd") { - std::string word; - - std::tie(word, pos) = nextWord(pos); - const float r = std::stof(word); - - std::tie(word, pos) = nextWord(pos); - const float g = std::stof(word); + ArrayView word; - std::tie(word, pos) = nextWord(pos); - const float b = std::stof(word); - - mat->diffuse = Color3(r, g, b); + for(int i : {0, 1, 2}) { + std::tie(word, pos) = nextWord(pos); + mat->diffuse[i] = strToFloat(word); + } /* Specular color */ } else if(keyword == "Ks") { - std::string word; + ArrayView word; - std::tie(word, pos) = nextWord(pos); - const float r = std::stof(word); - - std::tie(word, pos) = nextWord(pos); - const float g = std::stof(word); - - std::tie(word, pos) = nextWord(pos); - const float b = std::stof(word); - - mat->specular = Color3(r, g, b); + for(int i : {0, 1, 2}) { + std::tie(word, pos) = nextWord(pos); + mat->specular[i] = strToFloat(word); + } /* Specularity */ } else if(keyword == "Ns") { - std::string word; + ArrayView word; std::tie(word, pos) = nextWord(pos); - const float f = std::stof(word); + const float f = strToFloat(word); mat->specularity = f; /* Ambient texture */ - } else if(keyword == "map_Ka") { - std::string texture; - std::tie(texture, pos) = nextLine(pos); + } else if(keyword.substr(0, 4) == "map_") { + ArrayView line; + std::tie(line, pos) = nextLine(pos); + std::string texture{line.data(), line.size()}; - // This is also very similar code for every "map_*" - if(_state->textureIndices.find(texture) == _state->textureIndices.end()) { + int textureId = -1; + if(_state->textureIds.find(texture) == _state->textureIds.end()) { /* new texture, create it */ int index = _state->textures.size(); _state->textures.push_back(texture); - _state->textureIndices[texture] = index; - mat->ambientTexture = index; + _state->textureIds[texture] = index; + textureId = index; } else { - mat->ambientTexture = _state->textureIndices[texture]; + textureId = _state->textureIds[texture]; } - /* Diffuse texture */ - } else if(keyword == "map_Kd") { - std::string texture; - std::tie(texture, pos) = nextLine(pos); - - if(_state->textureIndices.find(texture) == _state->textureIndices.end()) { - /* new texture, create it */ - int index = _state->textures.size(); - _state->textures.push_back(texture); - - _state->textureIndices[texture] = index; - mat->diffuseTexture = index; - } else { - mat->diffuseTexture = _state->textureIndices[texture]; - } - - /* Specular texture */ - } else if(keyword == "map_Ks") { - std::string texture; - std::tie(texture, pos) = nextLine(pos); - - if(_state->textureIndices.find(texture) == _state->textureIndices.end()) { - /* new texture, create it */ - int index = _state->textures.size(); - _state->textures.push_back(texture); - - _state->textureIndices[texture] = index; - mat->specularTexture = index; + auto suffix = keyword.substr(4, 2); + if(suffix == "Kd") { + mat->diffuseTexture = textureId; + } else if(suffix == "Ka") { + mat->ambientTexture = textureId; + } else if(suffix == "Ks") { + mat->specularTexture = textureId; } else { - mat->specularTexture = _state->textureIndices[texture]; + Warning() << "Trade::ObjImporter::parseMaterialLibrary(): unsupported texture type:" << suffix; } /* Ignore unsupported keywords, error out on unknown keywords */ @@ -499,62 +729,171 @@ void ObjImporter::parseMaterialLibrary(std::string libname) { /* Ignore the rest of the line */ pos = skipWhitespaces(ignoreLine(pos)); } - - // add last currently active material to materials vector. Usually added in "newmtl" keyword handling - if(mat != nullptr) { - _state->materials.push_back(*mat); - _state->materialIndices.insert( - std::make_pair(mat->name, _state->materials.size()-1)); - delete mat; - } } UnsignedInt ObjImporter::doMesh3DCount() const { return _state->meshes.size(); } -UnsignedInt ObjImporter::doMaterialCount() const { return _state->meshMaterials.size(); } - +UnsignedInt ObjImporter::doMaterialCount() const { return _state->materials.size(); } UnsignedInt ObjImporter::doImage2DCount() const { return _state->textures.size(); } +UnsignedInt ObjImporter::doObject3DCount() const { return _state->meshes.size() + _state->meshlessObjects.size(); } + +Int ObjImporter::doObject3DForName(const std::string& name) { + auto result = _state->meshlessObjectIds.find(name); + if(result != _state->meshlessObjectIds.end()) { + return result->second; + } + + result = _state->meshIds.find(name); + if(result != _state->meshIds.end()) { + return result->second; + } + + return -1; +} + +std::string ObjImporter::doObject3DName(UnsignedInt id) { + const size_t numMeshes = _state->meshes.size(); + if(id >= numMeshes) { + return _state->meshlessObjects[id - numMeshes]; + } + return _state->meshes[id]->name(); /* Intentional, objects are just meshes + material */ +} + +std::unique_ptr ObjImporter::doObject3D(UnsignedInt id) { + const size_t numMeshes = _state->meshes.size(); + if(id > numMeshes) { + return std::unique_ptr{new ObjectData3D{{}, {}, &_state->meshlessObjects[id - numMeshes]}}; + } + const ObjMesh& mesh = *_state->meshes[id]; + return std::unique_ptr{ + new MeshObjectData3D{{}, {}, id, mesh.data.materialId, _state->meshes[id].get()}}; +} + Int ObjImporter::doMesh3DForName(const std::string& name) { - return 0; + return _state->meshIds[name]; } std::string ObjImporter::doMesh3DName(UnsignedInt id) { - return ""; + return _state->meshes[id]->name(); } std::optional ObjImporter::doMesh3D(UnsignedInt id) { - ObjMesh& mesh = *_state->meshes[id]; - - std::vector positions, normals; - std::vector textureCoords; - - // resolve indices... probably use combineIndexArrays instead? - positions.reserve(mesh.indices.size()); - normals.reserve(mesh.indices.size()); - textureCoords.reserve(mesh.indices.size()); + const ObjMesh& mesh = *_state->meshes[id]; + + const int primitiveSize = (mesh.primitive == MeshPrimitive::Triangles) + ? 3 : ((mesh.primitive == MeshPrimitive::Lines) ? 2 : 1); + + std::vector positionIndices; + std::vector normalIndices; + std::vector textureCoordinateIndices; + + positionIndices.reserve(mesh.minPrimitives*primitiveSize); + if(primitiveSize >= 2) { + /* Only need to allocate texCoords for lines and faces */ + textureCoordinateIndices.reserve(mesh.minPrimitives*primitiveSize); + if(primitiveSize == 3) { + /* Only need to allocate normals for faces */ + normalIndices.reserve(mesh.minPrimitives*primitiveSize); + } + } - for(std::array& indexArray : mesh.indices) { - positions.push_back(_state->positions[indexArray[0]-1]); + // TODO set to true on first non (-1) encounter + bool hasNormals = false; + bool hasTexCoords = false; + + for(auto section : mesh.sections) { + auto pos = skipWhitespaces(section); + + switch(mesh.primitive) { + case MeshPrimitive::Triangles: + while(!pos.empty()) { + CORRADE_ASSERT(pos[0] == 'f' && pos[1] == ' ', "Unexpected primitive keyword for Triangles", {}); + pos = pos.suffix(2); + + for(int i = 0; i < 3; ++i) { + std::array vertex; + std::tie(vertex, pos) = parseVertex(pos); + positionIndices.push_back(vertex[0]); + textureCoordinateIndices.push_back(vertex[1]); + normalIndices.push_back(vertex[2]); + + hasTexCoords = hasTexCoords || (vertex[1] != 0); + hasNormals = hasNormals || (vertex[2] != 0); + + pos = skipWhitespaces(pos, false); + } + pos = skipWhitespaces(ignoreLine(pos)); + } + break; + case MeshPrimitive::Lines: + while(!pos.empty()) { + CORRADE_ASSERT(pos[0] == 'l' && pos[1] == ' ', "Unexpected primitive keyword for Lines", {}); + pos = pos.suffix(2); + + while(!atEndOfLine(pos)) { + std::array line; + std::tie(line, pos) = parseLine(pos); + positionIndices.push_back(line[0]); + textureCoordinateIndices.push_back(line[1]); + + hasTexCoords = hasTexCoords || (line[1] != 0); + + pos = skipWhitespaces(pos, false); + } + pos = skipWhitespaces(ignoreLine(pos)); + } + break; + case MeshPrimitive::Points: + while(!pos.empty()) { + CORRADE_ASSERT(pos[0] == 'p' && pos[1] == ' ', "Unexpected primitive keyword for Points", {}); + pos = pos.suffix(2); + + ArrayView word; + while(!atEndOfLine(pos)) { + std::tie(word, pos) = nextWord(pos); + positionIndices.push_back(strToInt(word)); + pos = skipWhitespaces(pos, false); + } + pos = skipWhitespaces(ignoreLine(pos)); + } + break; + default: + CORRADE_ASSERT_UNREACHABLE(); + } + } - if(indexArray[1] != -1) // even though this looks like it's handling the case, it's not, this is utterly useless. - // may result in differently sized arrays, which is not valid. - normals.push_back(_state->normals[indexArray[1]-1]); - if(indexArray[2] != -1) - textureCoords.push_back(_state->textureCoordinates[indexArray[2]-1]); + /* Merge index arrays, if there aren't just the positions */ + std::vector indices; + + std::vector> positionLayers; + std::vector> normalLayers; + std::vector> texCoordLayers; + + std::vector>> arrays; + arrays.reserve(3); + arrays.emplace_back(positionIndices); + if(hasNormals) arrays.emplace_back(normalIndices); + if(hasTexCoords) arrays.emplace_back(textureCoordinateIndices); + indices = MeshTools::combineIndexArrays(arrays); + + /* Reindex data arrays */ + try { + positionLayers.push_back(reindex(positionIndices, _state->positions)); + if(hasNormals) normalLayers.push_back(reindex(normalIndices, _state->normals)); + if(hasTexCoords) texCoordLayers.push_back(reindex(textureCoordinateIndices, _state->texCoords)); + } catch(...) { + /* Error message already printed */ + return std::nullopt; } - return MeshData3D( - MeshPrimitive::Triangles, - {}, - {positions}, - {normals}, - {textureCoords}); + return MeshData3D(mesh.primitive, std::move(indices), std::move(positionLayers), + std::move(normalLayers), std::move(texCoordLayers), {}, &mesh); } -std::unique_ptr ObjImporter::doMaterial(UnsignedInt id) { - ObjMaterial& objMat = _state->materials[_state->meshMaterials[id]]; +std::unique_ptr ObjImporter::doMaterial(const UnsignedInt id) { + ObjMaterial& objMat = _state->materials[id]; PhongMaterialData::Flags flags; if(objMat.ambientTexture != -1) { diff --git a/src/MagnumPlugins/ObjImporter/ObjImporter.h b/src/MagnumPlugins/ObjImporter/ObjImporter.h index a3cca3ba1..b13d37ced 100644 --- a/src/MagnumPlugins/ObjImporter/ObjImporter.h +++ b/src/MagnumPlugins/ObjImporter/ObjImporter.h @@ -95,6 +95,11 @@ class MAGNUM_OBJIMPORTER_EXPORT ObjImporter: public AbstractImporter { MAGNUM_OBJIMPORTER_LOCAL void doOpenFile(const std::string& filename) override; MAGNUM_OBJIMPORTER_LOCAL void doClose() override; + MAGNUM_OBJIMPORTER_LOCAL UnsignedInt doObject3DCount() const override; + MAGNUM_OBJIMPORTER_LOCAL Int doObject3DForName(const std::string& name) override; + MAGNUM_OBJIMPORTER_LOCAL std::string doObject3DName(UnsignedInt id) override; + MAGNUM_OBJIMPORTER_LOCAL std::unique_ptr doObject3D(UnsignedInt id) override; + MAGNUM_OBJIMPORTER_LOCAL UnsignedInt doMesh3DCount() const override; MAGNUM_OBJIMPORTER_LOCAL Int doMesh3DForName(const std::string& name) override; MAGNUM_OBJIMPORTER_LOCAL std::string doMesh3DName(UnsignedInt id) override; @@ -107,7 +112,7 @@ class MAGNUM_OBJIMPORTER_EXPORT ObjImporter: public AbstractImporter { MAGNUM_OBJIMPORTER_LOCAL std::optional doImage2D(UnsignedInt id); MAGNUM_OBJIMPORTER_LOCAL void parse(); - MAGNUM_OBJIMPORTER_LOCAL void parseMaterialLibrary(std::string libname); + MAGNUM_OBJIMPORTER_LOCAL void parseMaterialLibrary(Containers::ArrayView libname); Containers::Array _in; std::unique_ptr _state; diff --git a/src/MagnumPlugins/ObjImporter/Test/Test.cpp b/src/MagnumPlugins/ObjImporter/Test/Test.cpp index d82b2afca..ad341af8e 100644 --- a/src/MagnumPlugins/ObjImporter/Test/Test.cpp +++ b/src/MagnumPlugins/ObjImporter/Test/Test.cpp @@ -50,7 +50,6 @@ struct ObjImporterTest: TestSuite::Tester { void textureCoordinatesNormals(); void emptyFile(); - void unnamedMesh(); void namedMesh(); void moreMeshes(); void unnamedFirstMesh(); @@ -104,7 +103,6 @@ ObjImporterTest::ObjImporterTest() { &ObjImporterTest::textureCoordinatesNormals, &ObjImporterTest::emptyFile, - &ObjImporterTest::unnamedMesh, &ObjImporterTest::namedMesh, &ObjImporterTest::moreMeshes, &ObjImporterTest::unnamedFirstMesh, @@ -156,11 +154,11 @@ void ObjImporterTest::pointMesh() { CORRADE_COMPARE(data->positionArrayCount(), 1); CORRADE_COMPARE(data->positions(0), (std::vector{ {0.5f, 2.0f, 3.0f}, - {0.0f, 1.5f, 1.0f}, - {2.0f, 3.0f, 5.0f} + {2.0f, 3.0f, 5.0f}, + {0.0f, 1.5f, 1.0f} })); CORRADE_COMPARE(data->indices(), (std::vector{ - 0, 2, 1, 0 + 0, 1, 2, 0 })); } @@ -206,12 +204,32 @@ void ObjImporterTest::triangleMesh() { void ObjImporterTest::mixedPrimitives() { ObjImporter importer; CORRADE_VERIFY(importer.openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "mixedPrimitives.obj"))); - CORRADE_COMPARE(importer.mesh3DCount(), 1); + CORRADE_COMPARE(importer.mesh3DCount(), 2); - std::ostringstream out; - Error redirectError{&out}; - CORRADE_VERIFY(!importer.mesh3D(0)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): mixed primitive MeshPrimitive::Points and MeshPrimitive::Lines\n"); + /* point mesh */ + auto pointData = importer.mesh3D(0); + CORRADE_VERIFY(pointData); + CORRADE_COMPARE(pointData->primitive(), MeshPrimitive::Points); + CORRADE_COMPARE(pointData->positions(0), (std::vector{ + {0.5f, 2.0f, 3.0f}, + {2.0f, 3.0f, 5.0f}, + {0.0f, 1.5f, 1.0f} + })); + CORRADE_COMPARE(pointData->indices(), (std::vector{ + 0, 1, 2 + })); + + auto lineData = importer.mesh3D(1); + CORRADE_VERIFY(lineData); + CORRADE_COMPARE(lineData->primitive(), MeshPrimitive::Lines); + CORRADE_COMPARE(lineData->positions(0), (std::vector{ + {0.5f, 2.0f, 3.0f}, + {0.0f, 1.5f, 1.0f}, + {2.0f, 3.0f, 5.0f} + })); + CORRADE_COMPARE(lineData->indices(), (std::vector{ + 0, 1, 1, 2 + })); } void ObjImporterTest::positionsOnly() { @@ -261,7 +279,7 @@ void ObjImporterTest::normals() { const Containers::Optional data = importer.mesh3D(0); CORRADE_VERIFY(data); - CORRADE_COMPARE(data->primitive(), MeshPrimitive::Lines); + CORRADE_COMPARE(data->primitive(), MeshPrimitive::Triangles); CORRADE_COMPARE(data->positionArrayCount(), 1); CORRADE_VERIFY(!data->hasTextureCoords2D()); CORRADE_COMPARE(data->normalArrayCount(), 1); @@ -278,7 +296,7 @@ void ObjImporterTest::normals() { {0.5f, 1.0f, 0.5f} })); CORRADE_COMPARE(data->indices(), (std::vector{ - 0, 1, 2, 3, 1, 0 + 0, 1, 0, 2, 3, 0, 1, 0, 0 })); } @@ -289,7 +307,7 @@ void ObjImporterTest::textureCoordinatesNormals() { const Containers::Optional data = importer.mesh3D(0); CORRADE_VERIFY(data); - CORRADE_COMPARE(data->primitive(), MeshPrimitive::Lines); + CORRADE_COMPARE(data->primitive(), MeshPrimitive::Triangles); CORRADE_COMPARE(data->positionArrayCount(), 1); CORRADE_COMPARE(data->textureCoords2DArrayCount(), 1); CORRADE_COMPARE(data->normalArrayCount(), 1); @@ -315,30 +333,26 @@ void ObjImporterTest::textureCoordinatesNormals() { {0.5f, 1.0f, 0.5f} })); CORRADE_COMPARE(data->indices(), (std::vector{ - 0, 1, 2, 3, 1, 0, 4, 2 + 0, 1, 2, 3, 1, 0, 4, 2, 2 })); } void ObjImporterTest::emptyFile() { ObjImporter importer; CORRADE_VERIFY(importer.openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "emptyFile.obj"))); - CORRADE_COMPARE(importer.mesh3DCount(), 1); -} -void ObjImporterTest::unnamedMesh() { - ObjImporter importer; - CORRADE_VERIFY(importer.openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "emptyFile.obj"))); - CORRADE_COMPARE(importer.mesh3DCount(), 1); - CORRADE_COMPARE(importer.mesh3DName(0), ""); - CORRADE_COMPARE(importer.mesh3DForName(""), -1); + CORRADE_COMPARE(importer.mesh3DCount(), 0); + CORRADE_COMPARE(importer.object3DCount(), 0); + CORRADE_COMPARE(importer.image3DCount(), 0); } void ObjImporterTest::namedMesh() { ObjImporter importer; CORRADE_VERIFY(importer.openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "namedMesh.obj"))); - CORRADE_COMPARE(importer.mesh3DCount(), 1); - CORRADE_COMPARE(importer.mesh3DName(0), "MyMesh"); - CORRADE_COMPARE(importer.mesh3DForName("MyMesh"), 0); + CORRADE_COMPARE(importer.mesh3DCount(), 0); + CORRADE_COMPARE(importer.object3DCount(), 1); + CORRADE_COMPARE(importer.object3DName(0), "MyMesh"); + CORRADE_COMPARE(importer.object3DForName("MyMesh"), 0); } void ObjImporterTest::moreMeshes() { @@ -357,7 +371,7 @@ void ObjImporterTest::moreMeshes() { {0.0f, 1.5f, 1.0f} })); CORRADE_COMPARE(data->indices(), (std::vector{ - 0, 1 + 0, 1, 0 })); CORRADE_COMPARE(importer.mesh3DName(1), "LineMesh"); @@ -393,13 +407,17 @@ void ObjImporterTest::moreMeshes() { void ObjImporterTest::unnamedFirstMesh() { ObjImporter importer; CORRADE_VERIFY(importer.openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "unnamedFirstMesh.obj"))); - CORRADE_COMPARE(importer.mesh3DCount(), 2); + CORRADE_COMPARE(importer.mesh3DCount(), 1); + CORRADE_COMPARE(importer.object3DCount(), 2); /* Second mesh is empty, hence 2 objects, but 1 mesh */ + + CORRADE_COMPARE(importer.object3DName(0), ""); + CORRADE_COMPARE(importer.object3DForName(""), 0); // TODO: why is this -1? CORRADE_COMPARE(importer.mesh3DName(0), ""); - CORRADE_COMPARE(importer.mesh3DForName(""), -1); + CORRADE_COMPARE(importer.mesh3DForName(""), 0); - CORRADE_COMPARE(importer.mesh3DName(1), "SecondMesh"); - CORRADE_COMPARE(importer.mesh3DForName("SecondMesh"), 1); + CORRADE_COMPARE(importer.object3DName(1), "SecondMesh"); + CORRADE_COMPARE(importer.object3DForName("SecondMesh"), 1); } void ObjImporterTest::wrongFloat() { @@ -410,7 +428,6 @@ void ObjImporterTest::wrongFloat() { std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer.mesh3D(id)); CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): error while converting numeric data\n"); } @@ -745,7 +762,6 @@ void ObjImporterTest::unsupportedKeyword() { CORRADE_COMPARE(data->positions(0), (std::vector{ {0.0f, 1.0f, 2.0f} })); - CORRADE_COMPARE(data->indices(), std::vector{0}); } void ObjImporterTest::unknownKeyword() { diff --git a/src/MagnumPlugins/ObjImporter/Test/moreMeshes.obj b/src/MagnumPlugins/ObjImporter/Test/moreMeshes.obj index e4820d5b9..0a779c0c0 100644 --- a/src/MagnumPlugins/ObjImporter/Test/moreMeshes.obj +++ b/src/MagnumPlugins/ObjImporter/Test/moreMeshes.obj @@ -4,8 +4,8 @@ v 0.5 2 3 v 0 1.5 1 vn 0.5 2 3 vn 0 1.5 1 -p 1//1 -p 2//2 +p 1 2 +p 1 # Lines o LineMesh diff --git a/src/MagnumPlugins/ObjImporter/Test/multiMaterial.obj b/src/MagnumPlugins/ObjImporter/Test/multiMaterial.obj index 85b14bcbc..4da632842 100644 --- a/src/MagnumPlugins/ObjImporter/Test/multiMaterial.obj +++ b/src/MagnumPlugins/ObjImporter/Test/multiMaterial.obj @@ -1,24 +1,22 @@ mtllib multiMaterial.mtl - - # Billboard -# positions +# positions v -2.94249 18.5768 3.27633 v 1.72414 18.9233 -3.20162 v 2.74428 -0.499733 -3.50576 v -1.92235 -0.846268 2.9722 -# normals +# normals vn 0.810085 0.0333816 0.585361 vn 0.810085 0.0333816 0.585361 vn 0.810085 0.0333816 0.585361 vn 0.810085 0.0333816 0.585361 -# texture coords +# texture coords vt 0 1 vt 0 0 vt 1 0 @@ -26,28 +24,28 @@ vt 1 1 usemtl mat_0 -# faces +# faces f 1/1/1 2/2/2 3/3/3 f 1/1/1 3/3/3 4/4/4 # Billboard -# positions +# positions v 2.43556 18.8755 2.23745 v -3.6539 18.6246 -2.16274 v -2.63377 -0.798466 -2.46688 v 3.4557 -0.547535 1.93331 -# normals +# normals vn 0.583961 0.0433639 -0.810622 vn 0.583961 0.0433639 -0.810622 vn 0.583961 0.0433639 -0.810622 vn 0.583961 0.0433639 -0.810622 -# texture coords +# texture coords vt 0 1 vt 0 0 vt 1 0 @@ -55,6 +53,6 @@ vt 1 1 usemtl mat_1 -# faces +# faces f 5/5/5 6/6/6 7/7/7 f 5/5/5 7/7/7 8/8/8 diff --git a/src/MagnumPlugins/ObjImporter/Test/normals.obj b/src/MagnumPlugins/ObjImporter/Test/normals.obj index 15711d5ca..deb4a897f 100644 --- a/src/MagnumPlugins/ObjImporter/Test/normals.obj +++ b/src/MagnumPlugins/ObjImporter/Test/normals.obj @@ -6,7 +6,7 @@ v 0 1.5 1 vn 1 0.5 3.5 vn 0.5 1 0.5 -# Lines -l 1//1 2//1 -l 1//2 2//2 -l 2//1 1//1 +# Triangles +f 1//1 2//1 1//1 +f 1//2 2//2 1//1 +f 2//1 1//1 1//1 diff --git a/src/MagnumPlugins/ObjImporter/Test/textureCoordinatesNormals.obj b/src/MagnumPlugins/ObjImporter/Test/textureCoordinatesNormals.obj index c1750ecb2..3ab1cab91 100644 --- a/src/MagnumPlugins/ObjImporter/Test/textureCoordinatesNormals.obj +++ b/src/MagnumPlugins/ObjImporter/Test/textureCoordinatesNormals.obj @@ -10,8 +10,7 @@ vt 0.5 1 vn 1 0.5 3.5 vn 0.5 1 0.5 -# Lines -l 1/1/1 2/1/2 -l 1/2/2 2/2/1 -l 2/1/2 1/1/1 -l 2/2/2 1/2/2 +# Faces +f 1/1/1 2/1/2 1/2/2 +f 2/2/1 2/1/2 1/1/1 +f 2/2/2 1/2/2 1/2/2 diff --git a/src/MagnumPlugins/ObjImporter/Test/unnamedFirstMesh.obj b/src/MagnumPlugins/ObjImporter/Test/unnamedFirstMesh.obj index 71752c6d9..7131e199b 100644 --- a/src/MagnumPlugins/ObjImporter/Test/unnamedFirstMesh.obj +++ b/src/MagnumPlugins/ObjImporter/Test/unnamedFirstMesh.obj @@ -1,2 +1,4 @@ v 1 2 3 +p 1 o SecondMesh +# empty From 52a1a868b65af13fb3e23e7aec6048ac3371dd85 Mon Sep 17 00:00:00 2001 From: Squareys Date: Sat, 10 Jun 2017 23:36:03 +0200 Subject: [PATCH 5/6] [WIP] Address the "easy" review comments Signed-off-by: Squareys --- src/MagnumPlugins/ObjImporter/ObjImporter.cpp | 309 +++++++++--------- src/MagnumPlugins/ObjImporter/ObjImporter.h | 4 +- src/MagnumPlugins/ObjImporter/Test/Test.cpp | 14 +- .../ObjImporter/Test/multiMaterial.mtl | 3 +- .../ObjImporter/Test/multiMaterial.obj | 1 + 5 files changed, 164 insertions(+), 167 deletions(-) diff --git a/src/MagnumPlugins/ObjImporter/ObjImporter.cpp b/src/MagnumPlugins/ObjImporter/ObjImporter.cpp index ff7dede1c..33ca2bc5b 100644 --- a/src/MagnumPlugins/ObjImporter/ObjImporter.cpp +++ b/src/MagnumPlugins/ObjImporter/ObjImporter.cpp @@ -28,8 +28,7 @@ #include #include #include -#include -#include +#include #include #include #include @@ -72,28 +71,26 @@ struct ObjMesh; struct ObjMeshData { ObjGroup& group; - int materialId; /* from 'usemtl' keyword */ + Int materialId; /* From 'usemtl' keyword */ - ObjMesh* points = nullptr; + ObjMesh* poInts = nullptr; ObjMesh* lines = nullptr; ObjMesh* faces = nullptr; - ObjMeshData()=delete; - ObjMeshData(ObjGroup& g, int matId): group(g), materialId(matId) {} + explicit ObjMeshData(ObjGroup& g, Int matId): group(g), materialId(matId) {} }; struct ObjGroup { ObjObject& object; - std::string name; /* from 'g' keyword */ + std::string name; /* From 'g' keyword */ std::vector> meshes; - std::unordered_map meshPerMaterial; + std::unordered_map meshPerMaterial; - ObjGroup()=delete; - ObjGroup(ObjObject& o): object(o) {} + explicit ObjGroup(ObjObject& o): object(o) {} /* Create or get mesh for given material id */ - ObjMeshData& meshDataForMaterial(int materialId) { + ObjMeshData& meshDataForMaterial(Int materialId) { if(meshPerMaterial.find(materialId) != meshPerMaterial.end()) { return *meshes[meshPerMaterial[materialId]]; } else { @@ -106,25 +103,25 @@ struct ObjGroup { }; struct ObjObject { - std::string name; /* from 'o' keyword */ + std::string name; /* From 'o' keyword */ std::vector> groups; ObjObject(): name{""} {} - ObjObject(ArrayView name): name{name.data(), name.size()} {} + ObjObject(ArrayView name): name{name.data(), name.size()} {} }; -/* An intermediate object representing a mesh and it's properties as well as +/* An Intermediate object representing a mesh and it's properties as well as * where to find the data associated to it */ struct ObjMesh { - ObjMeshData& data; /* parent object containing data shared meshes for different primitives */ + ObjMeshData& data; /* Parent object containing data shared meshes for different primitives */ MeshPrimitive primitive; /* Sections of the file belonging to this mesh */ - std::vector> sections; + std::vector> sections; - int minPrimitives = 0; /* For smarter vector memory allocation later */ + Int minPrimitives = 0; /* For smarter vector memory allocation later */ - ObjMesh(ObjMeshData& d, MeshPrimitive p): data(d), primitive(p) {} + explicit ObjMesh(ObjMeshData& d, MeshPrimitive p): data(d), primitive(p) {} std::string name() { std::string name = data.group.object.name; @@ -137,8 +134,8 @@ struct ObjMesh { name += "$" + std::to_string(data.materialId); } - const int numPrimitiveTypes = - ((data.points) ? 1 : 0) + const Int numPrimitiveTypes = + ((data.poInts) ? 1 : 0) + ((data.lines) ? 1 : 0) + ((data.faces) ? 1 : 0); if(numPrimitiveTypes > 1) { @@ -154,19 +151,22 @@ struct ObjMesh { /* The state of the imported generated by openData() */ struct ImporterState { + Containers::Array in; + std::string fileRoot; + std::vector materials; - std::unordered_map materialIds; + std::unordered_map materialIds; std::vector textures; - std::unordered_map textureIds; + std::unordered_map textureIds; std::vector> objects; std::vector> meshes; - std::unordered_map meshIds; + std::unordered_map meshIds; std::vector meshlessObjects; - std::unordered_map meshlessObjectIds; + std::unordered_map meshlessObjectIds; std::vector positions; std::vector texCoords; @@ -175,9 +175,10 @@ struct ImporterState { namespace { -int strToInt(const ArrayView str) { +/* Returns the given string parsed as Int */ +Int strToInt(const ArrayView str) { char* err; - const int i = int(strtol(str.data(), &err, 10)); + const Int i = Int(strtol(str.data(), &err, 10)); if(err == nullptr) { Error() << "Trade::ObjImporter::mesh3D(): error while converting numeric data"; return 0; @@ -186,9 +187,10 @@ int strToInt(const ArrayView str) { return i; } -float strToFloat(const ArrayView str) { +/* Returns the given string parsed as Float */ +Float strToFloat(const ArrayView str) { char* err; - const float f = strtof(str.data(), &err); + const Float f = strtof(str.data(), &err); if(err == nullptr) { Error() << "Trade::ObjImporter::mesh3D(): error while converting numeric data"; return 0; @@ -197,10 +199,15 @@ float strToFloat(const ArrayView str) { return f; } -// find first index of c in pos, cancel search at newline or whitespace if flag set. -int findNext(const ArrayView pos, char c, bool termByNewline=false, bool termByWhitespace=false) { - const int size = pos.size(); - for(int i = 0; i < size; ++i) { +/* + * Returns the index of the next occurrence of `c` or -1, if it could not be found. + * + * @param termByNewline if `true`, the search will terminate at '\n' and return -1. + * @param termByWhitespace if `true`, the search will terminate at ' ' and return -1. + */ +Int findNext(const ArrayView pos, char c, bool termByNewline=false, bool termByWhitespace=false) { + const Int size = pos.size(); + for(Int i = 0; i < size; ++i) { if(pos[i] == c) { return i; } @@ -212,11 +219,13 @@ int findNext(const ArrayView pos, char c, bool termByNewline=false, bool t return -1; } -// find next non-whitespace char and return suffix at that point. -// param newlineIsWhitespace if "true", '\n' and '\r' are skipped also -ArrayView skipWhitespaces(const ArrayView pos, bool newlineIsWhitespace=true) { - const int size = pos.size(); - for(int i = 0; i < size; ++i) { +/* + * Return suffix beginning at next non-whitespace character + * @param newlineIsWhitespace if `true`, '\n' and '\r' are skipped also + */ +ArrayView skipWhitespaces(const ArrayView pos, bool newlineIsWhitespace=true) { + const Int size = pos.size(); + for(Int i = 0; i < size; ++i) { const char c = pos[i]; if(c != ' ' && c != '\t' && c != '\0' && (!newlineIsWhitespace || (c != '\n' && c != '\r'))) { return pos.suffix(i); @@ -225,53 +234,53 @@ ArrayView skipWhitespaces(const ArrayView pos, bool newlineIsWhitesp return {}; } -// returns suffix after next '\n' -ArrayView ignoreLine(const ArrayView pos) { +/* Returns suffix after the next '\n' or the given ArrayView if no newline could be found */ +ArrayView ignoreLine(const ArrayView pos) { return pos.suffix(findNext(pos, '\n') + 1); } -// same as ignoreLine, but also returns the content "ignored". -// Different description: Get string until next '\n' and return ArrayView of data -// after it. -std::tuple, ArrayView> nextLine(const ArrayView& pos) { - int i = Math::min(findNext(pos, '\n'), findNext(pos, '\r')); +/* Returns a prefix until the next '\n' and the suffix after the newline. */ +std::pair, ArrayView> nextLine(const ArrayView& pos) { + Int i = Math::min(findNext(pos, '\n'), findNext(pos, '\r')); if(i == -1) { i = pos.size(); - return std::make_tuple(pos.prefix(i), pos.suffix(i)); + return {pos.prefix(i), pos.suffix(i)}; } - return std::make_tuple(pos.prefix(i), pos.suffix(i+1)); + return {pos.prefix(i), pos.suffix(i+1)}; } -bool atEndOfLine(const ArrayView pos) { - int i = 0; +/* Returns true if the next non-whitespace character is a newline character */ +bool atEndOfLine(const ArrayView pos) { + Int i = 0; while(pos[i] == ' ') ++i; return (pos[i] == '\n' || pos[i] == '\r'); } -// get string of content until next ' ' and also return ArrayView for data after the word. -std::tuple, ArrayView> nextWord(const ArrayView pos) { - int i = 0; - const int size = pos.size(); +/* Returns the prefix until the next ' ' character and the suffix starting with it */ +std::pair, ArrayView> nextWord(const ArrayView pos) { + Int i = 0; + const Int size = pos.size(); for(; i < size; ++i) { if(pos[i] == ' ' || pos[i] == '\r' || pos[i] == '\n' || pos[i] == '\0') { break; } } - return std::make_tuple(pos.prefix(i), pos.suffix(i)); + return {pos.prefix(i), pos.suffix(i)}; } -std::tuple, ArrayView> parseLine(const ArrayView pos) { - std::array indices{0, 0}; - ArrayView endpos{}; +/* Parse indices for a line, e.g. "1/2" */ +std::pair, ArrayView> parseLine(const ArrayView pos) { + std::array indices{0, 0}; + ArrayView endpos{}; - int i = findNext(pos, '/', true, true); + Int i = findNext(pos, '/', true, true); if(i == -1) { /* v1 v2 rather than v1/t1 v2/t2 or v1/ v2/ */ - ArrayView word; + ArrayView word; std::tie(word, endpos) = nextWord(pos); indices[0] = strToInt(word); - return std::make_tuple(indices, endpos); + return {indices, endpos}; } indices[0] = strToInt(pos.prefix(i)); @@ -291,30 +300,28 @@ std::tuple, ArrayView> parseLine(const ArrayView } } - return std::make_tuple(indices, endpos); + return {indices, endpos}; } -// Parse a "v/n/t" string to indices -// Warning: I'm not handing cases like "v/n", where the normal is not terminated by '/', but ' '! -// Same for "v", where even the normal is omitted. -std::tuple, ArrayView> parseVertex(const ArrayView pos) { - std::array indices{0, 0, 0}; - ArrayView endpos{}; +/* Parse a "v/n/t" string to indices for a face */ +std::pair, ArrayView> parseVertex(const ArrayView pos) { + std::array indices{0, 0, 0}; + ArrayView endpos{}; - int i = findNext(pos, '/', true, true); + Int i = findNext(pos, '/', true, true); if(i == -1) { /* v1 v2 rather than v1/t1 v2/t2 or v1/ v2/ */ - ArrayView word; + ArrayView word; std::tie(word, endpos) = nextWord(pos); indices[0] = strToInt(word); - return std::make_tuple(indices, endpos); + return {indices, endpos}; } indices[0] = strToInt(pos.prefix(i)); endpos = pos.suffix(i+1); i = findNext(endpos, '/', true, true); - if(i != -1) { /* there may not be a normal! Eg. "1//2", in which case the indices of the / are 1 apart */ + if(i != -1) { /* There may not be a normal! Eg. "1//2", in which case the indices of the / are 1 apart */ auto prefix = endpos.prefix(i); if(!prefix.empty()) { indices[1] = strToInt(prefix); @@ -322,13 +329,13 @@ std::tuple, ArrayView> parseVertex(const ArrayView, ArrayView> parseVertex(const ArrayView -ArrayView getVector(ArrayView pos, Math::Vector& v) { - ArrayView word; +template Math::Vector parseVector(ArrayView& pos) { + ArrayView word; + Math::Vector v; - for(int i = 0; i < int(D); ++i) { - pos = skipWhitespaces(pos); - std::tie(word, pos) = nextWord(pos); + for(Int i = 0; i < Int(dimensions); ++i) { + std::tie(word, pos) = nextWord(skipWhitespaces(pos)); v[i] = strToFloat(word); } - return pos; + return v; } -template std::vector reindex(const std::vector& indices, std::vector& data) { +template std::vector reindex(const std::vector& indices, const std::vector& data) { /* Check that indices are in range */ for(UnsignedInt i: indices) if(i >= data.size()) { Error() << "Trade::ObjImporter::mesh3D(): index out of range"; - throw 0; + return {}; } return MeshTools::duplicate(indices, data); @@ -372,32 +378,32 @@ ObjImporter::~ObjImporter() = default; auto ObjImporter::doFeatures() const -> Features { return Feature::OpenData; } -bool ObjImporter::doIsOpened() const { return _in; } +void ObjImporter::doClose() { _state->in = nullptr; } -void ObjImporter::doClose() { _in = nullptr; } +bool ObjImporter::doIsOpened() const { return _state->in; } void ObjImporter::doOpenFile(const std::string& filename) { - _fileRoot = filename.substr(0, filename.find_last_of('/')+1); + _state->fileRoot = Utility::Directory::path(filename); AbstractImporter::doOpenFile(filename); } void ObjImporter::doOpenData(Containers::ArrayView data) { - _in = Containers::Array{data.size()}; - std::copy(data.begin(), data.end(), _in.begin()); + _state->in = Containers::Array{data.size()}; + std::copy(data.begin(), data.end(), _state->in.begin()); _state.reset(new ImporterState); parse(); } void ObjImporter::parse() { - ArrayView line = _in; /* points to beginning of current line */ - ArrayView pos = _in; /* points to current character in line */ + ArrayView line = _state->in; /* Points to beginning of current line */ + ArrayView pos = _state->in; /* Points to current character in line */ ObjObject* object = nullptr; ObjGroup* group = nullptr; ObjMeshData* meshData = nullptr; - ArrayView section{nullptr}; - int minSectionPrimitives = 0; + ArrayView section{nullptr}; + Int minSectionPrimitives = 0; char sectionPrimitive = '?'; /* Set index 0 of data to default value */ @@ -444,12 +450,12 @@ void ObjImporter::parse() { ObjMesh* mesh; if(sectionPrimitive == 'p') { - if(!meshData->points) { + if(!meshData->poInts) { // TODO: C++ 17 _state->meshes.emplace_back(new ObjMesh{*meshData, MeshPrimitive::Points}); - meshData->points = _state->meshes.back().get(); + meshData->poInts = _state->meshes.back().get(); } - mesh = meshData->points; + mesh = meshData->poInts; } else if(sectionPrimitive == 'l') { if(!meshData->lines) { // TODO: C++ 17 @@ -457,13 +463,15 @@ void ObjImporter::parse() { meshData->lines = _state->meshes.back().get(); } mesh = meshData->lines; - } else { /* sectionPrimitive == 'f' */ + } else if(sectionPrimitive == 'f') { if(!meshData->faces) { // TODO: C++ 17 _state->meshes.emplace_back(new ObjMesh{*meshData, MeshPrimitive::Triangles}); meshData->faces = _state->meshes.back().get(); } mesh = meshData->faces; + } else { + CORRADE_ASSERT_UNREACHABLE(); } if(line.data() == nullptr) { /* Usually for the last line */ @@ -495,7 +503,7 @@ void ObjImporter::parse() { /* Parse the keyword */ std::string keyword; - ArrayView word; + ArrayView word; std::tie(word, pos) = nextWord(pos); keyword = std::string(word.data(), word.size()); @@ -504,23 +512,15 @@ void ObjImporter::parse() { /* Vertex position */ if(keyword == "v") { - // TODO C++17: Could be one slick line with emplace_back ref return. - Vector3 v; - pos = getVector<3>(pos, v); - _state->positions.push_back(v); + _state->positions.push_back(parseVector<3>(pos)); + /* Texture coordinate */ } else if(keyword == "vt") { - // TODO C++17: Could be one slick line with emplace_back ref return. - Vector2 tc; - pos = getVector<2>(pos, tc); - _state->texCoords.push_back(tc); + _state->texCoords.push_back(parseVector<2>(pos)); /* Normal */ } else if(keyword == "vn") { - // TODO C++17: Could be one slick line with emplace_back ref return. - Vector3 n; - pos = getVector<3>(pos, n); - _state->normals.push_back(n); + _state->normals.push_back(parseVector<3>(pos)); /* Indices */ } else if(keyword == "f" || keyword == "l" || keyword == "p") { @@ -544,7 +544,7 @@ void ObjImporter::parse() { finishSection(); finishObject(); - ArrayView name; + ArrayView name; std::tie(name, pos) = nextWord(pos); _state->objects.emplace_back(new ObjObject{name}); @@ -561,13 +561,13 @@ void ObjImporter::parse() { meshData = nullptr; - ArrayView name; + ArrayView name; std::tie(name, pos) = nextWord(pos); group->name = std::string{name.data(), name.size()}; /* Load a material library */ } else if(keyword == "mtllib") { - ArrayView word; + ArrayView word; pos = skipWhitespaces(pos); std::tie(word, pos) = nextWord(pos); @@ -575,11 +575,11 @@ void ObjImporter::parse() { /* Set current material and add a new mesh for it */ } else if(keyword == "usemtl") { - ArrayView word; - pos = skipWhitespaces(pos); + ArrayView word; + pos = skipWhitespaces(pos); std::tie(word, pos) = nextWord(pos); - const int materialIndex = _state->materialIds[std::string{word.data(), word.size()}]; + const Int materialIndex = _state->materialIds[std::string{word.data(), word.size()}]; if(meshData == nullptr || materialIndex != meshData->materialId) { /* Switching the material here, need to create a new mesh */ //TODO C++17 @@ -603,14 +603,14 @@ void ObjImporter::parse() { finishSection(); finishObject(); - int i = _state->meshes.size(); + Int i = _state->meshes.size(); for(auto name : _state->meshlessObjects) { _state->meshlessObjectIds[name] = i++; } } -void ObjImporter::parseMaterialLibrary(const ArrayView libname) { - std::string filename = _fileRoot + std::string(libname.data(), libname.size()); +void ObjImporter::parseMaterialLibrary(const ArrayView libname) { + std::string filename = _state->fileRoot + std::string(libname.data(), libname.size()); /* Open file */ if(!Utility::Directory::fileExists(filename)) { @@ -619,7 +619,7 @@ void ObjImporter::parseMaterialLibrary(const ArrayView libname) { } Containers::Array contents = Utility::Directory::read(filename); - ArrayView pos = contents; /* points to current character in line */ + ArrayView pos = contents; /* Points to current character in line */ ObjMaterial* mat = nullptr; @@ -633,7 +633,7 @@ void ObjImporter::parseMaterialLibrary(const ArrayView libname) { } /* Parse the keyword */ - ArrayView word; + ArrayView word; std::tie(word, pos) = nextWord(pos); std::string keyword{word.data(), word.size()}; @@ -659,49 +659,49 @@ void ObjImporter::parseMaterialLibrary(const ArrayView libname) { /* Ambient color */ if(keyword == "Ka") { - ArrayView word; + ArrayView word; - for(int i : {0, 1, 2}) { + for(Int i : {0, 1, 2}) { std::tie(word, pos) = nextWord(pos); mat->ambient[i] = strToFloat(word); } /* Diffuse color */ } else if(keyword == "Kd") { - ArrayView word; + ArrayView word; - for(int i : {0, 1, 2}) { + for(Int i : {0, 1, 2}) { std::tie(word, pos) = nextWord(pos); mat->diffuse[i] = strToFloat(word); } /* Specular color */ } else if(keyword == "Ks") { - ArrayView word; + ArrayView word; - for(int i : {0, 1, 2}) { + for(Int i : {0, 1, 2}) { std::tie(word, pos) = nextWord(pos); mat->specular[i] = strToFloat(word); } /* Specularity */ } else if(keyword == "Ns") { - ArrayView word; + ArrayView word; std::tie(word, pos) = nextWord(pos); - const float f = strToFloat(word); + const Float f = strToFloat(word); mat->specularity = f; /* Ambient texture */ } else if(keyword.substr(0, 4) == "map_") { - ArrayView line; + ArrayView line; std::tie(line, pos) = nextLine(pos); std::string texture{line.data(), line.size()}; - int textureId = -1; + Int textureId = -1; if(_state->textureIds.find(texture) == _state->textureIds.end()) { /* new texture, create it */ - int index = _state->textures.size(); + Int index = _state->textures.size(); _state->textures.push_back(texture); _state->textureIds[texture] = index; @@ -731,12 +731,6 @@ void ObjImporter::parseMaterialLibrary(const ArrayView libname) { } } -UnsignedInt ObjImporter::doMesh3DCount() const { return _state->meshes.size(); } - -UnsignedInt ObjImporter::doMaterialCount() const { return _state->materials.size(); } - -UnsignedInt ObjImporter::doImage2DCount() const { return _state->textures.size(); } - UnsignedInt ObjImporter::doObject3DCount() const { return _state->meshes.size() + _state->meshlessObjects.size(); } Int ObjImporter::doObject3DForName(const std::string& name) { @@ -754,23 +748,25 @@ Int ObjImporter::doObject3DForName(const std::string& name) { } std::string ObjImporter::doObject3DName(UnsignedInt id) { - const size_t numMeshes = _state->meshes.size(); - if(id >= numMeshes) { - return _state->meshlessObjects[id - numMeshes]; + const size_t lastMesh = _state->meshes.size() - 1; + if(id > lastMesh) { + return _state->meshlessObjects[id - lastMesh]; } - return _state->meshes[id]->name(); /* Intentional, objects are just meshes + material */ + return _state->meshes[id]->name(); } std::unique_ptr ObjImporter::doObject3D(UnsignedInt id) { - const size_t numMeshes = _state->meshes.size(); - if(id > numMeshes) { - return std::unique_ptr{new ObjectData3D{{}, {}, &_state->meshlessObjects[id - numMeshes]}}; + const size_t lastMesh = _state->meshes.size() - 1; + if(id > lastMesh) { + return std::unique_ptr{new ObjectData3D{{}, {}, &_state->meshlessObjects[id - lastMesh]}}; } const ObjMesh& mesh = *_state->meshes[id]; return std::unique_ptr{ new MeshObjectData3D{{}, {}, id, mesh.data.materialId, _state->meshes[id].get()}}; } +UnsignedInt ObjImporter::doMesh3DCount() const { return _state->meshes.size(); } + Int ObjImporter::doMesh3DForName(const std::string& name) { return _state->meshIds[name]; } @@ -782,7 +778,7 @@ std::string ObjImporter::doMesh3DName(UnsignedInt id) { std::optional ObjImporter::doMesh3D(UnsignedInt id) { const ObjMesh& mesh = *_state->meshes[id]; - const int primitiveSize = (mesh.primitive == MeshPrimitive::Triangles) + const Int primitiveSize = (mesh.primitive == MeshPrimitive::Triangles) ? 3 : ((mesh.primitive == MeshPrimitive::Lines) ? 2 : 1); std::vector positionIndices; @@ -812,8 +808,8 @@ std::optional ObjImporter::doMesh3D(UnsignedInt id) { CORRADE_ASSERT(pos[0] == 'f' && pos[1] == ' ', "Unexpected primitive keyword for Triangles", {}); pos = pos.suffix(2); - for(int i = 0; i < 3; ++i) { - std::array vertex; + for(Int i = 0; i < 3; ++i) { + std::array vertex; std::tie(vertex, pos) = parseVertex(pos); positionIndices.push_back(vertex[0]); textureCoordinateIndices.push_back(vertex[1]); @@ -833,7 +829,7 @@ std::optional ObjImporter::doMesh3D(UnsignedInt id) { pos = pos.suffix(2); while(!atEndOfLine(pos)) { - std::array line; + std::array line; std::tie(line, pos) = parseLine(pos); positionIndices.push_back(line[0]); textureCoordinateIndices.push_back(line[1]); @@ -850,7 +846,7 @@ std::optional ObjImporter::doMesh3D(UnsignedInt id) { CORRADE_ASSERT(pos[0] == 'p' && pos[1] == ' ', "Unexpected primitive keyword for Points", {}); pos = pos.suffix(2); - ArrayView word; + ArrayView word; while(!atEndOfLine(pos)) { std::tie(word, pos) = nextWord(pos); positionIndices.push_back(strToInt(word)); @@ -879,19 +875,16 @@ std::optional ObjImporter::doMesh3D(UnsignedInt id) { indices = MeshTools::combineIndexArrays(arrays); /* Reindex data arrays */ - try { - positionLayers.push_back(reindex(positionIndices, _state->positions)); - if(hasNormals) normalLayers.push_back(reindex(normalIndices, _state->normals)); - if(hasTexCoords) texCoordLayers.push_back(reindex(textureCoordinateIndices, _state->texCoords)); - } catch(...) { - /* Error message already printed */ - return std::nullopt; - } + positionLayers.push_back(reindex(positionIndices, _state->positions)); + if(hasNormals) normalLayers.push_back(reindex(normalIndices, _state->normals)); + if(hasTexCoords) texCoordLayers.push_back(reindex(textureCoordinateIndices, _state->texCoords)); return MeshData3D(mesh.primitive, std::move(indices), std::move(positionLayers), std::move(normalLayers), std::move(texCoordLayers), {}, &mesh); } +UnsignedInt ObjImporter::doMaterialCount() const { return _state->materials.size(); } + std::unique_ptr ObjImporter::doMaterial(const UnsignedInt id) { ObjMaterial& objMat = _state->materials[id]; PhongMaterialData::Flags flags; @@ -931,11 +924,13 @@ std::unique_ptr ObjImporter::doMaterial(const UnsignedInt return std::unique_ptr(mat); } +UnsignedInt ObjImporter::doImage2DCount() const { return _state->textures.size(); } + std::optional ObjImporter::doImage2D(UnsignedInt id) { CORRADE_ASSERT(manager(), "Trade::ObjImporter::image2D(): the plugin must be instantiated with access to plugin manager in order to open image files", {}); std::unique_ptr imageImporter = manager()->loadAndInstantiate("TgaImporter"); // probably AnyImageImporter would be the way to go here... - if(!imageImporter->openFile(_fileRoot + _state->textures[id])) { + if(!imageImporter->openFile(_state->fileRoot + _state->textures[id])) { return std::nullopt; } diff --git a/src/MagnumPlugins/ObjImporter/ObjImporter.h b/src/MagnumPlugins/ObjImporter/ObjImporter.h index b13d37ced..efc8d3365 100644 --- a/src/MagnumPlugins/ObjImporter/ObjImporter.h +++ b/src/MagnumPlugins/ObjImporter/ObjImporter.h @@ -112,11 +112,9 @@ class MAGNUM_OBJIMPORTER_EXPORT ObjImporter: public AbstractImporter { MAGNUM_OBJIMPORTER_LOCAL std::optional doImage2D(UnsignedInt id); MAGNUM_OBJIMPORTER_LOCAL void parse(); - MAGNUM_OBJIMPORTER_LOCAL void parseMaterialLibrary(Containers::ArrayView libname); + MAGNUM_OBJIMPORTER_LOCAL void parseMaterialLibrary(Containers::ArrayView libname); - Containers::Array _in; std::unique_ptr _state; - std::string _fileRoot; }; }} diff --git a/src/MagnumPlugins/ObjImporter/Test/Test.cpp b/src/MagnumPlugins/ObjImporter/Test/Test.cpp index ad341af8e..3d6e8b359 100644 --- a/src/MagnumPlugins/ObjImporter/Test/Test.cpp +++ b/src/MagnumPlugins/ObjImporter/Test/Test.cpp @@ -429,6 +429,7 @@ void ObjImporterTest::wrongFloat() { std::ostringstream out; Error redirectError{&out}; CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): error while converting numeric data\n"); + CORRADE_VERIFY(!importer.mesh3D(id)); } void ObjImporterTest::wrongInteger() { @@ -735,12 +736,12 @@ void ObjImporterTest::multiMaterialObject() { CORRADE_COMPARE(data->primitive(), MeshPrimitive::Triangles); CORRADE_COMPARE(data->positionArrayCount(), 1); CORRADE_COMPARE(data->positions(0), (std::vector{ - {Vector3(1.72414, 18.9233, -3.20162), - Vector3(2.74428, -0.499733, -3.50576), - Vector3(-1.92235, -0.846268, 2.9722), - Vector3(1.72414, 18.9233, -3.20162), - Vector3(-1.92235, -0.846268, 2.9722), - Vector3(2.43556, 18.8755, 2.23745)} + {{1.72414f, 18.9233f, -3.20162f}, + {2.74428f, -0.499733f, -3.50576f}, + {-1.92235f, -0.846268f, 2.9722f}, + {1.72414f, 18.9233f, -3.20162f}, + {-1.92235f, -0.846268f, 2.9722f}, + {2.43556f, 18.8755f, 2.23745f}} })); data = importer.mesh3D(1); @@ -762,6 +763,7 @@ void ObjImporterTest::unsupportedKeyword() { CORRADE_COMPARE(data->positions(0), (std::vector{ {0.0f, 1.0f, 2.0f} })); + CORRADE_COMPARE(data->indices(), std::vector{0}); } void ObjImporterTest::unknownKeyword() { diff --git a/src/MagnumPlugins/ObjImporter/Test/multiMaterial.mtl b/src/MagnumPlugins/ObjImporter/Test/multiMaterial.mtl index fcb923d86..5a4b92bc7 100644 --- a/src/MagnumPlugins/ObjImporter/Test/multiMaterial.mtl +++ b/src/MagnumPlugins/ObjImporter/Test/multiMaterial.mtl @@ -5,4 +5,5 @@ map_Ka Textures/mat_0.tga newmtl mat_1 Ka 1 1 1 -map_Ka Textures/mat_1.tga \ No newline at end of file +map_Ka Textures/mat_1.tga + diff --git a/src/MagnumPlugins/ObjImporter/Test/multiMaterial.obj b/src/MagnumPlugins/ObjImporter/Test/multiMaterial.obj index 4da632842..f809fe2fc 100644 --- a/src/MagnumPlugins/ObjImporter/Test/multiMaterial.obj +++ b/src/MagnumPlugins/ObjImporter/Test/multiMaterial.obj @@ -56,3 +56,4 @@ usemtl mat_1 # faces f 5/5/5 6/6/6 7/7/7 f 5/5/5 7/7/7 8/8/8 + From 4503b01c4b00afb5343afd1eb192dc77f010e64a Mon Sep 17 00:00:00 2001 From: Squareys Date: Wed, 28 Mar 2018 15:39:17 +0200 Subject: [PATCH 6/6] ObjImporter: Use AnyImageImporter Signed-off-by: Squareys --- src/MagnumPlugins/ObjImporter/CMakeLists.txt | 26 ++++++++++++++++--- src/MagnumPlugins/ObjImporter/ObjImporter.cpp | 4 +-- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/MagnumPlugins/ObjImporter/CMakeLists.txt b/src/MagnumPlugins/ObjImporter/CMakeLists.txt index a9a275618..70f9a1078 100644 --- a/src/MagnumPlugins/ObjImporter/CMakeLists.txt +++ b/src/MagnumPlugins/ObjImporter/CMakeLists.txt @@ -60,6 +60,9 @@ if(BUILD_STATIC_PIC) set_target_properties(ObjImporter PROPERTIES POSITION_INDEPENDENT_CODE ON) endif() target_link_libraries(ObjImporter Magnum MagnumMeshTools) +if(CORRADE_TARGET_WINDOWS) + target_link_libraries(ObjImporter AnyImageImporter) +endif() install(FILES ${ObjImporter_HEADERS} DESTINATION ${MAGNUM_PLUGINS_INCLUDE_INSTALL_DIR}/ObjImporter) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/configure.h DESTINATION ${MAGNUM_PLUGINS_INCLUDE_INSTALL_DIR}/ObjImporter) @@ -73,11 +76,26 @@ if(BUILD_PLUGINS_STATIC) endif() if(BUILD_TESTS) - add_library(MagnumObjImporterTestLib STATIC - $ - ${PROJECT_SOURCE_DIR}/src/dummy.cpp) # XCode workaround, see file comment for details + # On Win32 we need to avoid dllimporting AnyImageImporter symbols, because + # it would search for the symbols in some DLL even when they were linked + # statically. However it apparently doesn't matter that they were + # dllexported when building the static library. EH. And because the + # -DObjImporterObjects_EXPORTS is no longer set in this case, we need + # to avoid dllimporting ObjImporter symbols as well. + if(CORRADE_TARGET_WINDOWS) + add_library(MagnumObjImporterTestLib STATIC + ${ObjImporter_SRCS} + ${ObjImporter_HEADERS}) + target_compile_definitions(MagnumObjImporterTestLib PRIVATE + "MAGNUM_ANYIMAGEIMPORTER_BUILD_STATIC" + "MAGNUM_OBJIMPORTER_BUILD_STATIC") + else() + add_library(MagnumObjImporterTestLib STATIC + $ + ${PROJECT_SOURCE_DIR}/src/dummy.cpp) # XCode workaround, see file comment for details + endif() set_target_properties(MagnumObjImporterTestLib PROPERTIES FOLDER "MagnumPlugins/ObjImporter") - target_link_libraries(MagnumObjImporterTestLib Magnum MagnumMeshTools) + target_link_libraries(MagnumObjImporterTestLib Magnum MagnumMeshTools MagnumAnyImageImporterTestLib) add_subdirectory(Test) endif() diff --git a/src/MagnumPlugins/ObjImporter/ObjImporter.cpp b/src/MagnumPlugins/ObjImporter/ObjImporter.cpp index 33ca2bc5b..bacebe782 100644 --- a/src/MagnumPlugins/ObjImporter/ObjImporter.cpp +++ b/src/MagnumPlugins/ObjImporter/ObjImporter.cpp @@ -44,7 +44,7 @@ #include "Magnum/Trade/ImageData.h" #include "Magnum/Trade/PhongMaterialData.h" -#include "MagnumPlugins/TgaImporter/TgaImporter.h" +#include "MagnumPlugins/AnyImageImporter/AnyImageImporter.h" using namespace Corrade::Containers; @@ -929,7 +929,7 @@ UnsignedInt ObjImporter::doImage2DCount() const { return _state->textures.size() std::optional ObjImporter::doImage2D(UnsignedInt id) { CORRADE_ASSERT(manager(), "Trade::ObjImporter::image2D(): the plugin must be instantiated with access to plugin manager in order to open image files", {}); - std::unique_ptr imageImporter = manager()->loadAndInstantiate("TgaImporter"); // probably AnyImageImporter would be the way to go here... + AnyImageImporter imageImporter = AnyImageImporter{manager()} if(!imageImporter->openFile(_state->fileRoot + _state->textures[id])) { return std::nullopt; }