Browse Source

[WIP] Address the "easy" review comments

Signed-off-by: Squareys <squareys@googlemail.com>
pull/205/head
Squareys 9 years ago
parent
commit
52a1a868b6
  1. 309
      src/MagnumPlugins/ObjImporter/ObjImporter.cpp
  2. 4
      src/MagnumPlugins/ObjImporter/ObjImporter.h
  3. 14
      src/MagnumPlugins/ObjImporter/Test/Test.cpp
  4. 1
      src/MagnumPlugins/ObjImporter/Test/multiMaterial.mtl
  5. 1
      src/MagnumPlugins/ObjImporter/Test/multiMaterial.obj

309
src/MagnumPlugins/ObjImporter/ObjImporter.cpp

@ -28,8 +28,7 @@
#include <fstream> #include <fstream>
#include <limits> #include <limits>
#include <sstream> #include <sstream>
#include <tuple> #include <cstdlib>
#include <stdlib.h>
#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>
@ -72,28 +71,26 @@ struct ObjMesh;
struct ObjMeshData { struct ObjMeshData {
ObjGroup& group; ObjGroup& group;
int materialId; /* from 'usemtl' keyword */ Int materialId; /* From 'usemtl' keyword */
ObjMesh* points = nullptr; ObjMesh* poInts = nullptr;
ObjMesh* lines = nullptr; ObjMesh* lines = nullptr;
ObjMesh* faces = nullptr; ObjMesh* faces = nullptr;
ObjMeshData()=delete; explicit ObjMeshData(ObjGroup& g, Int matId): group(g), materialId(matId) {}
ObjMeshData(ObjGroup& g, int matId): group(g), materialId(matId) {}
}; };
struct ObjGroup { struct ObjGroup {
ObjObject& object; ObjObject& object;
std::string name; /* from 'g' keyword */ std::string name; /* From 'g' keyword */
std::vector<std::unique_ptr<ObjMeshData>> meshes; std::vector<std::unique_ptr<ObjMeshData>> meshes;
std::unordered_map<int, int> meshPerMaterial; std::unordered_map<Int, Int> meshPerMaterial;
ObjGroup()=delete; explicit ObjGroup(ObjObject& o): object(o) {}
ObjGroup(ObjObject& o): object(o) {}
/* Create or get mesh for given material id */ /* Create or get mesh for given material id */
ObjMeshData& meshDataForMaterial(int materialId) { ObjMeshData& meshDataForMaterial(Int materialId) {
if(meshPerMaterial.find(materialId) != meshPerMaterial.end()) { if(meshPerMaterial.find(materialId) != meshPerMaterial.end()) {
return *meshes[meshPerMaterial[materialId]]; return *meshes[meshPerMaterial[materialId]];
} else { } else {
@ -106,25 +103,25 @@ struct ObjGroup {
}; };
struct ObjObject { struct ObjObject {
std::string name; /* from 'o' keyword */ std::string name; /* From 'o' keyword */
std::vector<std::unique_ptr<ObjGroup>> groups; std::vector<std::unique_ptr<ObjGroup>> groups;
ObjObject(): name{""} {} ObjObject(): name{""} {}
ObjObject(ArrayView<char> name): name{name.data(), name.size()} {} ObjObject(ArrayView<const char> 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 */ * where to find the data associated to it */
struct ObjMesh { 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; MeshPrimitive primitive;
/* Sections of the file belonging to this mesh */ /* Sections of the file belonging to this mesh */
std::vector<ArrayView<char>> sections; std::vector<ArrayView<const char>> 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() {
std::string name = data.group.object.name; std::string name = data.group.object.name;
@ -137,8 +134,8 @@ struct ObjMesh {
name += "$" + std::to_string(data.materialId); name += "$" + std::to_string(data.materialId);
} }
const int numPrimitiveTypes = const Int numPrimitiveTypes =
((data.points) ? 1 : 0) ((data.poInts) ? 1 : 0)
+ ((data.lines) ? 1 : 0) + ((data.lines) ? 1 : 0)
+ ((data.faces) ? 1 : 0); + ((data.faces) ? 1 : 0);
if(numPrimitiveTypes > 1) { if(numPrimitiveTypes > 1) {
@ -154,19 +151,22 @@ struct ObjMesh {
/* The state of the imported generated by openData() */ /* The state of the imported generated by openData() */
struct ImporterState { struct ImporterState {
Containers::Array<char> in;
std::string fileRoot;
std::vector<ObjMaterial> materials; std::vector<ObjMaterial> materials;
std::unordered_map<std::string, int> materialIds; std::unordered_map<std::string, Int> materialIds;
std::vector<std::string> textures; std::vector<std::string> textures;
std::unordered_map<std::string, int> textureIds; std::unordered_map<std::string, Int> textureIds;
std::vector<std::unique_ptr<ObjObject>> objects; std::vector<std::unique_ptr<ObjObject>> objects;
std::vector<std::unique_ptr<ObjMesh>> meshes; std::vector<std::unique_ptr<ObjMesh>> meshes;
std::unordered_map<std::string, int> meshIds; std::unordered_map<std::string, Int> meshIds;
std::vector<std::string> meshlessObjects; std::vector<std::string> meshlessObjects;
std::unordered_map<std::string, int> meshlessObjectIds; std::unordered_map<std::string, Int> meshlessObjectIds;
std::vector<Vector3> positions; std::vector<Vector3> positions;
std::vector<Vector2> texCoords; std::vector<Vector2> texCoords;
@ -175,9 +175,10 @@ struct ImporterState {
namespace { namespace {
int strToInt(const ArrayView<char> str) { /* Returns the given string parsed as Int */
Int strToInt(const ArrayView<const char> str) {
char* err; char* err;
const int i = int(strtol(str.data(), &err, 10)); const Int i = Int(strtol(str.data(), &err, 10));
if(err == nullptr) { if(err == nullptr) {
Error() << "Trade::ObjImporter::mesh3D(): error while converting numeric data"; Error() << "Trade::ObjImporter::mesh3D(): error while converting numeric data";
return 0; return 0;
@ -186,9 +187,10 @@ int strToInt(const ArrayView<char> str) {
return i; return i;
} }
float strToFloat(const ArrayView<char> str) { /* Returns the given string parsed as Float */
Float strToFloat(const ArrayView<const char> str) {
char* err; char* err;
const float f = strtof(str.data(), &err); const Float f = strtof(str.data(), &err);
if(err == nullptr) { if(err == nullptr) {
Error() << "Trade::ObjImporter::mesh3D(): error while converting numeric data"; Error() << "Trade::ObjImporter::mesh3D(): error while converting numeric data";
return 0; return 0;
@ -197,10 +199,15 @@ float strToFloat(const ArrayView<char> str) {
return f; return f;
} }
// 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) { * Returns the index of the next occurrence of `c` or -1, if it could not be found.
const int size = pos.size(); *
for(int i = 0; i < size; ++i) { * @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<const char> 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) { if(pos[i] == c) {
return i; return i;
} }
@ -212,11 +219,13 @@ int findNext(const ArrayView<char> pos, char c, bool termByNewline=false, bool t
return -1; return -1;
} }
// find next non-whitespace char and return suffix at that point. /*
// param newlineIsWhitespace if "true", '\n' and '\r' are skipped also * Return suffix beginning at next non-whitespace character
ArrayView<char> skipWhitespaces(const ArrayView<char> pos, bool newlineIsWhitespace=true) { * @param newlineIsWhitespace if `true`, '\n' and '\r' are skipped also
const int size = pos.size(); */
for(int i = 0; i < size; ++i) { ArrayView<const char> skipWhitespaces(const ArrayView<const char> pos, bool newlineIsWhitespace=true) {
const Int size = pos.size();
for(Int i = 0; i < size; ++i) {
const char c = pos[i]; const char c = pos[i];
if(c != ' ' && c != '\t' && c != '\0' && (!newlineIsWhitespace || (c != '\n' && c != '\r'))) { if(c != ' ' && c != '\t' && c != '\0' && (!newlineIsWhitespace || (c != '\n' && c != '\r'))) {
return pos.suffix(i); return pos.suffix(i);
@ -225,53 +234,53 @@ ArrayView<char> skipWhitespaces(const ArrayView<char> pos, bool newlineIsWhitesp
return {}; return {};
} }
// returns suffix after next '\n' /* Returns suffix after the next '\n' or the given ArrayView if no newline could be found */
ArrayView<char> ignoreLine(const ArrayView<char> pos) { ArrayView<const char> ignoreLine(const ArrayView<const char> pos) {
return pos.suffix(findNext(pos, '\n') + 1); return pos.suffix(findNext(pos, '\n') + 1);
} }
// same as ignoreLine, but also returns the content "ignored". /* Returns a prefix until the next '\n' and the suffix after the newline. */
// Different description: Get string until next '\n' and return ArrayView of data std::pair<ArrayView<const char>, ArrayView<const char>> nextLine(const ArrayView<const char>& pos) {
// after it. Int i = Math::min(findNext(pos, '\n'), findNext(pos, '\r'));
std::tuple<ArrayView<char>, ArrayView<char>> nextLine(const ArrayView<char>& pos) {
int i = Math::min(findNext(pos, '\n'), findNext(pos, '\r'));
if(i == -1) { if(i == -1) {
i = pos.size(); 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<char> pos) { /* Returns true if the next non-whitespace character is a newline character */
int i = 0; bool atEndOfLine(const ArrayView<const char> pos) {
Int i = 0;
while(pos[i] == ' ') ++i; while(pos[i] == ' ') ++i;
return (pos[i] == '\n' || pos[i] == '\r'); return (pos[i] == '\n' || pos[i] == '\r');
} }
// get string of content until next ' ' and also return ArrayView for data after the word. /* Returns the prefix until the next ' ' character and the suffix starting with it */
std::tuple<ArrayView<char>, ArrayView<char>> nextWord(const ArrayView<char> pos) { std::pair<ArrayView<const char>, ArrayView<const char>> nextWord(const ArrayView<const char> pos) {
int i = 0; Int i = 0;
const int size = pos.size(); const Int size = pos.size();
for(; i < size; ++i) { for(; i < size; ++i) {
if(pos[i] == ' ' || pos[i] == '\r' || pos[i] == '\n' || pos[i] == '\0') { if(pos[i] == ' ' || pos[i] == '\r' || pos[i] == '\n' || pos[i] == '\0') {
break; break;
} }
} }
return std::make_tuple(pos.prefix(i), pos.suffix(i)); return {pos.prefix(i), pos.suffix(i)};
} }
std::tuple<std::array<int, 2>, ArrayView<char>> parseLine(const ArrayView<char> pos) { /* Parse indices for a line, e.g. "1/2" */
std::array<int, 2> indices{0, 0}; std::pair<std::array<Int, 2>, ArrayView<const char>> parseLine(const ArrayView<const char> pos) {
ArrayView<char> endpos{}; std::array<Int, 2> indices{0, 0};
ArrayView<const char> endpos{};
int i = findNext(pos, '/', true, true); Int i = findNext(pos, '/', true, true);
if(i == -1) { if(i == -1) {
/* v1 v2 rather than v1/t1 v2/t2 or v1/ v2/ */ /* v1 v2 rather than v1/t1 v2/t2 or v1/ v2/ */
ArrayView<char> word; ArrayView<const char> word;
std::tie(word, endpos) = nextWord(pos); std::tie(word, endpos) = nextWord(pos);
indices[0] = strToInt(word); indices[0] = strToInt(word);
return std::make_tuple(indices, endpos); return {indices, endpos};
} }
indices[0] = strToInt(pos.prefix(i)); indices[0] = strToInt(pos.prefix(i));
@ -291,30 +300,28 @@ std::tuple<std::array<int, 2>, ArrayView<char>> parseLine(const ArrayView<char>
} }
} }
return std::make_tuple(indices, endpos); return {indices, endpos};
} }
// Parse a "v/n/t" string to indices /* Parse a "v/n/t" string to indices for a face */
// Warning: I'm not handing cases like "v/n", where the normal is not terminated by '/', but ' '! std::pair<std::array<Int, 3>, ArrayView<const char>> parseVertex(const ArrayView<const char> pos) {
// Same for "v", where even the normal is omitted. std::array<Int, 3> indices{0, 0, 0};
std::tuple<std::array<int, 3>, ArrayView<char>> parseVertex(const ArrayView<char> pos) { ArrayView<const char> endpos{};
std::array<int, 3> indices{0, 0, 0};
ArrayView<char> endpos{};
int i = findNext(pos, '/', true, true); Int i = findNext(pos, '/', true, true);
if(i == -1) { if(i == -1) {
/* v1 v2 rather than v1/t1 v2/t2 or v1/ v2/ */ /* v1 v2 rather than v1/t1 v2/t2 or v1/ v2/ */
ArrayView<char> word; ArrayView<const char> word;
std::tie(word, endpos) = nextWord(pos); std::tie(word, endpos) = nextWord(pos);
indices[0] = strToInt(word); indices[0] = strToInt(word);
return std::make_tuple(indices, endpos); return {indices, endpos};
} }
indices[0] = strToInt(pos.prefix(i)); indices[0] = strToInt(pos.prefix(i));
endpos = pos.suffix(i+1); endpos = pos.suffix(i+1);
i = findNext(endpos, '/', true, true); 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); auto prefix = endpos.prefix(i);
if(!prefix.empty()) { if(!prefix.empty()) {
indices[1] = strToInt(prefix); indices[1] = strToInt(prefix);
@ -322,13 +329,13 @@ std::tuple<std::array<int, 3>, ArrayView<char>> parseVertex(const ArrayView<char
endpos = endpos.suffix(i+1); 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) if(i == -1)
i = findNext(endpos, '\r', true, true); i = findNext(endpos, '\r', true, true);
if(i == -1) if(i == -1)
i = findNext(endpos, '\n', true, true); i = findNext(endpos, '\n', true, true);
if(i != -1) { // there may not be a texCoord! Eg. "1//" if(i != -1) { /* There may not be a texCoord! Eg. "1//" */
auto prefix = endpos.prefix(i); auto prefix = endpos.prefix(i);
if(!prefix.empty()) { if(!prefix.empty()) {
indices[2] = strToInt(prefix); indices[2] = strToInt(prefix);
@ -336,27 +343,26 @@ std::tuple<std::array<int, 3>, ArrayView<char>> parseVertex(const ArrayView<char
} }
} }
return std::make_tuple(indices, endpos); return {indices, endpos};
} }
template<UnsignedInt D> template<UnsignedInt dimensions> Math::Vector<dimensions, Float> parseVector(ArrayView<const char>& pos) {
ArrayView<char> getVector(ArrayView<char> pos, Math::Vector<D, Float>& v) { ArrayView<const char> word;
ArrayView<char> word; Math::Vector<dimensions, Float> v;
for(int i = 0; i < int(D); ++i) { for(Int i = 0; i < Int(dimensions); ++i) {
pos = skipWhitespaces(pos); std::tie(word, pos) = nextWord(skipWhitespaces(pos));
std::tie(word, pos) = nextWord(pos);
v[i] = strToFloat(word); v[i] = strToFloat(word);
} }
return pos; return v;
} }
template<class T> std::vector<T> reindex(const std::vector<UnsignedInt>& indices, std::vector<T>& data) { template<class T> std::vector<T> reindex(const std::vector<UnsignedInt>& indices, const std::vector<T>& data) {
/* Check that indices are in range */ /* Check that indices are in range */
for(UnsignedInt i: indices) if(i >= data.size()) { for(UnsignedInt i: indices) if(i >= data.size()) {
Error() << "Trade::ObjImporter::mesh3D(): index out of range"; Error() << "Trade::ObjImporter::mesh3D(): index out of range";
throw 0; return {};
} }
return MeshTools::duplicate(indices, data); return MeshTools::duplicate(indices, data);
@ -372,32 +378,32 @@ ObjImporter::~ObjImporter() = default;
auto ObjImporter::doFeatures() const -> Features { return Feature::OpenData; } 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) { 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); 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()}; _state->in = Containers::Array<char>{data.size()};
std::copy(data.begin(), data.end(), _in.begin()); std::copy(data.begin(), data.end(), _state->in.begin());
_state.reset(new ImporterState); _state.reset(new ImporterState);
parse(); parse();
} }
void ObjImporter::parse() { void ObjImporter::parse() {
ArrayView<char> line = _in; /* points to beginning of current line */ ArrayView<const char> line = _state->in; /* Points to beginning of current line */
ArrayView<char> pos = _in; /* points to current character in line */ ArrayView<const char> pos = _state->in; /* Points to current character in line */
ObjObject* object = nullptr; ObjObject* object = nullptr;
ObjGroup* group = nullptr; ObjGroup* group = nullptr;
ObjMeshData* meshData = nullptr; ObjMeshData* meshData = nullptr;
ArrayView<char> section{nullptr}; ArrayView<const char> section{nullptr};
int minSectionPrimitives = 0; Int minSectionPrimitives = 0;
char sectionPrimitive = '?'; char sectionPrimitive = '?';
/* Set index 0 of data to default value */ /* Set index 0 of data to default value */
@ -444,12 +450,12 @@ void ObjImporter::parse() {
ObjMesh* mesh; ObjMesh* mesh;
if(sectionPrimitive == 'p') { if(sectionPrimitive == 'p') {
if(!meshData->points) { if(!meshData->poInts) {
// TODO: C++ 17 // TODO: C++ 17
_state->meshes.emplace_back(new ObjMesh{*meshData, MeshPrimitive::Points}); _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') { } else if(sectionPrimitive == 'l') {
if(!meshData->lines) { if(!meshData->lines) {
// TODO: C++ 17 // TODO: C++ 17
@ -457,13 +463,15 @@ void ObjImporter::parse() {
meshData->lines = _state->meshes.back().get(); meshData->lines = _state->meshes.back().get();
} }
mesh = meshData->lines; mesh = meshData->lines;
} else { /* sectionPrimitive == 'f' */ } else if(sectionPrimitive == 'f') {
if(!meshData->faces) { if(!meshData->faces) {
// TODO: C++ 17 // TODO: C++ 17
_state->meshes.emplace_back(new ObjMesh{*meshData, MeshPrimitive::Triangles}); _state->meshes.emplace_back(new ObjMesh{*meshData, MeshPrimitive::Triangles});
meshData->faces = _state->meshes.back().get(); meshData->faces = _state->meshes.back().get();
} }
mesh = meshData->faces; mesh = meshData->faces;
} else {
CORRADE_ASSERT_UNREACHABLE();
} }
if(line.data() == nullptr) { if(line.data() == nullptr) {
/* Usually for the last line */ /* Usually for the last line */
@ -495,7 +503,7 @@ void ObjImporter::parse() {
/* Parse the keyword */ /* Parse the keyword */
std::string keyword; std::string keyword;
ArrayView<char> word; ArrayView<const char> word;
std::tie(word, pos) = nextWord(pos); std::tie(word, pos) = nextWord(pos);
keyword = std::string(word.data(), word.size()); keyword = std::string(word.data(), word.size());
@ -504,23 +512,15 @@ void ObjImporter::parse() {
/* Vertex position */ /* Vertex position */
if(keyword == "v") { if(keyword == "v") {
// TODO C++17: Could be one slick line with emplace_back ref return. _state->positions.push_back(parseVector<3>(pos));
Vector3 v;
pos = getVector<3>(pos, v);
_state->positions.push_back(v);
/* Texture coordinate */ /* Texture coordinate */
} else if(keyword == "vt") { } else if(keyword == "vt") {
// TODO C++17: Could be one slick line with emplace_back ref return. _state->texCoords.push_back(parseVector<2>(pos));
Vector2 tc;
pos = getVector<2>(pos, tc);
_state->texCoords.push_back(tc);
/* Normal */ /* Normal */
} else if(keyword == "vn") { } else if(keyword == "vn") {
// TODO C++17: Could be one slick line with emplace_back ref return. _state->normals.push_back(parseVector<3>(pos));
Vector3 n;
pos = getVector<3>(pos, n);
_state->normals.push_back(n);
/* Indices */ /* Indices */
} else if(keyword == "f" || keyword == "l" || keyword == "p") { } else if(keyword == "f" || keyword == "l" || keyword == "p") {
@ -544,7 +544,7 @@ void ObjImporter::parse() {
finishSection(); finishSection();
finishObject(); finishObject();
ArrayView<char> name; ArrayView<const char> name;
std::tie(name, pos) = nextWord(pos); std::tie(name, pos) = nextWord(pos);
_state->objects.emplace_back(new ObjObject{name}); _state->objects.emplace_back(new ObjObject{name});
@ -561,13 +561,13 @@ void ObjImporter::parse() {
meshData = nullptr; meshData = nullptr;
ArrayView<char> name; ArrayView<const char> name;
std::tie(name, pos) = nextWord(pos); std::tie(name, pos) = nextWord(pos);
group->name = std::string{name.data(), name.size()}; group->name = std::string{name.data(), name.size()};
/* Load a material library */ /* Load a material library */
} else if(keyword == "mtllib") { } else if(keyword == "mtllib") {
ArrayView<char> word; ArrayView<const char> word;
pos = skipWhitespaces(pos); pos = skipWhitespaces(pos);
std::tie(word, pos) = nextWord(pos); std::tie(word, pos) = nextWord(pos);
@ -575,11 +575,11 @@ void ObjImporter::parse() {
/* Set current material and add a new mesh for it */ /* Set current material and add a new mesh for it */
} else if(keyword == "usemtl") { } else if(keyword == "usemtl") {
ArrayView<char> word; ArrayView<const char> word;
pos = skipWhitespaces(pos); pos = skipWhitespaces(pos);
std::tie(word, pos) = nextWord(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) { if(meshData == nullptr || materialIndex != meshData->materialId) {
/* Switching the material here, need to create a new mesh */ /* Switching the material here, need to create a new mesh */
//TODO C++17 //TODO C++17
@ -603,14 +603,14 @@ void ObjImporter::parse() {
finishSection(); finishSection();
finishObject(); finishObject();
int i = _state->meshes.size(); Int i = _state->meshes.size();
for(auto name : _state->meshlessObjects) { for(auto name : _state->meshlessObjects) {
_state->meshlessObjectIds[name] = i++; _state->meshlessObjectIds[name] = i++;
} }
} }
void ObjImporter::parseMaterialLibrary(const ArrayView<char> libname) { void ObjImporter::parseMaterialLibrary(const ArrayView<const char> libname) {
std::string filename = _fileRoot + std::string(libname.data(), libname.size()); std::string filename = _state->fileRoot + std::string(libname.data(), libname.size());
/* Open file */ /* Open file */
if(!Utility::Directory::fileExists(filename)) { if(!Utility::Directory::fileExists(filename)) {
@ -619,7 +619,7 @@ void ObjImporter::parseMaterialLibrary(const ArrayView<char> libname) {
} }
Containers::Array<char> contents = Utility::Directory::read(filename); Containers::Array<char> contents = Utility::Directory::read(filename);
ArrayView<char> pos = contents; /* points to current character in line */ ArrayView<const char> pos = contents; /* Points to current character in line */
ObjMaterial* mat = nullptr; ObjMaterial* mat = nullptr;
@ -633,7 +633,7 @@ void ObjImporter::parseMaterialLibrary(const ArrayView<char> libname) {
} }
/* Parse the keyword */ /* Parse the keyword */
ArrayView<char> word; ArrayView<const char> word;
std::tie(word, pos) = nextWord(pos); std::tie(word, pos) = nextWord(pos);
std::string keyword{word.data(), word.size()}; std::string keyword{word.data(), word.size()};
@ -659,49 +659,49 @@ void ObjImporter::parseMaterialLibrary(const ArrayView<char> libname) {
/* Ambient color */ /* Ambient color */
if(keyword == "Ka") { if(keyword == "Ka") {
ArrayView<char> word; ArrayView<const char> word;
for(int i : {0, 1, 2}) { for(Int i : {0, 1, 2}) {
std::tie(word, pos) = nextWord(pos); std::tie(word, pos) = nextWord(pos);
mat->ambient[i] = strToFloat(word); mat->ambient[i] = strToFloat(word);
} }
/* Diffuse color */ /* Diffuse color */
} else if(keyword == "Kd") { } else if(keyword == "Kd") {
ArrayView<char> word; ArrayView<const char> word;
for(int i : {0, 1, 2}) { for(Int i : {0, 1, 2}) {
std::tie(word, pos) = nextWord(pos); std::tie(word, pos) = nextWord(pos);
mat->diffuse[i] = strToFloat(word); mat->diffuse[i] = strToFloat(word);
} }
/* Specular color */ /* Specular color */
} else if(keyword == "Ks") { } else if(keyword == "Ks") {
ArrayView<char> word; ArrayView<const char> word;
for(int i : {0, 1, 2}) { for(Int i : {0, 1, 2}) {
std::tie(word, pos) = nextWord(pos); std::tie(word, pos) = nextWord(pos);
mat->specular[i] = strToFloat(word); mat->specular[i] = strToFloat(word);
} }
/* Specularity */ /* Specularity */
} else if(keyword == "Ns") { } else if(keyword == "Ns") {
ArrayView<char> word; ArrayView<const char> word;
std::tie(word, pos) = nextWord(pos); std::tie(word, pos) = nextWord(pos);
const float f = strToFloat(word); const Float f = strToFloat(word);
mat->specularity = f; mat->specularity = f;
/* Ambient texture */ /* Ambient texture */
} else if(keyword.substr(0, 4) == "map_") { } else if(keyword.substr(0, 4) == "map_") {
ArrayView<char> line; ArrayView<const char> line;
std::tie(line, pos) = nextLine(pos); std::tie(line, pos) = nextLine(pos);
std::string texture{line.data(), line.size()}; std::string texture{line.data(), line.size()};
int textureId = -1; Int textureId = -1;
if(_state->textureIds.find(texture) == _state->textureIds.end()) { if(_state->textureIds.find(texture) == _state->textureIds.end()) {
/* new texture, create it */ /* new texture, create it */
int index = _state->textures.size(); Int index = _state->textures.size();
_state->textures.push_back(texture); _state->textures.push_back(texture);
_state->textureIds[texture] = index; _state->textureIds[texture] = index;
@ -731,12 +731,6 @@ void ObjImporter::parseMaterialLibrary(const ArrayView<char> 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(); } UnsignedInt ObjImporter::doObject3DCount() const { return _state->meshes.size() + _state->meshlessObjects.size(); }
Int ObjImporter::doObject3DForName(const std::string& name) { Int ObjImporter::doObject3DForName(const std::string& name) {
@ -754,23 +748,25 @@ Int ObjImporter::doObject3DForName(const std::string& name) {
} }
std::string ObjImporter::doObject3DName(UnsignedInt id) { std::string ObjImporter::doObject3DName(UnsignedInt id) {
const size_t numMeshes = _state->meshes.size(); const size_t lastMesh = _state->meshes.size() - 1;
if(id >= numMeshes) { if(id > lastMesh) {
return _state->meshlessObjects[id - numMeshes]; return _state->meshlessObjects[id - lastMesh];
} }
return _state->meshes[id]->name(); /* Intentional, objects are just meshes + material */ return _state->meshes[id]->name();
} }
std::unique_ptr<ObjectData3D> ObjImporter::doObject3D(UnsignedInt id) { std::unique_ptr<ObjectData3D> ObjImporter::doObject3D(UnsignedInt id) {
const size_t numMeshes = _state->meshes.size(); const size_t lastMesh = _state->meshes.size() - 1;
if(id > numMeshes) { if(id > lastMesh) {
return std::unique_ptr<ObjectData3D>{new ObjectData3D{{}, {}, &_state->meshlessObjects[id - numMeshes]}}; return std::unique_ptr<ObjectData3D>{new ObjectData3D{{}, {}, &_state->meshlessObjects[id - lastMesh]}};
} }
const ObjMesh& mesh = *_state->meshes[id]; const ObjMesh& mesh = *_state->meshes[id];
return std::unique_ptr<ObjectData3D>{ return std::unique_ptr<ObjectData3D>{
new MeshObjectData3D{{}, {}, id, mesh.data.materialId, _state->meshes[id].get()}}; 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) { Int ObjImporter::doMesh3DForName(const std::string& name) {
return _state->meshIds[name]; return _state->meshIds[name];
} }
@ -782,7 +778,7 @@ std::string ObjImporter::doMesh3DName(UnsignedInt id) {
std::optional<MeshData3D> ObjImporter::doMesh3D(UnsignedInt id) { std::optional<MeshData3D> ObjImporter::doMesh3D(UnsignedInt id) {
const ObjMesh& mesh = *_state->meshes[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); ? 3 : ((mesh.primitive == MeshPrimitive::Lines) ? 2 : 1);
std::vector<UnsignedInt> positionIndices; std::vector<UnsignedInt> positionIndices;
@ -812,8 +808,8 @@ std::optional<MeshData3D> ObjImporter::doMesh3D(UnsignedInt id) {
CORRADE_ASSERT(pos[0] == 'f' && pos[1] == ' ', "Unexpected primitive keyword for Triangles", {}); CORRADE_ASSERT(pos[0] == 'f' && pos[1] == ' ', "Unexpected primitive keyword for Triangles", {});
pos = pos.suffix(2); pos = pos.suffix(2);
for(int i = 0; i < 3; ++i) { for(Int i = 0; i < 3; ++i) {
std::array<int, 3> vertex; std::array<Int, 3> vertex;
std::tie(vertex, pos) = parseVertex(pos); std::tie(vertex, pos) = parseVertex(pos);
positionIndices.push_back(vertex[0]); positionIndices.push_back(vertex[0]);
textureCoordinateIndices.push_back(vertex[1]); textureCoordinateIndices.push_back(vertex[1]);
@ -833,7 +829,7 @@ std::optional<MeshData3D> ObjImporter::doMesh3D(UnsignedInt id) {
pos = pos.suffix(2); pos = pos.suffix(2);
while(!atEndOfLine(pos)) { while(!atEndOfLine(pos)) {
std::array<int, 2> line; std::array<Int, 2> line;
std::tie(line, pos) = parseLine(pos); std::tie(line, pos) = parseLine(pos);
positionIndices.push_back(line[0]); positionIndices.push_back(line[0]);
textureCoordinateIndices.push_back(line[1]); textureCoordinateIndices.push_back(line[1]);
@ -850,7 +846,7 @@ std::optional<MeshData3D> ObjImporter::doMesh3D(UnsignedInt id) {
CORRADE_ASSERT(pos[0] == 'p' && pos[1] == ' ', "Unexpected primitive keyword for Points", {}); CORRADE_ASSERT(pos[0] == 'p' && pos[1] == ' ', "Unexpected primitive keyword for Points", {});
pos = pos.suffix(2); pos = pos.suffix(2);
ArrayView<char> word; ArrayView<const char> word;
while(!atEndOfLine(pos)) { while(!atEndOfLine(pos)) {
std::tie(word, pos) = nextWord(pos); std::tie(word, pos) = nextWord(pos);
positionIndices.push_back(strToInt(word)); positionIndices.push_back(strToInt(word));
@ -879,19 +875,16 @@ std::optional<MeshData3D> ObjImporter::doMesh3D(UnsignedInt id) {
indices = MeshTools::combineIndexArrays(arrays); indices = MeshTools::combineIndexArrays(arrays);
/* Reindex data arrays */ /* Reindex data arrays */
try { positionLayers.push_back(reindex(positionIndices, _state->positions));
positionLayers.push_back(reindex(positionIndices, _state->positions)); if(hasNormals) normalLayers.push_back(reindex(normalIndices, _state->normals));
if(hasNormals) normalLayers.push_back(reindex(normalIndices, _state->normals)); if(hasTexCoords) texCoordLayers.push_back(reindex(textureCoordinateIndices, _state->texCoords));
if(hasTexCoords) texCoordLayers.push_back(reindex(textureCoordinateIndices, _state->texCoords));
} catch(...) {
/* Error message already printed */
return std::nullopt;
}
return MeshData3D(mesh.primitive, std::move(indices), std::move(positionLayers), return MeshData3D(mesh.primitive, std::move(indices), std::move(positionLayers),
std::move(normalLayers), std::move(texCoordLayers), {}, &mesh); std::move(normalLayers), std::move(texCoordLayers), {}, &mesh);
} }
UnsignedInt ObjImporter::doMaterialCount() const { return _state->materials.size(); }
std::unique_ptr<AbstractMaterialData> ObjImporter::doMaterial(const UnsignedInt id) { std::unique_ptr<AbstractMaterialData> ObjImporter::doMaterial(const UnsignedInt id) {
ObjMaterial& objMat = _state->materials[id]; ObjMaterial& objMat = _state->materials[id];
PhongMaterialData::Flags flags; PhongMaterialData::Flags flags;
@ -931,11 +924,13 @@ std::unique_ptr<AbstractMaterialData> ObjImporter::doMaterial(const UnsignedInt
return std::unique_ptr<AbstractMaterialData>(mat); return std::unique_ptr<AbstractMaterialData>(mat);
} }
UnsignedInt ObjImporter::doImage2DCount() const { return _state->textures.size(); }
std::optional<ImageData2D> ObjImporter::doImage2D(UnsignedInt id) { 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", {}); 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... std::unique_ptr<AbstractImporter> 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; return std::nullopt;
} }

4
src/MagnumPlugins/ObjImporter/ObjImporter.h

@ -112,11 +112,9 @@ class MAGNUM_OBJIMPORTER_EXPORT ObjImporter: public AbstractImporter {
MAGNUM_OBJIMPORTER_LOCAL std::optional<ImageData2D> doImage2D(UnsignedInt id); MAGNUM_OBJIMPORTER_LOCAL std::optional<ImageData2D> doImage2D(UnsignedInt id);
MAGNUM_OBJIMPORTER_LOCAL void parse(); MAGNUM_OBJIMPORTER_LOCAL void parse();
MAGNUM_OBJIMPORTER_LOCAL void parseMaterialLibrary(Containers::ArrayView<char> libname); MAGNUM_OBJIMPORTER_LOCAL void parseMaterialLibrary(Containers::ArrayView<const char> libname);
Containers::Array<char> _in;
std::unique_ptr<struct ImporterState> _state; std::unique_ptr<struct ImporterState> _state;
std::string _fileRoot;
}; };
}} }}

14
src/MagnumPlugins/ObjImporter/Test/Test.cpp

@ -429,6 +429,7 @@ void ObjImporterTest::wrongFloat() {
std::ostringstream out; std::ostringstream out;
Error redirectError{&out}; Error redirectError{&out};
CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): error while converting numeric data\n"); CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): error while converting numeric data\n");
CORRADE_VERIFY(!importer.mesh3D(id));
} }
void ObjImporterTest::wrongInteger() { void ObjImporterTest::wrongInteger() {
@ -735,12 +736,12 @@ void ObjImporterTest::multiMaterialObject() {
CORRADE_COMPARE(data->primitive(), MeshPrimitive::Triangles); CORRADE_COMPARE(data->primitive(), MeshPrimitive::Triangles);
CORRADE_COMPARE(data->positionArrayCount(), 1); CORRADE_COMPARE(data->positionArrayCount(), 1);
CORRADE_COMPARE(data->positions(0), (std::vector<Vector3>{ CORRADE_COMPARE(data->positions(0), (std::vector<Vector3>{
{Vector3(1.72414, 18.9233, -3.20162), {{1.72414f, 18.9233f, -3.20162f},
Vector3(2.74428, -0.499733, -3.50576), {2.74428f, -0.499733f, -3.50576f},
Vector3(-1.92235, -0.846268, 2.9722), {-1.92235f, -0.846268f, 2.9722f},
Vector3(1.72414, 18.9233, -3.20162), {1.72414f, 18.9233f, -3.20162f},
Vector3(-1.92235, -0.846268, 2.9722), {-1.92235f, -0.846268f, 2.9722f},
Vector3(2.43556, 18.8755, 2.23745)} {2.43556f, 18.8755f, 2.23745f}}
})); }));
data = importer.mesh3D(1); data = importer.mesh3D(1);
@ -762,6 +763,7 @@ void ObjImporterTest::unsupportedKeyword() {
CORRADE_COMPARE(data->positions(0), (std::vector<Vector3>{ CORRADE_COMPARE(data->positions(0), (std::vector<Vector3>{
{0.0f, 1.0f, 2.0f} {0.0f, 1.0f, 2.0f}
})); }));
CORRADE_COMPARE(data->indices(), std::vector<UnsignedInt>{0});
} }
void ObjImporterTest::unknownKeyword() { void ObjImporterTest::unknownKeyword() {

1
src/MagnumPlugins/ObjImporter/Test/multiMaterial.mtl

@ -6,3 +6,4 @@ map_Ka Textures/mat_0.tga
newmtl mat_1 newmtl mat_1
Ka 1 1 1 Ka 1 1 1
map_Ka Textures/mat_1.tga map_Ka Textures/mat_1.tga

1
src/MagnumPlugins/ObjImporter/Test/multiMaterial.obj

@ -56,3 +56,4 @@ usemtl mat_1
# faces # faces
f 5/5/5 6/6/6 7/7/7 f 5/5/5 6/6/6 7/7/7
f 5/5/5 7/7/7 8/8/8 f 5/5/5 7/7/7 8/8/8

Loading…
Cancel
Save