Browse Source

SCRAP: Add super-rudimentary objimporter rework + multiMaterial test

Signed-off-by: Squareys <squareys@googlemail.com>
pull/205/head
Squareys 10 years ago
parent
commit
f79d002475
  1. 623
      src/MagnumPlugins/ObjImporter/ObjImporter.cpp
  2. 10
      src/MagnumPlugins/ObjImporter/ObjImporter.h
  3. 29
      src/MagnumPlugins/ObjImporter/Test/Test.cpp
  4. 8
      src/MagnumPlugins/ObjImporter/Test/multiMaterial.mtl
  5. 60
      src/MagnumPlugins/ObjImporter/Test/multiMaterial.obj

623
src/MagnumPlugins/ObjImporter/ObjImporter.cpp

@ -32,12 +32,18 @@
#include <unordered_map> #include <unordered_map>
#include <Corrade/Containers/ArrayView.h> #include <Corrade/Containers/ArrayView.h>
#include <Corrade/Utility/String.h> #include <Corrade/Utility/String.h>
#include <Corrade/Utility/Directory.h>
#include "Magnum/Mesh.h" #include "Magnum/Mesh.h"
#include "Magnum/MeshTools/CombineIndexedArrays.h" #include "Magnum/MeshTools/CombineIndexedArrays.h"
#include "Magnum/MeshTools/Duplicate.h" #include "Magnum/MeshTools/Duplicate.h"
#include "Magnum/Math/Vector3.h"
#include "Magnum/Math/Color.h" #include "Magnum/Math/Color.h"
#include "Magnum/Trade/MeshData3D.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) #if defined(CORRADE_TARGET_NACL_NEWLIB) || defined(CORRADE_TARGET_ANDROID)
#include <sstream> #include <sstream>
@ -47,78 +53,154 @@ using namespace Corrade::Containers;
namespace Magnum { namespace Trade { namespace Magnum { namespace Trade {
struct ImporterState {
std::unordered_map<std::string, UnsignedInt> meshesForName;
std::vector<std::string> meshNames;
std::vector<std::tuple<std::streampos, std::streampos, UnsignedInt, UnsignedInt, UnsignedInt>> meshes;
std::optional<MeshPrimitive> 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<std::array<int, 3>> 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<Vector3> positions; std::vector<Vector3> positions;
std::vector<std::vector<Vector2>> textureCoordinates; std::vector<Vector2> textureCoordinates;
std::vector<std::vector<Vector3>> normals; std::vector<Vector3> normals;
std::vector<UnsignedInt> positionIndices; std::vector<ObjMaterial> materials;
std::vector<UnsignedInt> textureCoordinateIndices; std::vector<Int> meshMaterials;
std::vector<UnsignedInt> normalIndices; std::unordered_map<std::string, int> materialIndices;
std::vector<std::unique_ptr<ObjMesh>> meshes;
std::vector<std::string> textures;
std::unordered_map<std::string, int> textureIndices;
}; };
namespace { namespace {
int findNext(const ArrayView<char>& pos, char c) { // entire contents of data in a std::string
std::string arrayToString(const Containers::ArrayView<const char> 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<char>& pos, char c, bool termByNewline=false, bool termByWhitespace=false) {
for(int i = 0; i < pos.size(); ++i) { for(int i = 0; i < pos.size(); ++i) {
if(pos[i] == c) { if(pos[i] == c) {
return i; return i;
} }
if((pos[i] == '\n' && termByNewline) || (pos[i] == ' ' && termByWhitespace)) {
return -1;
}
} }
return -1; return -1;
} }
ArrayView<char> skipWhitespaces(const ArrayView<char>& pos) { // find next non-whitespace char and return suffix at that point.
// param strict if "true", '\n' and '\r' are not considered whitespace
ArrayView<char> skipWhitespaces(const ArrayView<char>& pos, bool strict=false) {
for(int i = 0; i < pos.size(); ++i) { 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 pos.suffix(i);
} }
} }
return ArrayView<char>{}; return ArrayView<char>{};
} }
// returns suffix from after next '\n'
ArrayView<char> ignoreLine(const ArrayView<char>& pos) { ArrayView<char> ignoreLine(const ArrayView<char>& 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<std::string, ArrayView<char>> nextLine(const ArrayView<char>& pos) { std::tuple<std::string, ArrayView<char>> nextLine(const ArrayView<char>& pos) {
int i = 0; int i = Math::min(findNext(pos, '\n'), findNext(pos, '\r'));
for(; i < pos.size(); ++i) { if(i == -1) {
if(pos[i] == '\n') { i = pos.size();
break; 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)); return std::make_tuple(str, pos.suffix(i+1));
} }
std::tuple<std::string, ArrayView<char>> nextWord(const ArrayView<char>& pos) { // Parse a "v/n/t" string to indices
int i = 0; // Warning: I'm not handing cases like "v/n", where the normal is not terminated by '/', but ' '!
for(; i < pos.size(); ++i) { // Same for "v", where even the normal is omitted.
if(pos[i] == ' ') { std::tuple<std::array<int, 3>, ArrayView<char>> parseVertex(const ArrayView<char>& pos) {
break; std::array<int, 3> 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<float, ArrayView<char>> asFloat(const ArrayView<char>& 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<class T> void reindex(const std::vector<UnsignedInt>& indices, std::vector<T>& data) { // get string of content until next ' ' and also return ArrayView for data after the word.
/* Check that indices are in range */ std::tuple<std::string, ArrayView<char>> nextWord(const ArrayView<char>& pos) {
for(UnsignedInt i: indices) if(i >= data.size()) { int i = 0;
Error() << "Trade::ObjImporter::mesh3D(): index out of range"; for(; i < pos.size(); ++i) {
throw 0; 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::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<const char> data) { void ObjImporter::doOpenData(Containers::ArrayView<const char> data) {
_in = Containers::Array<char>{data.size()}; _in = Containers::Array<char>{data.size()};
std::copy(data.begin(), data.end(), _in.begin()); std::copy(data.begin(), data.end(), _in.begin());
_state.reset(new ImporterState); _state.reset(new ImporterState);
parseMeshNames(); parse();
} }
void ObjImporter::parseMeshNames() { void ObjImporter::parse() {
/* First mesh starts at the beginning, its indices start from 1. The end ArrayView<char> pos = _in;
offset will be updated to proper value later. */ ObjMesh* mesh = new ObjMesh;
UnsignedInt positionIndexOffset = 1;
UnsignedInt normalIndexOffset = 1;
UnsignedInt textureCoordinateIndexOffset = 1;
_state->meshes.emplace_back(0, 0, positionIndexOffset, normalIndexOffset, textureCoordinateIndexOffset);
_state->meshNames.emplace_back(); int currentMaterialIndex = -1;
ArrayView<char> pos = _in;
while(!pos.empty()) { while(!pos.empty()) {
/* Comment line */ /* Comment line */
if(pos[0] == '#') { if(pos[0] == '#') {
pos = ignoreLine(pos); pos = ignoreLine(pos);
pos = skipWhitespaces(pos);
continue; continue;
} }
@ -165,208 +248,364 @@ void ObjImporter::parseMeshNames() {
std::string keyword; std::string keyword;
std::tie(keyword, pos) = nextWord(pos); std::tie(keyword, pos) = nextWord(pos);
/* Mesh name */ pos = skipWhitespaces(pos);
if(keyword == "o") {
// TODO
continue;
}
/* Vertex position */ /* Vertex position */
if(keyword == "v") { if(keyword == "v") {
Float extra{1.0f}; std::string word;
const Vector3 data = extractFloatData<3>(pos, &extra);
if(!Math::TypeTraits<Float>::equals(extra, 1.0f)) { // All of this code should be abstracted away in a function.
Error() << "Trade::ObjImporter::mesh3D(): homogeneous coordinates are not supported"; // Currently copy pasted to vt, vn, Ka, Ks, Kd...
return Containers::NullOpt; std::tie(word, pos) = nextWord(pos);
} const float x = std::stof(word);
positions.push_back(data); 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->positions.push_back(Vector3{x, y, z});
/* Texture coordinate */ /* Texture coordinate */
} else if(keyword == "vt") { } else if(keyword == "vt") {
Float extra{0.0f}; std::string word;
const auto data = extractFloatData<2>(contents, &extra);
if(!Math::TypeTraits<Float>::equals(extra, 0.0f)) {
Error() << "Trade::ObjImporter::mesh3D(): 3D texture coordinates are not supported";
return Containers::NullOpt;
}
if(textureCoordinates.empty()) textureCoordinates.emplace_back(); std::tie(word, pos) = nextWord(pos);
textureCoordinates.front().emplace_back(data); const float x = std::stof(word);
std::tie(word, pos) = nextWord(pos);
const float y = std::stof(word);
_state->textureCoordinates.push_back(Vector2{x, y});
/* Normal */ /* Normal */
} else if(keyword == "vn") { } else if(keyword == "vn") {
if(normals.empty()) normals.emplace_back(); std::string word;
normals.front().emplace_back(extractFloatData<3>(contents));
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 */ /* Indices */
} else if(keyword == "p" || keyword == "l" || keyword == "f") { } else if(keyword == "f") {
const std::vector<std::string> indexTuples = Utility::String::splitWithoutEmptyParts(contents, ' '); // Not handling case that there are more than three vertices!
for(int i = 0; i < 3; ++i) {
std::array<int, 3> 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<ObjMesh>(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;
}
/* Points */ /* Ignore the rest of the line */
if(keyword == "p") { pos = skipWhitespaces(ignoreLine(pos));
/* 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; if(!mesh->indices.empty()) {
return Containers::NullOpt; _state->meshes.push_back(std::unique_ptr<ObjMesh>(mesh));
} }
}
/* Check vertex count per primitive */ void ObjImporter::parseMaterialLibrary(std::string libname) {
if(indexTuples.size() != 1) { std::string filename = _fileRoot + libname;
Error() << "Trade::ObjImporter::mesh3D(): wrong index count for point";
return Containers::NullOpt; /* Open file */
if(!Utility::Directory::fileExists(filename)) {
Error() << "Trade::AbstractImporter::parseMaterialLibrary(): cannot open file" << filename;
return;
} }
primitive = MeshPrimitive::Points; Containers::Array<char> contents = Utility::Directory::read(filename);
ArrayView<char> pos = contents;
ObjMaterial* mat = nullptr;
while(!pos.empty()) {
/* Lines */ /* Comment line */
} else if(keyword == "l") { if(pos[0] == '#') {
/* Check that we don't mix the primitives in one mesh */ pos = ignoreLine(pos);
if(primitive && primitive != MeshPrimitive::Lines) { pos = skipWhitespaces(pos);
Error() << "Trade::ObjImporter::mesh3D(): mixed primitive" << *primitive << "and" << MeshPrimitive::Lines; continue;
return Containers::NullOpt;
} }
/* Check vertex count per primitive */ /* Parse the keyword */
if(indexTuples.size() != 2) { std::string keyword;
Error() << "Trade::ObjImporter::mesh3D(): wrong index count for line"; std::tie(keyword, pos) = nextWord(pos);
return Containers::NullOpt;
if(keyword.empty()) {
continue;
} }
primitive = MeshPrimitive::Lines; pos = skipWhitespaces(pos);
/* Faces */ if(keyword == "newmtl") {
} else if(keyword == "f") { if(mat != nullptr) {
/* Check that we don't mix the primitives in one mesh */ _state->materials.push_back(*mat);
if(primitive && primitive != MeshPrimitive::Triangles) { _state->materialIndices.insert(
Error() << "Trade::ObjImporter::mesh3D(): mixed primitive" << *primitive << "and" << MeshPrimitive::Triangles; std::make_pair(mat->name, _state->materials.size()-1));
return Containers::NullOpt; delete mat;
}
mat = new ObjMaterial;
std::tie(mat->name, pos) = nextWord(pos);
continue;
} else if (mat == nullptr) {
Error() << "Expected newmtl keyword, got" << keyword;
} }
/* Check vertex count per primitive */ /* Ambient color */
if(indexTuples.size() < 3) { if(keyword == "Ka") {
Error() << "Trade::ObjImporter::mesh3D(): wrong index count for triangle"; std::string word;
return Containers::NullOpt;
} else if(indexTuples.size() != 3) { // again, this code is duplicated alot.
Error() << "Trade::ObjImporter::mesh3D(): polygons are not supported"; std::tie(word, pos) = nextWord(pos);
return Containers::NullOpt; 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];
} }
primitive = MeshPrimitive::Triangles; /* Diffuse texture */
} else if(keyword == "map_Kd") {
std::string texture;
std::tie(texture, pos) = nextLine(pos);
} else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ if(_state->textureIndices.find(texture) == _state->textureIndices.end()) {
/* new texture, create it */
int index = _state->textures.size();
_state->textures.push_back(texture);
for(const std::string& indexTuple: indexTuples) { _state->textureIndices[texture] = index;
std::vector<std::string> indices = Utility::String::split(indexTuple, '/'); mat->diffuseTexture = index;
if(indices.size() > 3) { } else {
Error() << "Trade::ObjImporter::mesh3D(): invalid index data"; mat->diffuseTexture = _state->textureIndices[texture];
return Containers::NullOpt;
} }
/* Position indices */ /* Specular texture */
positionIndices.push_back(std::stoul(indices[0]) - positionIndexOffset); } else if(keyword == "map_Ks") {
std::string texture;
std::tie(texture, pos) = nextLine(pos);
/* Texture coordinates */ if(_state->textureIndices.find(texture) == _state->textureIndices.end()) {
if(indices.size() == 2 || (indices.size() == 3 && !indices[1].empty())) /* new texture, create it */
textureCoordinateIndices.push_back(std::stoul(indices[1]) - textureCoordinateIndexOffset); int index = _state->textures.size();
_state->textures.push_back(texture);
/* Normal indices */ _state->textureIndices[texture] = index;
if(indices.size() == 3) mat->specularTexture = index;
normalIndices.push_back(std::stoul(indices[2]) - normalIndexOffset); } else {
mat->specularTexture = _state->textureIndices[texture];
} }
/* Ignore unsupported keywords, error out on unknown keywords */ /* Ignore unsupported keywords, error out on unknown keywords */
} else if(![&keyword](){ } else {
/* Using lambda to emulate for-else construct like in Python */ Warning() << "Trade::ObjImporter::parseMaterialLibrary(): unknown keyword:" << keyword;
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;
} }
/* Ignore the rest of the line */ /* 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::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) { Int ObjImporter::doMesh3DForName(const std::string& name) {
const auto it = _state->meshesForName.find(name); return 0;
return it == _state->meshesForName.end() ? -1 : it->second;
} }
std::string ObjImporter::doMesh3DName(UnsignedInt id) { std::string ObjImporter::doMesh3DName(UnsignedInt id) {
return _state->meshNames[id]; return "";
} }
std::optional<MeshData3D> ObjImporter::doMesh3D(UnsignedInt id) { std::optional<MeshData3D> ObjImporter::doMesh3D(UnsignedInt id) {
/* Seek the file, set mesh parsing parameters */ ObjMesh& mesh = *_state->meshes[id];
std::streampos begin, end;
UnsignedInt positionIndexOffset, textureCoordinateIndexOffset, normalIndexOffset; std::vector<Vector3> positions, normals;
std::tie(begin, end, positionIndexOffset, textureCoordinateIndexOffset, normalIndexOffset) = _state->meshes[id]; std::vector<Vector2> textureCoords;
/* There should be at least indexed position data */ Debug() << "Have" << mesh.indices.size() << "vertices"; //
if(positions.empty() || positionIndices.empty()) {
Error() << "Trade::ObjImporter::mesh3D(): incomplete position data"; // resolve indices... probably use combineIndexArrays instead?
return Containers::NullOpt; positions.reserve(mesh.indices.size());
} normals.reserve(mesh.indices.size());
textureCoords.reserve(mesh.indices.size());
/* If there are index data, there should be also vertex data (and also the other way) */
if(normals.empty() != normalIndices.empty()) { for(std::array<int, 3>& indexArray : mesh.indices) {
Error() << "Trade::ObjImporter::mesh3D(): incomplete normal data"; positions.push_back(_state->positions[indexArray[0]-1]);
return Containers::NullOpt;
} if(indexArray[1] != -1) // even though this looks like it's handling the case, it's not, this is utterly useless.
if(textureCoordinates.empty() != textureCoordinateIndices.empty()) { // may result in differently sized arrays, which is not valid.
Error() << "Trade::ObjImporter::mesh3D(): incomplete texture coordinate data"; normals.push_back(_state->normals[indexArray[1]-1]);
return Containers::NullOpt; if(indexArray[2] != -1)
} textureCoords.push_back(_state->textureCoordinates[indexArray[2]-1]);
}
/* All index arrays should have the same length */
if(!normalIndices.empty() && normalIndices.size() != positionIndices.size()) { return MeshData3D(
CORRADE_INTERNAL_ASSERT(normalIndices.size() < positionIndices.size()); MeshPrimitive::Triangles,
Error() << "Trade::ObjImporter::mesh3D(): some normal indices are missing"; {},
return Containers::NullOpt; {positions},
} {normals},
if(!textureCoordinates.empty() && textureCoordinateIndices.size() != positionIndices.size()) { {textureCoords});
CORRADE_INTERNAL_ASSERT(textureCoordinateIndices.size() < positionIndices.size()); }
Error() << "Trade::ObjImporter::mesh3D(): some texture coordinate indices are missing";
return Containers::NullOpt; std::unique_ptr<AbstractMaterialData> ObjImporter::doMaterial(UnsignedInt id) {
} ObjMaterial& objMat = _state->materials[_state->meshMaterials[id]];
PhongMaterialData::Flags flags;
/* Merge index arrays, if there aren't just the positions */
std::vector<UnsignedInt> indices; if(objMat.ambientTexture != -1) {
if(!normalIndices.empty() || !textureCoordinateIndices.empty()) { flags |= PhongMaterialData::Flag::AmbientTexture;
std::vector<std::reference_wrapper<std::vector<UnsignedInt>>> arrays; }
arrays.reserve(3); if(objMat.diffuseTexture != -1) {
arrays.emplace_back(positionIndices); flags |= PhongMaterialData::Flag::DiffuseTexture;
if(!normalIndices.empty()) arrays.emplace_back(normalIndices); }
if(!textureCoordinateIndices.empty()) arrays.emplace_back(textureCoordinateIndices); if(objMat.specularTexture != -1) {
indices = MeshTools::combineIndexArrays(arrays); flags |= PhongMaterialData::Flag::SpecularTexture;
}
/* Reindex data arrays */
try { PhongMaterialData* mat = new PhongMaterialData{
reindex(positionIndices, positions); flags,
if(!normalIndices.empty()) reindex(normalIndices, normals.front()); objMat.specularity};
if(!textureCoordinateIndices.empty()) reindex(textureCoordinateIndices, textureCoordinates.front());
} catch(...) { if(objMat.ambientTexture == -1) {
/* Error message already printed */ mat->ambientColor() = objMat.ambient;
return Containers::NullOpt; } else {
} mat->ambientTexture() = objMat.ambientTexture;
}
/* Otherwise just use the original position index array. Don't forget to
check range */ if(objMat.diffuseTexture == -1) {
mat->diffuseColor() = objMat.diffuse;
} else {
mat->diffuseTexture() = objMat.diffuseTexture;
}
if(objMat.specularTexture == -1) {
mat->specularColor() = objMat.specular;
} else { } else {
indices = std::move(positionIndices); mat->specularTexture() = objMat.specularTexture;
for(UnsignedInt i: indices) if(i >= positions.size()) {
Error() << "Trade::ObjImporter::mesh3D(): index out of range";
return Containers::NullOpt;
} }
return std::unique_ptr<AbstractMaterialData>(mat);
}
std::optional<ImageData2D> 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<AbstractImporter> 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);
} }
}} }}

10
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 std::string doMesh3DName(UnsignedInt id) override;
MAGNUM_OBJIMPORTER_LOCAL Containers::Optional<MeshData3D> doMesh3D(UnsignedInt id) override; MAGNUM_OBJIMPORTER_LOCAL Containers::Optional<MeshData3D> doMesh3D(UnsignedInt id) override;
MAGNUM_OBJIMPORTER_LOCAL void parseMeshNames(); MAGNUM_OBJIMPORTER_LOCAL UnsignedInt doMaterialCount() const override;
MAGNUM_OBJIMPORTER_LOCAL std::unique_ptr<AbstractMaterialData> doMaterial(UnsignedInt id) override;
MAGNUM_OBJIMPORTER_LOCAL UnsignedInt doImage2DCount() const;
MAGNUM_OBJIMPORTER_LOCAL std::optional<ImageData2D> doImage2D(UnsignedInt id);
MAGNUM_OBJIMPORTER_LOCAL void parse();
MAGNUM_OBJIMPORTER_LOCAL void parseMaterialLibrary(std::string libname);
Containers::Array<char> _in; Containers::Array<char> _in;
std::unique_ptr<struct ImporterState> _state; std::unique_ptr<struct ImporterState> _state;
std::string _fileRoot;
}; };
}} }}

29
src/MagnumPlugins/ObjImporter/Test/Test.cpp

@ -83,6 +83,8 @@ struct ObjImporterTest: TestSuite::Tester {
void missingNormalIndices(); void missingNormalIndices();
void missingTextureCoordinateIndices(); void missingTextureCoordinateIndices();
void multiMaterialObject();
void wrongTextureCoordinateIndexCount(); void wrongTextureCoordinateIndexCount();
void wrongNormalIndexCount(); void wrongNormalIndexCount();
@ -134,6 +136,8 @@ ObjImporterTest::ObjImporterTest() {
&ObjImporterTest::missingNormalIndices, &ObjImporterTest::missingNormalIndices,
&ObjImporterTest::missingTextureCoordinateIndices, &ObjImporterTest::missingTextureCoordinateIndices,
&ObjImporterTest::multiMaterialObject,
&ObjImporterTest::wrongTextureCoordinateIndexCount, &ObjImporterTest::wrongTextureCoordinateIndexCount,
&ObjImporterTest::wrongNormalIndexCount, &ObjImporterTest::wrongNormalIndexCount,
@ -702,6 +706,31 @@ void ObjImporterTest::wrongTextureCoordinateIndexCount() {
CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): some texture coordinate indices are missing\n"); 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<MeshData3D> 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>{
{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() { void ObjImporterTest::unsupportedKeyword() {
ObjImporter importer; ObjImporter importer;
CORRADE_VERIFY(importer.openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "keywords.obj"))); CORRADE_VERIFY(importer.openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "keywords.obj")));

8
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

60
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
Loading…
Cancel
Save