Merge pull request #4452 from sethalves/obj-reader-fixes

Obj reader fixes
This commit is contained in:
Andrew Meadows 2015-03-16 11:35:51 -07:00
commit e18d9fd81c
3 changed files with 226 additions and 50 deletions

View file

@ -31,6 +31,7 @@ public:
}; };
int nextToken(); int nextToken();
const QByteArray& getDatum() const { return _datum; } const QByteArray& getDatum() const { return _datum; }
bool isNextTokenFloat();
void skipLine() { _device->readLine(); } void skipLine() { _device->readLine(); }
void pushBackToken(int token) { _pushedBackToken = token; } void pushBackToken(int token) { _pushedBackToken = token; }
void ungetChar(char ch) { _device->ungetChar(ch); } void ungetChar(char ch) { _device->ungetChar(ch); }
@ -91,14 +92,32 @@ int OBJTokenizer::nextToken() {
return NO_TOKEN; return NO_TOKEN;
} }
bool OBJTokenizer::isNextTokenFloat() {
if (nextToken() != OBJTokenizer::DATUM_TOKEN) {
return false;
}
QByteArray token = getDatum();
pushBackToken(OBJTokenizer::DATUM_TOKEN);
bool ok;
token.toFloat(&ok);
return ok;
}
bool parseOBJGroup(OBJTokenizer &tokenizer, const QVariantHash& mapping, FBXGeometry &geometry) { bool parseOBJGroup(OBJTokenizer &tokenizer, const QVariantHash& mapping,
FBXGeometry &geometry, QVector<glm::vec3>& faceNormals, QVector<int>& faceNormalIndexes) {
FBXMesh &mesh = geometry.meshes[0]; FBXMesh &mesh = geometry.meshes[0];
mesh.parts.append(FBXMeshPart()); mesh.parts.append(FBXMeshPart());
FBXMeshPart &meshPart = mesh.parts.last(); FBXMeshPart &meshPart = mesh.parts.last();
bool sawG = false; bool sawG = false;
bool result = true; bool result = true;
meshPart.diffuseColor = glm::vec3(1, 1, 1);
meshPart.specularColor = glm::vec3(1, 1, 1);
meshPart.emissiveColor = glm::vec3(0, 0, 0);
meshPart.emissiveParams = glm::vec2(0, 1);
meshPart.shininess = 40;
meshPart.opacity = 1;
meshPart.materialID = QString("dontknow") + QString::number(mesh.parts.count()); meshPart.materialID = QString("dontknow") + QString::number(mesh.parts.count());
meshPart.opacity = 1.0; meshPart.opacity = 1.0;
meshPart._material = model::MaterialPointer(new model::Material()); meshPart._material = model::MaterialPointer(new model::Material());
@ -131,11 +150,27 @@ bool parseOBJGroup(OBJTokenizer &tokenizer, const QVariantHash& mapping, FBXGeom
break; break;
} }
float x = std::stof(tokenizer.getDatum().data()); float x = std::stof(tokenizer.getDatum().data());
// notice the order of z and y -- in OBJ files, up is the 3rd value
if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) {
break;
}
float y = std::stof(tokenizer.getDatum().data());
if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) { if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) {
break; break;
} }
float z = std::stof(tokenizer.getDatum().data()); float z = std::stof(tokenizer.getDatum().data());
while (tokenizer.isNextTokenFloat()) {
// the spec(s) get(s) vague here. might be w, might be a color... chop it off.
tokenizer.nextToken();
}
mesh.vertices.append(glm::vec3(x, y, z));
} else if (token == "vn") {
if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) {
break;
}
float x = std::stof(tokenizer.getDatum().data());
if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) { if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) {
break; break;
} }
@ -143,35 +178,74 @@ bool parseOBJGroup(OBJTokenizer &tokenizer, const QVariantHash& mapping, FBXGeom
if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) { if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) {
break; break;
} }
// the spec gets vague here. might be w, might be a color... chop it off. float z = std::stof(tokenizer.getDatum().data());
tokenizer.skipLine();
mesh.vertices.append(glm::vec3(x, y, z)); while (tokenizer.isNextTokenFloat()) {
mesh.colors.append(glm::vec3(1, 1, 1)); // the spec gets vague here. might be w
tokenizer.nextToken();
}
faceNormals.append(glm::vec3(x, y, z));
} else if (token == "f") { } else if (token == "f") {
// a face can have 3 or more vertices // a face can have 3 or more vertices
QVector<int> indices; QVector<int> indices;
QVector<int> normalIndices;
while (true) { while (true) {
if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) { goto done; } if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) {
try { if (indices.count() == 0) {
int vertexIndex = std::stoi(tokenizer.getDatum().data()); goto done;
// negative indexes count backward from the current end of the vertex list }
vertexIndex = (vertexIndex >= 0 ? vertexIndex : mesh.vertices.count() + vertexIndex + 1); break;
// obj index is 1 based
assert(vertexIndex >= 1);
indices.append(vertexIndex - 1);
} }
catch(const std::exception& e) { // faces can be:
// wasn't a number, but it back. // vertex-index
// vertex-index/texture-index
// vertex-index/texture-index/surface-normal-index
QByteArray token = tokenizer.getDatum();
QList<QByteArray> parts = token.split('/');
assert(parts.count() >= 1);
assert(parts.count() <= 3);
QByteArray vertIndexBA = parts[ 0 ];
bool ok;
int vertexIndex = vertIndexBA.toInt(&ok);
if (!ok) {
// it wasn't #/#/#, put it back and exit this loop.
tokenizer.pushBackToken(OBJTokenizer::DATUM_TOKEN); tokenizer.pushBackToken(OBJTokenizer::DATUM_TOKEN);
break; break;
} }
// if (parts.count() > 1) {
// QByteArray textureIndexBA = parts[ 1 ];
// }
if (parts.count() > 2) {
QByteArray normalIndexBA = parts[ 2 ];
bool ok;
int normalIndex = normalIndexBA.toInt(&ok);
if (ok) {
normalIndices.append(normalIndex - 1);
}
}
// negative indexes count backward from the current end of the vertex list
vertexIndex = (vertexIndex >= 0 ? vertexIndex : mesh.vertices.count() + vertexIndex + 1);
// obj index is 1 based
assert(vertexIndex >= 1);
indices.append(vertexIndex - 1);
} }
if (indices.count() == 3) { if (indices.count() == 3) {
// flip these around (because of the y/z swap above) so our triangles face outward
meshPart.triangleIndices.append(indices[0]); meshPart.triangleIndices.append(indices[0]);
meshPart.triangleIndices.append(indices[2]);
meshPart.triangleIndices.append(indices[1]); meshPart.triangleIndices.append(indices[1]);
meshPart.triangleIndices.append(indices[2]);
if (normalIndices.count() == 3) {
faceNormalIndexes.append(normalIndices[0]);
faceNormalIndexes.append(normalIndices[1]);
faceNormalIndexes.append(normalIndices[2]);
} else {
// hmm.
}
} else if (indices.count() == 4) { } else if (indices.count() == 4) {
meshPart.quadIndices << indices; meshPart.quadIndices << indices;
} else { } else {
@ -205,6 +279,10 @@ FBXGeometry readOBJ(const QByteArray& model, const QVariantHash& mapping) {
FBXGeometry readOBJ(QIODevice* device, const QVariantHash& mapping) { FBXGeometry readOBJ(QIODevice* device, const QVariantHash& mapping) {
FBXGeometry geometry; FBXGeometry geometry;
OBJTokenizer tokenizer(device); OBJTokenizer tokenizer(device);
QVector<int> faceNormalIndexes;
QVector<glm::vec3> faceNormals;
faceNormalIndexes.clear();
geometry.meshExtents.reset(); geometry.meshExtents.reset();
geometry.meshes.append(FBXMesh()); geometry.meshes.append(FBXMesh());
@ -216,7 +294,7 @@ FBXGeometry readOBJ(QIODevice* device, const QVariantHash& mapping) {
// add a new meshPart to the geometry's single mesh. // add a new meshPart to the geometry's single mesh.
bool success = true; bool success = true;
while (success) { while (success) {
success = parseOBJGroup(tokenizer, mapping, geometry); success = parseOBJGroup(tokenizer, mapping, geometry, faceNormals, faceNormalIndexes);
} }
FBXMesh &mesh = geometry.meshes[0]; FBXMesh &mesh = geometry.meshes[0];
@ -229,31 +307,34 @@ FBXGeometry readOBJ(QIODevice* device, const QVariantHash& mapping) {
geometry.joints.resize(1); geometry.joints.resize(1);
geometry.joints[0].isFree = false; geometry.joints[0].isFree = false;
// geometry.joints[0].freeLineage;
geometry.joints[0].parentIndex = -1; geometry.joints[0].parentIndex = -1;
geometry.joints[0].distanceToParent = 0; geometry.joints[0].distanceToParent = 0;
geometry.joints[0].boneRadius = 0; geometry.joints[0].boneRadius = 0;
geometry.joints[0].translation = glm::vec3(0, 0, 0); geometry.joints[0].translation = glm::vec3(0, 0, 0);
// geometry.joints[0].preTransform = ;
geometry.joints[0].preRotation = glm::quat(0, 0, 0, 1);
geometry.joints[0].rotation = glm::quat(0, 0, 0, 1);
geometry.joints[0].postRotation = glm::quat(0, 0, 0, 1);
// geometry.joints[0].postTransform = ;
// geometry.joints[0].transform = ;
geometry.joints[0].rotationMin = glm::vec3(0, 0, 0); geometry.joints[0].rotationMin = glm::vec3(0, 0, 0);
geometry.joints[0].rotationMax = glm::vec3(0, 0, 0); geometry.joints[0].rotationMax = glm::vec3(0, 0, 0);
geometry.joints[0].inverseDefaultRotation = glm::quat(0, 0, 0, 1);
geometry.joints[0].inverseBindRotation = glm::quat(0, 0, 0, 1);
// geometry.joints[0].bindTransform = ;
geometry.joints[0].name = "OBJ"; geometry.joints[0].name = "OBJ";
geometry.joints[0].shapePosition = glm::vec3(0, 0, 0); geometry.joints[0].shapePosition = glm::vec3(0, 0, 0);
geometry.joints[0].shapeRotation = glm::quat(0, 0, 0, 1);
geometry.joints[0].shapeType = SPHERE_SHAPE; geometry.joints[0].shapeType = SPHERE_SHAPE;
geometry.joints[0].isSkeletonJoint = false; geometry.joints[0].isSkeletonJoint = true;
geometry.jointIndices["x"] = 1;
FBXCluster cluster;
cluster.jointIndex = 0;
cluster.inverseBindMatrix = glm::mat4(1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1);
mesh.clusters.append(cluster);
// The OBJ format has normals for faces. The FBXGeometry structure has normals for points.
// run through all the faces, look-up (or determine) a normal and set the normal for the points
// that make up each face.
QVector<glm::vec3> pointNormalsSums;
// add bogus normal data for this mesh
mesh.normals.fill(glm::vec3(0,0,0), mesh.vertices.count()); mesh.normals.fill(glm::vec3(0,0,0), mesh.vertices.count());
mesh.tangents.fill(glm::vec3(0,0,0), mesh.vertices.count()); pointNormalsSums.fill(glm::vec3(0,0,0), mesh.vertices.count());
foreach (FBXMeshPart meshPart, mesh.parts) { foreach (FBXMeshPart meshPart, mesh.parts) {
int triCount = meshPart.triangleIndices.count() / 3; int triCount = meshPart.triangleIndices.count() / 3;
@ -262,21 +343,43 @@ FBXGeometry readOBJ(QIODevice* device, const QVariantHash& mapping) {
int p1Index = meshPart.triangleIndices[i*3+1]; int p1Index = meshPart.triangleIndices[i*3+1];
int p2Index = meshPart.triangleIndices[i*3+2]; int p2Index = meshPart.triangleIndices[i*3+2];
glm::vec3 p0 = mesh.vertices[p0Index]; assert(p0Index < mesh.vertices.count());
glm::vec3 p1 = mesh.vertices[p1Index]; assert(p1Index < mesh.vertices.count());
glm::vec3 p2 = mesh.vertices[p2Index]; assert(p2Index < mesh.vertices.count());
glm::vec3 n = glm::cross(p1 - p0, p2 - p0); glm::vec3 n0, n1, n2;
glm::vec3 t = glm::cross(p2 - p0, n); if (i < faceNormalIndexes.count()) {
int n0Index = faceNormalIndexes[i*3];
int n1Index = faceNormalIndexes[i*3+1];
int n2Index = faceNormalIndexes[i*3+2];
n0 = faceNormals[n0Index];
n1 = faceNormals[n1Index];
n2 = faceNormals[n2Index];
} else {
// We didn't read normals, add bogus normal data for this face
glm::vec3 p0 = mesh.vertices[p0Index];
glm::vec3 p1 = mesh.vertices[p1Index];
glm::vec3 p2 = mesh.vertices[p2Index];
n0 = glm::cross(p1 - p0, p2 - p0);
n1 = n0;
n2 = n0;
}
mesh.normals[p0Index] = n; // we sum up the normal for each point and then divide by the count to get an average
mesh.normals[p1Index] = n; pointNormalsSums[p0Index] += n0;
mesh.normals[p2Index] = n; pointNormalsSums[p1Index] += n1;
pointNormalsSums[p2Index] += n2;
mesh.tangents[p0Index] = t;
mesh.tangents[p1Index] = t;
mesh.tangents[p2Index] = t;
} }
int vertCount = mesh.vertices.count();
for (int i = 0; i < vertCount; i++) {
float length = glm::length(pointNormalsSums[i]);
if (length > FLT_EPSILON) {
mesh.normals[i] = glm::normalize(pointNormalsSums[i]);
}
}
// XXX do same normal calculation for quadCount
} }
} }
catch(const std::exception& e) { catch(const std::exception& e) {
@ -285,3 +388,77 @@ FBXGeometry readOBJ(QIODevice* device, const QVariantHash& mapping) {
return geometry; return geometry;
} }
void fbxDebugDump(const FBXGeometry& fbxgeo) {
qDebug() << "---------------- fbxGeometry ----------------";
qDebug() << " hasSkeletonJoints =" << fbxgeo.hasSkeletonJoints;
qDebug() << " offset =" << fbxgeo.offset;
qDebug() << " attachments.count() = " << fbxgeo.attachments.count();
qDebug() << " meshes.count() =" << fbxgeo.meshes.count();
foreach (FBXMesh mesh, fbxgeo.meshes) {
qDebug() << " vertices.count() =" << mesh.vertices.count();
qDebug() << " normals.count() =" << mesh.normals.count();
if (mesh.normals.count() == mesh.vertices.count()) {
for (int i = 0; i < mesh.normals.count(); i++) {
qDebug() << " " << mesh.vertices[ i ] << mesh.normals[ i ];
}
}
qDebug() << " tangents.count() =" << mesh.tangents.count();
qDebug() << " colors.count() =" << mesh.colors.count();
qDebug() << " texCoords.count() =" << mesh.texCoords.count();
qDebug() << " texCoords1.count() =" << mesh.texCoords1.count();
qDebug() << " clusterIndices.count() =" << mesh.clusterIndices.count();
qDebug() << " clusterWeights.count() =" << mesh.clusterWeights.count();
qDebug() << " meshExtents =" << mesh.meshExtents;
qDebug() << " modelTransform =" << mesh.modelTransform;
qDebug() << " parts.count() =" << mesh.parts.count();
foreach (FBXMeshPart meshPart, mesh.parts) {
qDebug() << " quadIndices.count() =" << meshPart.quadIndices.count();
qDebug() << " triangleIndices.count() =" << meshPart.triangleIndices.count();
qDebug() << " diffuseColor =" << meshPart.diffuseColor;
qDebug() << " specularColor =" << meshPart.specularColor;
qDebug() << " emissiveColor =" << meshPart.emissiveColor;
qDebug() << " emissiveParams =" << meshPart.emissiveParams;
qDebug() << " shininess =" << meshPart.shininess;
qDebug() << " opacity =" << meshPart.opacity;
qDebug() << " materialID =" << meshPart.materialID;
}
qDebug() << " clusters.count() =" << mesh.clusters.count();
foreach (FBXCluster cluster, mesh.clusters) {
qDebug() << " jointIndex =" << cluster.jointIndex;
qDebug() << " inverseBindMatrix =" << cluster.inverseBindMatrix;
}
}
qDebug() << " jointIndices =" << fbxgeo.jointIndices;
qDebug() << " joints.count() =" << fbxgeo.joints.count();
foreach (FBXJoint joint, fbxgeo.joints) {
qDebug() << " isFree =" << joint.isFree;
qDebug() << " freeLineage" << joint.freeLineage;
qDebug() << " parentIndex" << joint.parentIndex;
qDebug() << " distanceToParent" << joint.distanceToParent;
qDebug() << " boneRadius" << joint.boneRadius;
qDebug() << " translation" << joint.translation;
qDebug() << " preTransform" << joint.preTransform;
qDebug() << " preRotation" << joint.preRotation;
qDebug() << " rotation" << joint.rotation;
qDebug() << " postRotation" << joint.postRotation;
qDebug() << " postTransform" << joint.postTransform;
qDebug() << " transform" << joint.transform;
qDebug() << " rotationMin" << joint.rotationMin;
qDebug() << " rotationMax" << joint.rotationMax;
qDebug() << " inverseDefaultRotation" << joint.inverseDefaultRotation;
qDebug() << " inverseBindRotation" << joint.inverseBindRotation;
qDebug() << " bindTransform" << joint.bindTransform;
qDebug() << " name" << joint.name;
qDebug() << " shapePosition" << joint.shapePosition;
qDebug() << " shapeRotation" << joint.shapeRotation;
qDebug() << " shapeType" << joint.shapeType;
qDebug() << " isSkeletonJoint" << joint.isSkeletonJoint;
}
qDebug() << "\n";
}

View file

@ -4,3 +4,4 @@
FBXGeometry readOBJ(const QByteArray& model, const QVariantHash& mapping); FBXGeometry readOBJ(const QByteArray& model, const QVariantHash& mapping);
FBXGeometry readOBJ(QIODevice* device, const QVariantHash& mapping); FBXGeometry readOBJ(QIODevice* device, const QVariantHash& mapping);
void fbxDebugDump(const FBXGeometry& fbxgeo);

View file

@ -50,15 +50,13 @@ bool writeOBJ(QString outFileName, QVector<QVector<VHACD::IVHACD::ConvexHull>>&
for (unsigned int i = 0; i < hull.m_nPoints; i++) { for (unsigned int i = 0; i < hull.m_nPoints; i++) {
out << "v "; out << "v ";
out << formatFloat(hull.m_points[i*3]) << " "; out << formatFloat(hull.m_points[i*3]) << " ";
// swap y and z because up is 3rd value in OBJ out << formatFloat(hull.m_points[i*3+1]) << " ";
out << formatFloat(hull.m_points[i*3+2]) << " "; out << formatFloat(hull.m_points[i*3+2]) << "\n";
out << formatFloat(hull.m_points[i*3+1]) << "\n";
} }
for (unsigned int i = 0; i < hull.m_nTriangles; i++) { for (unsigned int i = 0; i < hull.m_nTriangles; i++) {
out << "f "; out << "f ";
// change order to flip normal (due to swapping y and z, above)
out << hull.m_triangles[i*3+1] + 1 + pointStartOffset << " ";
out << hull.m_triangles[i*3] + 1 + pointStartOffset << " "; out << hull.m_triangles[i*3] + 1 + pointStartOffset << " ";
out << hull.m_triangles[i*3+1] + 1 + pointStartOffset << " ";
out << hull.m_triangles[i*3+2] + 1 + pointStartOffset << "\n"; out << hull.m_triangles[i*3+2] + 1 + pointStartOffset << "\n";
} }
out << "\n"; out << "\n";