diff --git a/interface/src/avatar/BlendFace.cpp b/interface/src/avatar/BlendFace.cpp index ef3e6ff6af..7e30cfdcda 100644 --- a/interface/src/avatar/BlendFace.cpp +++ b/interface/src/avatar/BlendFace.cpp @@ -11,6 +11,7 @@ #include "Application.h" #include "BlendFace.h" #include "Head.h" +#include "renderer/FBXReader.h" using namespace fs; using namespace std; @@ -180,7 +181,12 @@ void BlendFace::handleModelDownloadProgress(qint64 bytesReceived, qint64 bytesTo _modelReply->deleteLater(); _modelReply = 0; - qDebug("Got %d bytes.\n", entirety.size()); + try { + printNode(parseFBX(entirety)); + + } catch (const QString& error) { + qDebug() << error << "\n"; + } } void BlendFace::handleModelReplyError() { diff --git a/interface/src/renderer/FBXReader.cpp b/interface/src/renderer/FBXReader.cpp new file mode 100644 index 0000000000..385ed17d4d --- /dev/null +++ b/interface/src/renderer/FBXReader.cpp @@ -0,0 +1,192 @@ +// +// FBXReader.cpp +// interface +// +// Created by Andrzej Kapolka on 9/18/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#include +#include +#include +#include +#include + +#include "FBXReader.h" + +FBXNode parseFBX(const QByteArray& data) { + QBuffer buffer(const_cast(&data)); + buffer.open(QIODevice::ReadOnly); + return parseFBX(&buffer); +} + +template QVariant readArray(QDataStream& in) { + quint32 arrayLength; + quint32 encoding; + quint32 compressedLength; + + in >> arrayLength; + in >> encoding; + in >> compressedLength; + + QVector values; + const int DEFLATE_ENCODING = 1; + if (encoding == DEFLATE_ENCODING) { + // preface encoded data with uncompressed length + QByteArray compressed(sizeof(quint32) + compressedLength, 0); + *((quint32*)compressed.data()) = qToBigEndian(arrayLength * sizeof(T)); + in.readRawData(compressed.data() + sizeof(quint32), compressedLength); + QByteArray uncompressed = qUncompress(compressed); + QDataStream uncompressedIn(uncompressed); + uncompressedIn.setByteOrder(QDataStream::LittleEndian); + for (int i = 0; i < arrayLength; i++) { + T value; + uncompressedIn >> value; + values.append(value); + } + } else { + for (int i = 0; i < arrayLength; i++) { + T value; + in >> value; + values.append(value); + } + } + return QVariant::fromValue(values); +} + +QVariant parseFBXProperty(QDataStream& in) { + char ch; + in.device()->getChar(&ch); + switch (ch) { + case 'Y': { + qint16 value; + in >> value; + return QVariant::fromValue(value); + } + case 'C': { + bool value; + in >> value; + return QVariant::fromValue(value); + } + case 'I': { + qint32 value; + in >> value; + return QVariant::fromValue(value); + } + case 'F': { + float value; + in >> value; + return QVariant::fromValue(value); + } + case 'D': { + double value; + in >> value; + return QVariant::fromValue(value); + } + case 'L': { + qint64 value; + in >> value; + return QVariant::fromValue(value); + } + case 'f': { + return readArray(in); + } + case 'd': { + return readArray(in); + } + case 'l': { + return readArray(in); + } + case 'i': { + return readArray(in); + } + case 'b': { + return readArray(in); + } + case 'S': + case 'R': { + quint32 length; + in >> length; + return QVariant::fromValue(in.device()->read(length)); + } + default: + throw QString("Unknown property type: ") + ch; + } +} + +FBXNode parseFBXNode(QDataStream& in) { + quint32 endOffset; + quint32 propertyCount; + quint32 propertyListLength; + quint8 nameLength; + + in >> endOffset; + in >> propertyCount; + in >> propertyListLength; + in >> nameLength; + + FBXNode node; + const int MIN_VALID_OFFSET = 40; + if (endOffset < MIN_VALID_OFFSET || nameLength == 0) { + // use a null name to indicate a null node + return node; + } + node.name = in.device()->read(nameLength); + + for (int i = 0; i < propertyCount; i++) { + node.properties.append(parseFBXProperty(in)); + } + + while (endOffset > in.device()->pos()) { + FBXNode child = parseFBXNode(in); + if (child.name.isNull()) { + return node; + + } else { + node.children.append(child); + } + } + + return node; +} + +FBXNode parseFBX(QIODevice* device) { + QDataStream in(device); + in.setByteOrder(QDataStream::LittleEndian); + + // verify the prolog + const QByteArray EXPECTED_PROLOG = "Kaydara FBX Binary "; + if (device->read(EXPECTED_PROLOG.size()) != EXPECTED_PROLOG) { + throw QString("Invalid header."); + } + + // skip the rest of the header + const int HEADER_SIZE = 27; + in.skipRawData(HEADER_SIZE - EXPECTED_PROLOG.size()); + + // parse the top-level node + FBXNode top; + while (device->bytesAvailable() >= 13) { + FBXNode next = parseFBXNode(in); + if (next.name.isNull()) { + return top; + + } else { + top.children.append(next); + } + } + + return top; +} + +void printNode(const FBXNode& node, int indent) { + QByteArray spaces(indent, ' '); + qDebug("%s%s: ", spaces.data(), node.name.data()); + foreach (const QVariant& property, node.properties) { + qDebug() << property; + } + qDebug() << "\n"; + foreach (const FBXNode& child, node.children) { + printNode(child, indent + 1); + } +} diff --git a/interface/src/renderer/FBXReader.h b/interface/src/renderer/FBXReader.h new file mode 100644 index 0000000000..92fa293560 --- /dev/null +++ b/interface/src/renderer/FBXReader.h @@ -0,0 +1,40 @@ +// +// FBXReader.h +// interface +// +// Created by Andrzej Kapolka on 9/18/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __interface__FBXReader__ +#define __interface__FBXReader__ + +#include +#include + +class QIODevice; + +class FBXNode; + +typedef QList FBXNodeList; + +/// A node within an FBX document. +class FBXNode { +public: + + QByteArray name; + QVariantList properties; + FBXNodeList children; +}; + +/// Parses the input from the supplied data as an FBX file. +/// \exception QString if an error occurs in parsing +FBXNode parseFBX(const QByteArray& data); + +/// Parses the input from the supplied device as an FBX file. +/// \exception QString if an error occurs in parsing +FBXNode parseFBX(QIODevice* device); + +void printNode(const FBXNode& node, int indent = 0); + +#endif /* defined(__interface__FBXReader__) */