overte-HifiExperiments/libraries/fbx/src/OBJReader.cpp

287 lines
10 KiB
C++

//
// OBJReader.cpp
// libraries/fbx/src/
//
// Created by Seth Alves on 3/7/15.
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// http://en.wikipedia.org/wiki/Wavefront_.obj_file
// http://www.scratchapixel.com/old/lessons/3d-advanced-lessons/obj-file-format/obj-file-format/
// http://paulbourke.net/dataformats/obj/
#include <QBuffer>
#include <QIODevice>
#include "FBXReader.h"
#include "OBJReader.h"
#include "Shape.h"
class OBJTokenizer {
public:
OBJTokenizer(QIODevice* device) : _device(device), _pushedBackToken(-1) { }
enum SpecialToken {
NO_TOKEN = -1,
NO_PUSHBACKED_TOKEN = -1,
DATUM_TOKEN = 0x100
};
int nextToken();
const QByteArray& getDatum() const { return _datum; }
void skipLine() { _device->readLine(); }
void pushBackToken(int token) { _pushedBackToken = token; }
void ungetChar(char ch) { _device->ungetChar(ch); }
private:
QIODevice* _device;
QByteArray _datum;
int _pushedBackToken;
};
int OBJTokenizer::nextToken() {
if (_pushedBackToken != NO_PUSHBACKED_TOKEN) {
int token = _pushedBackToken;
_pushedBackToken = NO_PUSHBACKED_TOKEN;
return token;
}
char ch;
while (_device->getChar(&ch)) {
if (QChar(ch).isSpace()) {
continue; // skip whitespace
}
switch (ch) {
case '#':
_device->readLine(); // skip the comment
break;
case '\"':
_datum = "";
while (_device->getChar(&ch)) {
if (ch == '\"') { // end on closing quote
break;
}
if (ch == '\\') { // handle escaped quotes
if (_device->getChar(&ch) && ch != '\"') {
_datum.append('\\');
}
}
_datum.append(ch);
}
return DATUM_TOKEN;
default:
_datum = "";
_datum.append(ch);
while (_device->getChar(&ch)) {
if (QChar(ch).isSpace() || ch == '\"') {
ungetChar(ch); // read until we encounter a special character, then replace it
break;
}
_datum.append(ch);
}
return DATUM_TOKEN;
}
}
return NO_TOKEN;
}
bool parseOBJGroup(OBJTokenizer &tokenizer, const QVariantHash& mapping, FBXGeometry &geometry) {
FBXMesh &mesh = geometry.meshes[0];
mesh.parts.append(FBXMeshPart());
FBXMeshPart &meshPart = mesh.parts.last();
bool sawG = false;
bool result = true;
meshPart.materialID = QString("dontknow") + QString::number(mesh.parts.count());
meshPart.opacity = 1.0;
meshPart._material = model::MaterialPointer(new model::Material());
meshPart._material->setDiffuse(glm::vec3(1.0, 1.0, 1.0));
meshPart._material->setOpacity(1.0);
meshPart._material->setSpecular(glm::vec3(1.0, 1.0, 1.0));
meshPart._material->setShininess(96.0);
meshPart._material->setEmissive(glm::vec3(0.0, 0.0, 0.0));
while (true) {
if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) {
result = false;
break;
}
QByteArray token = tokenizer.getDatum();
if (token == "g") {
if (sawG) {
// we've encountered the beginning of the next group.
tokenizer.pushBackToken(OBJTokenizer::DATUM_TOKEN);
break;
}
sawG = true;
if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) {
break;
}
QByteArray groupName = tokenizer.getDatum();
meshPart.materialID = groupName;
} else if (token == "v") {
if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) {
break;
}
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 z = std::stof(tokenizer.getDatum().data());
if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) {
break;
}
float y = std::stof(tokenizer.getDatum().data());
if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) {
break;
}
// the spec gets vague here. might be w, might be a color... chop it off.
tokenizer.skipLine();
mesh.vertices.append(glm::vec3(x, y, z));
mesh.colors.append(glm::vec3(1, 1, 1));
} else if (token == "f") {
// a face can have 3 or more vertices
QVector<int> indices;
while (true) {
if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) { goto done; }
try {
int vertexIndex = std::stoi(tokenizer.getDatum().data());
// 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);
}
catch(const std::exception& e) {
// wasn't a number, but it back.
tokenizer.pushBackToken(OBJTokenizer::DATUM_TOKEN);
break;
}
}
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[2]);
meshPart.triangleIndices.append(indices[1]);
} else if (indices.count() == 4) {
meshPart.quadIndices << indices;
} else {
qDebug() << "no support for more than 4 vertices on a face in OBJ files";
}
} else {
// something we don't (yet) care about
qDebug() << "OBJ parser is skipping a line with" << token;
tokenizer.skipLine();
}
}
done:
return result;
}
FBXGeometry extractOBJGeometry(const FBXNode& node, const QVariantHash& mapping) {
FBXGeometry geometry;
return geometry;
}
FBXGeometry readOBJ(const QByteArray& model, const QVariantHash& mapping) {
QBuffer buffer(const_cast<QByteArray*>(&model));
buffer.open(QIODevice::ReadOnly);
return readOBJ(&buffer, mapping);
}
FBXGeometry readOBJ(QIODevice* device, const QVariantHash& mapping) {
FBXGeometry geometry;
OBJTokenizer tokenizer(device);
geometry.meshExtents.reset();
geometry.meshes.append(FBXMesh());
try {
// call parseOBJGroup as long as it's returning true. Each successful call will
// add a new meshPart to the geometry's single mesh.
bool success = true;
while (success) {
success = parseOBJGroup(tokenizer, mapping, geometry);
}
FBXMesh &mesh = geometry.meshes[0];
mesh.meshExtents.reset();
foreach (const glm::vec3& vertex, mesh.vertices) {
mesh.meshExtents.addPoint(vertex);
geometry.meshExtents.addPoint(vertex);
}
geometry.joints.resize(1);
geometry.joints[0].isFree = false;
// geometry.joints[0].freeLineage;
geometry.joints[0].parentIndex = -1;
geometry.joints[0].distanceToParent = 0;
geometry.joints[0].boneRadius = 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].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].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].isSkeletonJoint = false;
// add bogus normal data for this mesh
mesh.normals.fill(glm::vec3(0,0,0), mesh.vertices.count());
mesh.tangents.fill(glm::vec3(0,0,0), mesh.vertices.count());
foreach (FBXMeshPart meshPart, mesh.parts) {
int triCount = meshPart.triangleIndices.count() / 3;
for (int i = 0; i < triCount; i++) {
int p0Index = meshPart.triangleIndices[i*3];
int p1Index = meshPart.triangleIndices[i*3+1];
int p2Index = meshPart.triangleIndices[i*3+2];
glm::vec3 p0 = mesh.vertices[p0Index];
glm::vec3 p1 = mesh.vertices[p1Index];
glm::vec3 p2 = mesh.vertices[p2Index];
glm::vec3 n = glm::cross(p1 - p0, p2 - p0);
glm::vec3 t = glm::cross(p2 - p0, n);
mesh.normals[p0Index] = n;
mesh.normals[p1Index] = n;
mesh.normals[p2Index] = n;
mesh.tangents[p0Index] = t;
mesh.tangents[p1Index] = t;
mesh.tangents[p2Index] = t;
}
}
}
catch(const std::exception& e) {
qDebug() << "something went wrong in OBJ reader";
}
return geometry;
}