From f79d002475e49c6d1ebd822188a4cc5e0ae0f1e4 Mon Sep 17 00:00:00 2001 From: Squareys Date: Fri, 24 Jun 2016 11:56:20 +0200 Subject: [PATCH] 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