From 66e0cbf6adc5c465b93d07dd9f57bd0cf9d9525f Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Wed, 30 Apr 2014 18:34:43 -0700 Subject: [PATCH 1/9] Working on FBX uploads (i.e., without FST). --- interface/src/ModelUploader.cpp | 245 +++++++++++++++++++------------- interface/src/ModelUploader.h | 28 +++- libraries/fbx/src/FBXReader.cpp | 27 ++++ libraries/fbx/src/FBXReader.h | 3 + 4 files changed, 205 insertions(+), 98 deletions(-) diff --git a/interface/src/ModelUploader.cpp b/interface/src/ModelUploader.cpp index 048e13bdf2..c74c90b586 100644 --- a/interface/src/ModelUploader.cpp +++ b/interface/src/ModelUploader.cpp @@ -10,13 +10,18 @@ // #include +#include #include #include +#include #include #include +#include #include #include +#include #include +#include #include #include @@ -32,6 +37,7 @@ static const QString NAME_FIELD = "name"; static const QString FILENAME_FIELD = "filename"; static const QString TEXDIR_FIELD = "texdir"; static const QString LOD_FIELD = "lod"; +static const QString JOINT_INDEX_FIELD = "jointIndex"; static const QString S3_URL = "http://highfidelity-public.s3-us-west-1.amazonaws.com"; static const QString DATA_SERVER_URL = "https://data-web.highfidelity.io"; @@ -76,7 +82,8 @@ bool ModelUploader::zip() { } - QString filename = QFileDialog::getOpenFileName(NULL, "Select your .fst file ...", lastLocation, "*.fst"); + QString filename = QFileDialog::getOpenFileName(NULL, "Select your model file ...", + lastLocation, "Model files (*.fst *.fbx)"); if (filename == "") { // If the user canceled we return. Application::getInstance()->unlockSettings(); @@ -85,89 +92,112 @@ bool ModelUploader::zip() { settings->setValue(SETTING_NAME, filename); Application::getInstance()->unlockSettings(); - bool _nameIsPresent = false; - QString texDir; + // First we check the FST file (if any) + QFile* fst; + QVariantHash mapping; + QString basePath; QString fbxFile; + if (filename.toLower().endsWith(".fst")) { + fst = new QFile(filename, this); + if (!fst->open(QFile::ReadOnly | QFile::Text)) { + QMessageBox::warning(NULL, + QString("ModelUploader::zip()"), + QString("Could not open FST file."), + QMessageBox::Ok); + qDebug() << "[Warning] " << QString("Could not open FST file."); + return false; + } + qDebug() << "Reading FST file : " << QFileInfo(*fst).filePath(); + mapping = readMapping(fst->readAll()); + basePath = QFileInfo(*fst).path(); + fbxFile = basePath + "/" + mapping.value(FILENAME_FIELD).toString(); + QFileInfo fbxInfo(fbxFile); + if (!fbxInfo.exists() || !fbxInfo.isFile()) { // Check existence + QMessageBox::warning(NULL, + QString("ModelUploader::zip()"), + QString("FBX file %1 could not be found.").arg(fbxInfo.fileName()), + QMessageBox::Ok); + qDebug() << "[Warning] " << QString("FBX file %1 could not be found.").arg(fbxInfo.fileName()); + return false; + } + } else { + fst = new QTemporaryFile(this); + fbxFile = filename; + basePath = QFileInfo(filename).path(); + } + // open the fbx file + QFile fbx(fbxFile); + if (!fbx.open(QIODevice::ReadOnly)) { + return false; + } + QByteArray fbxContents = fbx.readAll(); + FBXGeometry geometry = readFBX(fbxContents, QVariantHash()); - // First we check the FST file - QFile fst(filename); - if (!fst.open(QFile::ReadOnly | QFile::Text)) { + ModelPropertiesDialog properties(_isHead, mapping); + if (properties.exec() == QDialog::Rejected) { + return false; + } + mapping = properties.getMapping(); + + QByteArray nameField = mapping.value(NAME_FIELD).toByteArray(); + if (!nameField.isEmpty()) { + QHttpPart textPart; + textPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"model_name\""); + textPart.setBody(nameField); + _dataMultiPart->append(textPart); + _url = S3_URL + ((_isHead)? "/models/heads/" : "/models/skeletons/") + nameField + ".fst"; + } else { QMessageBox::warning(NULL, QString("ModelUploader::zip()"), - QString("Could not open FST file."), + QString("Model name is missing in the .fst file."), QMessageBox::Ok); - qDebug() << "[Warning] " << QString("Could not open FST file."); - return false; - } - qDebug() << "Reading FST file : " << QFileInfo(fst).filePath(); - - // Compress and copy the fst - if (!addPart(QFileInfo(fst).filePath(), QString("fst"))) { + qDebug() << "[Warning] " << QString("Model name is missing in the .fst file."); return false; } - // Let's read through the FST file - QTextStream stream(&fst); - QList line; - while (!stream.atEnd()) { - line = stream.readLine().split(QRegExp("[ =]"), QString::SkipEmptyParts); - if (line.isEmpty()) { - continue; - } - - // according to what is read, we modify the command - if (line[0] == NAME_FIELD) { - QHttpPart textPart; - textPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data;" - " name=\"model_name\""); - textPart.setBody(line[1].toUtf8()); - _dataMultiPart->append(textPart); - _url = S3_URL + ((_isHead)? "/models/heads/" : "/models/skeletons/") + line[1].toUtf8() + ".fst"; - _nameIsPresent = true; - } else if (line[0] == FILENAME_FIELD) { - fbxFile = QFileInfo(fst).path() + "/" + line[1]; - QFileInfo fbxInfo(fbxFile); - if (!fbxInfo.exists() || !fbxInfo.isFile()) { // Check existence - QMessageBox::warning(NULL, - QString("ModelUploader::zip()"), - QString("FBX file %1 could not be found.").arg(fbxInfo.fileName()), - QMessageBox::Ok); - qDebug() << "[Warning] " << QString("FBX file %1 could not be found.").arg(fbxInfo.fileName()); - return false; - } - // Compress and copy - if (!addPart(fbxInfo.filePath(), "fbx")) { - return false; - } - } else if (line[0] == TEXDIR_FIELD) { // Check existence - texDir = QFileInfo(fst).path() + "/" + line[1]; - QFileInfo texInfo(texDir); - if (!texInfo.exists() || !texInfo.isDir()) { - QMessageBox::warning(NULL, - QString("ModelUploader::zip()"), - QString("Texture directory could not be found."), - QMessageBox::Ok); - qDebug() << "[Warning] " << QString("Texture directory could not be found."); - return false; - } - } else if (line[0] == LOD_FIELD) { - QFileInfo lod(QFileInfo(fst).path() + "/" + line[1]); - if (!lod.exists() || !lod.isFile()) { // Check existence - QMessageBox::warning(NULL, - QString("ModelUploader::zip()"), - QString("LOD file %1 could not be found.").arg(lod.fileName()), - QMessageBox::Ok); - qDebug() << "[Warning] " << QString("FBX file %1 could not be found.").arg(lod.fileName()); - } - // Compress and copy - if (!addPart(lod.filePath(), QString("lod%1").arg(++_lodCount))) { - return false; - } + QByteArray texdirField = mapping.value(TEXDIR_FIELD).toByteArray(); + QString texDir; + if (!texdirField.isEmpty()) { + texDir = basePath + "/" + texdirField; + QFileInfo texInfo(texDir); + if (!texInfo.exists() || !texInfo.isDir()) { + QMessageBox::warning(NULL, + QString("ModelUploader::zip()"), + QString("Texture directory could not be found."), + QMessageBox::Ok); + qDebug() << "[Warning] " << QString("Texture directory could not be found."); + return false; } } - if (!addTextures(texDir, fbxFile)) { + QVariantHash lodField = mapping.value(LOD_FIELD).toHash(); + for (QVariantHash::const_iterator it = lodField.constBegin(); it != lodField.constEnd(); it++) { + QFileInfo lod(basePath + "/" + it.key()); + if (!lod.exists() || !lod.isFile()) { // Check existence + QMessageBox::warning(NULL, + QString("ModelUploader::zip()"), + QString("LOD file %1 could not be found.").arg(lod.fileName()), + QMessageBox::Ok); + qDebug() << "[Warning] " << QString("FBX file %1 could not be found.").arg(lod.fileName()); + } + // Compress and copy + if (!addPart(lod.filePath(), QString("lod%1").arg(++_lodCount))) { + return false; + } + } + + // Write out, compress and copy the fst + if (!addPart(*fst, writeMapping(mapping), QString("fst"))) { + return false; + } + + // Compress and copy the fbx + if (!addPart(fbx, fbxContents, "fbx")) { + return false; + } + + if (!addTextures(texDir, geometry)) { return false; } @@ -181,15 +211,6 @@ bool ModelUploader::zip() { } _dataMultiPart->append(textPart); - if (!_nameIsPresent) { - QMessageBox::warning(NULL, - QString("ModelUploader::zip()"), - QString("Model name is missing in the .fst file."), - QMessageBox::Ok); - qDebug() << "[Warning] " << QString("Model name is missing in the .fst file."); - return false; - } - _readyToSend = true; return true; } @@ -350,25 +371,16 @@ void ModelUploader::processCheck() { delete reply; } -bool ModelUploader::addTextures(const QString& texdir, const QString fbxFile) { - QFile fbx(fbxFile); - if (!fbx.open(QIODevice::ReadOnly)) { - return false; - } - - QByteArray buffer = fbx.readAll(); - QVariantHash variantHash = readMapping(buffer); - FBXGeometry geometry = readFBX(buffer, variantHash); - +bool ModelUploader::addTextures(const QString& texdir, const FBXGeometry& geometry) { foreach (FBXMesh mesh, geometry.meshes) { foreach (FBXMeshPart part, mesh.parts) { - if (!part.diffuseTexture.filename.isEmpty()) { + if (!part.diffuseTexture.filename.isEmpty() && part.diffuseTexture.content.isEmpty()) { if (!addPart(texdir + "/" + part.diffuseTexture.filename, QString("texture%1").arg(++_texturesCount))) { return false; } } - if (!part.normalTexture.filename.isEmpty()) { + if (!part.normalTexture.filename.isEmpty() && part.normalTexture.content.isEmpty()) { if (!addPart(texdir + "/" + part.normalTexture.filename, QString("texture%1").arg(++_texturesCount))) { return false; @@ -390,7 +402,11 @@ bool ModelUploader::addPart(const QString &path, const QString& name) { qDebug() << "[Warning] " << QString("Could not open %1").arg(path); return false; } - QByteArray buffer = qCompress(file.readAll()); + return addPart(file, file.readAll(), name); +} + +bool ModelUploader::addPart(const QFile& file, const QByteArray& contents, const QString& name) { + QByteArray buffer = qCompress(contents); // Qt's qCompress() default compression level (-1) is the standard zLib compression. // Here remove Qt's custom header that prevent the data server from uncompressing the files with zLib. @@ -420,8 +436,45 @@ bool ModelUploader::addPart(const QString &path, const QString& name) { return true; } +ModelPropertiesDialog::ModelPropertiesDialog(bool isHead, const QVariantHash& originalMapping) : + _originalMapping(originalMapping) { + + setWindowTitle("Set Model Properties"); + + QFormLayout* form = new QFormLayout(); + setLayout(form); + + form->addRow("Name:", _name = new QLineEdit()); + form->addRow("Texture Directory:", _textureDirectory = new QPushButton()); + connect(_textureDirectory, SIGNAL(clicked(bool)), SLOT(chooseTextureDirectory())); + + QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | + QDialogButtonBox::Cancel | QDialogButtonBox::Reset); + connect(buttons, SIGNAL(accepted()), SLOT(accept())); + connect(buttons, SIGNAL(rejected()), SLOT(reject())); + connect(buttons->button(QDialogButtonBox::Reset), SIGNAL(clicked(bool)), SLOT(reset())); + + form->addRow(buttons); + + // reset to initialize the fields + reset(); +} +QVariantHash ModelPropertiesDialog::getMapping() const { + QVariantHash mapping = _originalMapping; + mapping.insert(NAME_FIELD, _name->text()); + mapping.insert(TEXDIR_FIELD, _textureDirectory->text()); + return mapping; +} +void ModelPropertiesDialog::reset() { + _name->setText(_originalMapping.value(NAME_FIELD).toString()); + _textureDirectory->setText(_originalMapping.value(TEXDIR_FIELD).toString()); +} - - +void ModelPropertiesDialog::chooseTextureDirectory() { + QString directory = QFileDialog::getExistingDirectory(this, "Choose Texture Directory", _textureDirectory->text()); + if (!directory.isEmpty()) { + _textureDirectory->setText(directory); + } +} diff --git a/interface/src/ModelUploader.h b/interface/src/ModelUploader.h index 54702d6420..bcb4be3327 100644 --- a/interface/src/ModelUploader.h +++ b/interface/src/ModelUploader.h @@ -12,12 +12,16 @@ #ifndef hifi_ModelUploader_h #define hifi_ModelUploader_h +#include #include -class QDialog; class QFileInfo; class QHttpMultiPart; +class QLineEdit; class QProgressBar; +class QPushButton; + +class FBXGeometry; class ModelUploader : public QObject { Q_OBJECT @@ -56,8 +60,28 @@ private: bool zip(); - bool addTextures(const QString& texdir, const QString fbxFile); + bool addTextures(const QString& texdir, const FBXGeometry& geometry); bool addPart(const QString& path, const QString& name); + bool addPart(const QFile& file, const QByteArray& contents, const QString& name); +}; + +/// A dialog that allows customization of various model properties. +class ModelPropertiesDialog : public QDialog { + Q_OBJECT + +public: + ModelPropertiesDialog(bool isHead, const QVariantHash& originalMapping); + + QVariantHash getMapping() const; + +private slots: + void reset(); + void chooseTextureDirectory(); + +private: + QVariantHash _originalMapping; + QLineEdit* _name; + QPushButton* _textureDirectory; }; #endif // hifi_ModelUploader_h diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 7692d81eb9..628296c9f6 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -1803,6 +1803,33 @@ QVariantHash readMapping(const QByteArray& data) { return parseMapping(&buffer); } +QByteArray writeMapping(const QVariantHash& mapping) { + QBuffer buffer; + buffer.open(QIODevice::WriteOnly); + for (QVariantHash::const_iterator first = mapping.constBegin(); first != mapping.constEnd(); first++) { + QByteArray key = first.key().toUtf8() + " = "; + QVariantHash hashValue = first.value().toHash(); + if (hashValue.isEmpty()) { + buffer.write(key + first.value().toByteArray() + "\n"); + continue; + } + for (QVariantHash::const_iterator second = hashValue.constBegin(); second != hashValue.constEnd(); second++) { + QByteArray extendedKey = key + second.key().toUtf8(); + QVariantList listValue = second.value().toList(); + if (listValue.isEmpty()) { + buffer.write(extendedKey + " = " + second.value().toByteArray() + "\n"); + continue; + } + buffer.write(extendedKey); + for (QVariantList::const_iterator third = listValue.constBegin(); third != listValue.constEnd(); third++) { + buffer.write(" = " + third->toByteArray()); + } + buffer.write("\n"); + } + } + return buffer.data(); +} + FBXGeometry readFBX(const QByteArray& model, const QVariantHash& mapping) { QBuffer buffer(const_cast(&model)); buffer.open(QIODevice::ReadOnly); diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h index e437961385..f43131e41a 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -218,6 +218,9 @@ Q_DECLARE_METATYPE(FBXGeometry) /// Reads an FST mapping from the supplied data. QVariantHash readMapping(const QByteArray& data); +/// Writes an FST mapping to a byte array. +QByteArray writeMapping(const QVariantHash& mapping); + /// Reads FBX geometry from the supplied model and mapping data. /// \exception QString if an error occurs in parsing FBXGeometry readFBX(const QByteArray& model, const QVariantHash& mapping); From c8877c31fed423c1a3d93c381d490306830468d3 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 1 May 2014 13:25:23 -0700 Subject: [PATCH 2/9] More work on setting model properties before upload. --- interface/src/ModelUploader.cpp | 141 ++++++++++++++++++++++++++++++-- interface/src/ModelUploader.h | 26 +++++- 2 files changed, 156 insertions(+), 11 deletions(-) diff --git a/interface/src/ModelUploader.cpp b/interface/src/ModelUploader.cpp index c74c90b586..309ac5dcd6 100644 --- a/interface/src/ModelUploader.cpp +++ b/interface/src/ModelUploader.cpp @@ -9,12 +9,15 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include #include #include +#include #include #include #include #include +#include #include #include #include @@ -23,12 +26,11 @@ #include #include #include +#include #include #include -#include - #include "Application.h" #include "ModelUploader.h" @@ -38,6 +40,9 @@ static const QString FILENAME_FIELD = "filename"; static const QString TEXDIR_FIELD = "texdir"; static const QString LOD_FIELD = "lod"; static const QString JOINT_INDEX_FIELD = "jointIndex"; +static const QString SCALE_FIELD = "scale"; +static const QString JOINT_FIELD = "joint"; +static const QString FREE_JOINT_FIELD = "freeJoint"; static const QString S3_URL = "http://highfidelity-public.s3-us-west-1.amazonaws.com"; static const QString DATA_SERVER_URL = "https://data-web.highfidelity.io"; @@ -124,6 +129,7 @@ bool ModelUploader::zip() { fst = new QTemporaryFile(this); fbxFile = filename; basePath = QFileInfo(filename).path(); + mapping.insert(FILENAME_FIELD, QFileInfo(filename).fileName()); } // open the fbx file @@ -134,7 +140,16 @@ bool ModelUploader::zip() { QByteArray fbxContents = fbx.readAll(); FBXGeometry geometry = readFBX(fbxContents, QVariantHash()); - ModelPropertiesDialog properties(_isHead, mapping); + // make sure we have some basic mappings + if (!mapping.contains(NAME_FIELD)) { + mapping.insert(NAME_FIELD, QFileInfo(filename).baseName()); + } + if (!mapping.contains(TEXDIR_FIELD)) { + mapping.insert(TEXDIR_FIELD, "."); + } + + // open the dialog to configure the rest + ModelPropertiesDialog properties(_isHead, mapping, basePath, geometry); if (properties.exec() == QDialog::Rejected) { return false; } @@ -211,6 +226,9 @@ bool ModelUploader::zip() { } _dataMultiPart->append(textPart); + qDebug() << writeMapping(mapping); + return false; + _readyToSend = true; return true; } @@ -436,8 +454,12 @@ bool ModelUploader::addPart(const QFile& file, const QByteArray& contents, const return true; } -ModelPropertiesDialog::ModelPropertiesDialog(bool isHead, const QVariantHash& originalMapping) : - _originalMapping(originalMapping) { +ModelPropertiesDialog::ModelPropertiesDialog(bool isHead, const QVariantHash& originalMapping, + const QString& basePath, const FBXGeometry& geometry) : + _isHead(isHead), + _originalMapping(originalMapping), + _basePath(basePath), + _geometry(geometry) { setWindowTitle("Set Model Properties"); @@ -445,9 +467,32 @@ ModelPropertiesDialog::ModelPropertiesDialog(bool isHead, const QVariantHash& or setLayout(form); form->addRow("Name:", _name = new QLineEdit()); + form->addRow("Texture Directory:", _textureDirectory = new QPushButton()); connect(_textureDirectory, SIGNAL(clicked(bool)), SLOT(chooseTextureDirectory())); + form->addRow("Scale:", _scale = new QDoubleSpinBox()); + _scale->setMaximum(FLT_MAX); + _scale->setSingleStep(0.01); + + if (isHead) { + form->addRow("Left Eye Joint:", _leftEyeJoint = createJointBox()); + form->addRow("Right Eye Joint:", _rightEyeJoint = createJointBox()); + } + form->addRow("Neck Joint:", _neckJoint = createJointBox()); + if (!isHead) { + form->addRow("Root Joint:", _rootJoint = createJointBox()); + form->addRow("Lean Joint:", _leanJoint = createJointBox()); + form->addRow("Head Joint:", _headJoint = createJointBox()); + form->addRow("Left Hand Joint:", _leftHandJoint = createJointBox()); + form->addRow("Right Hand Joint:", _rightHandJoint = createJointBox()); + + form->addRow("Free Joints:", _freeJoints = new QVBoxLayout()); + QPushButton* newFreeJoint = new QPushButton("New Free Joint"); + _freeJoints->addWidget(newFreeJoint); + connect(newFreeJoint, SIGNAL(clicked(bool)), SLOT(createNewFreeJoint())); + } + QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Reset); connect(buttons, SIGNAL(accepted()), SLOT(accept())); @@ -464,17 +509,97 @@ QVariantHash ModelPropertiesDialog::getMapping() const { QVariantHash mapping = _originalMapping; mapping.insert(NAME_FIELD, _name->text()); mapping.insert(TEXDIR_FIELD, _textureDirectory->text()); + mapping.insert(SCALE_FIELD, QString::number(_scale->value())); + + // update the joint indices + QVariantHash jointIndices; + for (int i = 0; i < _geometry.joints.size(); i++) { + jointIndices.insert(_geometry.joints.at(i).name, QString::number(i)); + } + mapping.insert(JOINT_INDEX_FIELD, jointIndices); + + QVariantHash joints = mapping.value(JOINT_FIELD).toHash(); + if (_isHead) { + joints.insert("jointEyeLeft", _leftEyeJoint->currentText()); + joints.insert("jointEyeRight", _rightEyeJoint->currentText()); + } + joints.insert("jointNeck", _neckJoint->currentText()); + if (!_isHead) { + joints.insert("jointRoot", _rootJoint->currentText()); + joints.insert("jointLean", _leanJoint->currentText()); + joints.insert("jointHead", _headJoint->currentText()); + joints.insert("jointLeftHand", _leftHandJoint->currentText()); + joints.insert("jointRightHand", _rightHandJoint->currentText()); + + mapping.remove(FREE_JOINT_FIELD); + for (int i = 0; i < _freeJoints->count() - 1; i++) { + QComboBox* box = static_cast(_freeJoints->itemAt(i)->widget()->layout()->itemAt(0)->widget()); + mapping.insertMulti(FREE_JOINT_FIELD, box->currentText()); + } + } + mapping.insert(JOINT_FIELD, joints); + return mapping; } void ModelPropertiesDialog::reset() { _name->setText(_originalMapping.value(NAME_FIELD).toString()); _textureDirectory->setText(_originalMapping.value(TEXDIR_FIELD).toString()); + _scale->setValue(_originalMapping.value(SCALE_FIELD, 1.0).toDouble()); + + QVariantHash jointHash = _originalMapping.value(JOINT_FIELD).toHash(); + if (_isHead) { + _leftEyeJoint->setCurrentText(jointHash.value("jointEyeLeft").toString()); + _rightEyeJoint->setCurrentText(jointHash.value("jointEyeRight").toString()); + } + _neckJoint->setCurrentText(jointHash.value("jointNeck").toString()); + if (!_isHead) { + _rootJoint->setCurrentText(jointHash.value("jointRoot").toString()); + _leanJoint->setCurrentText(jointHash.value("jointLean").toString()); + _headJoint->setCurrentText(jointHash.value("jointHead").toString()); + _leftHandJoint->setCurrentText(jointHash.value("jointLeftHand").toString()); + _rightHandJoint->setCurrentText(jointHash.value("jointRightHand").toString()); + + while (_freeJoints->count() > 1) { + delete _freeJoints->itemAt(0)->widget(); + } + foreach (const QVariant& joint, _originalMapping.values(FREE_JOINT_FIELD)) { + createNewFreeJoint(joint.toString()); + } + } } void ModelPropertiesDialog::chooseTextureDirectory() { - QString directory = QFileDialog::getExistingDirectory(this, "Choose Texture Directory", _textureDirectory->text()); - if (!directory.isEmpty()) { - _textureDirectory->setText(directory); + QString directory = QFileDialog::getExistingDirectory(this, "Choose Texture Directory", + _basePath + "/" + _textureDirectory->text()); + if (directory.isEmpty()) { + return; } + if (!directory.startsWith(_basePath)) { + QMessageBox::warning(NULL, "Invalid texture directory", "Texture directory must be child of base path."); + return; + } + _textureDirectory->setText(directory.length() == _basePath.length() ? "." : directory.mid(_basePath.length() + 1)); +} + +void ModelPropertiesDialog::createNewFreeJoint(const QString& joint) { + QWidget* freeJoint = new QWidget(); + QHBoxLayout* freeJointLayout = new QHBoxLayout(); + freeJointLayout->setContentsMargins(QMargins()); + freeJoint->setLayout(freeJointLayout); + QComboBox* jointBox = createJointBox(); + jointBox->setCurrentText(joint); + freeJointLayout->addWidget(jointBox, 1); + QPushButton* deleteJoint = new QPushButton("Delete"); + freeJointLayout->addWidget(deleteJoint); + freeJoint->connect(deleteJoint, SIGNAL(clicked(bool)), SLOT(deleteLater())); + _freeJoints->insertWidget(_freeJoints->count() - 1, freeJoint); +} + +QComboBox* ModelPropertiesDialog::createJointBox() const { + QComboBox* box = new QComboBox(); + foreach (const FBXJoint& joint, _geometry.joints) { + box->addItem(joint.name); + } + return box; } diff --git a/interface/src/ModelUploader.h b/interface/src/ModelUploader.h index bcb4be3327..a30729d6e2 100644 --- a/interface/src/ModelUploader.h +++ b/interface/src/ModelUploader.h @@ -15,13 +15,16 @@ #include #include +#include + +class QComboBox; +class QDoubleSpinBox; class QFileInfo; class QHttpMultiPart; class QLineEdit; class QProgressBar; class QPushButton; - -class FBXGeometry; +class QVBoxLayout; class ModelUploader : public QObject { Q_OBJECT @@ -70,18 +73,35 @@ class ModelPropertiesDialog : public QDialog { Q_OBJECT public: - ModelPropertiesDialog(bool isHead, const QVariantHash& originalMapping); + ModelPropertiesDialog(bool isHead, const QVariantHash& originalMapping, + const QString& basePath, const FBXGeometry& geometry); QVariantHash getMapping() const; private slots: void reset(); void chooseTextureDirectory(); + void createNewFreeJoint(const QString& joint = QString()); private: + QComboBox* createJointBox() const; + + bool _isHead; QVariantHash _originalMapping; + QString _basePath; + FBXGeometry _geometry; QLineEdit* _name; QPushButton* _textureDirectory; + QDoubleSpinBox* _scale; + QComboBox* _leftEyeJoint; + QComboBox* _rightEyeJoint; + QComboBox* _neckJoint; + QComboBox* _rootJoint; + QComboBox* _leanJoint; + QComboBox* _headJoint; + QComboBox* _leftHandJoint; + QComboBox* _rightHandJoint; + QVBoxLayout* _freeJoints; }; #endif // hifi_ModelUploader_h From 6caa928e6445accbbd0d138934d62fffe2c53aef Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 1 May 2014 16:12:20 -0700 Subject: [PATCH 3/9] More work on avatar customization. --- interface/src/ModelUploader.cpp | 99 ++++++++++++++++++++++++--------- interface/src/ModelUploader.h | 3 +- libraries/fbx/src/FBXReader.cpp | 21 ++++++- libraries/fbx/src/FBXReader.h | 2 + 4 files changed, 95 insertions(+), 30 deletions(-) diff --git a/interface/src/ModelUploader.cpp b/interface/src/ModelUploader.cpp index 309ac5dcd6..09715fde04 100644 --- a/interface/src/ModelUploader.cpp +++ b/interface/src/ModelUploader.cpp @@ -148,6 +148,43 @@ bool ModelUploader::zip() { mapping.insert(TEXDIR_FIELD, "."); } + // mixamo/autodesk defaults + if (!mapping.contains(SCALE_FIELD)) { + mapping.insert(SCALE_FIELD, 10.0); + } + QVariantHash joints = mapping.value(JOINT_FIELD).toHash(); + if (!joints.contains("jointEyeLeft")) { + joints.insert("jointEyeLeft", "LeftEye"); + } + if (!joints.contains("jointEyeRight")) { + joints.insert("jointEyeRight", "RightEye"); + } + if (!joints.contains("jointNeck")) { + joints.insert("jointNeck", "Neck"); + } + if (!joints.contains("jointRoot")) { + joints.insert("jointRoot", "Hips"); + } + if (!joints.contains("jointLean")) { + joints.insert("jointLean", "Spine"); + } + if (!joints.contains("jointHead")) { + joints.insert("jointHead", geometry.applicationName == "mixamo.com" ? "HeadTop_End" : "HeadEnd"); + } + if (!joints.contains("jointLeftHand")) { + joints.insert("jointLeftHand", "LeftHand"); + } + if (!joints.contains("jointRightHand")) { + joints.insert("jointRightHand", "RightHand"); + } + mapping.insert(JOINT_FIELD, joints); + if (!mapping.contains(FREE_JOINT_FIELD)) { + mapping.insertMulti(FREE_JOINT_FIELD, "LeftArm"); + mapping.insertMulti(FREE_JOINT_FIELD, "LeftForeArm"); + mapping.insertMulti(FREE_JOINT_FIELD, "RightArm"); + mapping.insertMulti(FREE_JOINT_FIELD, "RightForeArm"); + } + // open the dialog to configure the rest ModelPropertiesDialog properties(_isHead, mapping, basePath, geometry); if (properties.exec() == QDialog::Rejected) { @@ -475,10 +512,8 @@ ModelPropertiesDialog::ModelPropertiesDialog(bool isHead, const QVariantHash& or _scale->setMaximum(FLT_MAX); _scale->setSingleStep(0.01); - if (isHead) { - form->addRow("Left Eye Joint:", _leftEyeJoint = createJointBox()); - form->addRow("Right Eye Joint:", _rightEyeJoint = createJointBox()); - } + form->addRow("Left Eye Joint:", _leftEyeJoint = createJointBox()); + form->addRow("Right Eye Joint:", _rightEyeJoint = createJointBox()); form->addRow("Neck Joint:", _neckJoint = createJointBox()); if (!isHead) { form->addRow("Root Joint:", _rootJoint = createJointBox()); @@ -519,17 +554,15 @@ QVariantHash ModelPropertiesDialog::getMapping() const { mapping.insert(JOINT_INDEX_FIELD, jointIndices); QVariantHash joints = mapping.value(JOINT_FIELD).toHash(); - if (_isHead) { - joints.insert("jointEyeLeft", _leftEyeJoint->currentText()); - joints.insert("jointEyeRight", _rightEyeJoint->currentText()); - } - joints.insert("jointNeck", _neckJoint->currentText()); + insertJointMapping(joints, "jointEyeLeft", _leftEyeJoint->currentText()); + insertJointMapping(joints, "jointEyeRight", _rightEyeJoint->currentText()); + insertJointMapping(joints, "jointNeck", _neckJoint->currentText()); if (!_isHead) { - joints.insert("jointRoot", _rootJoint->currentText()); - joints.insert("jointLean", _leanJoint->currentText()); - joints.insert("jointHead", _headJoint->currentText()); - joints.insert("jointLeftHand", _leftHandJoint->currentText()); - joints.insert("jointRightHand", _rightHandJoint->currentText()); + insertJointMapping(joints, "jointRoot", _rootJoint->currentText()); + insertJointMapping(joints, "jointLean", _leanJoint->currentText()); + insertJointMapping(joints, "jointHead", _headJoint->currentText()); + insertJointMapping(joints, "jointLeftHand", _leftHandJoint->currentText()); + insertJointMapping(joints, "jointRightHand", _rightHandJoint->currentText()); mapping.remove(FREE_JOINT_FIELD); for (int i = 0; i < _freeJoints->count() - 1; i++) { @@ -542,23 +575,25 @@ QVariantHash ModelPropertiesDialog::getMapping() const { return mapping; } +static void setJointText(QComboBox* box, const QString& text) { + box->setCurrentIndex(qMax(box->findText(text), 0)); +} + void ModelPropertiesDialog::reset() { _name->setText(_originalMapping.value(NAME_FIELD).toString()); _textureDirectory->setText(_originalMapping.value(TEXDIR_FIELD).toString()); _scale->setValue(_originalMapping.value(SCALE_FIELD, 1.0).toDouble()); QVariantHash jointHash = _originalMapping.value(JOINT_FIELD).toHash(); - if (_isHead) { - _leftEyeJoint->setCurrentText(jointHash.value("jointEyeLeft").toString()); - _rightEyeJoint->setCurrentText(jointHash.value("jointEyeRight").toString()); - } - _neckJoint->setCurrentText(jointHash.value("jointNeck").toString()); + setJointText(_leftEyeJoint, jointHash.value("jointEyeLeft").toString()); + setJointText(_rightEyeJoint, jointHash.value("jointEyeRight").toString()); + setJointText(_neckJoint, jointHash.value("jointNeck").toString()); if (!_isHead) { - _rootJoint->setCurrentText(jointHash.value("jointRoot").toString()); - _leanJoint->setCurrentText(jointHash.value("jointLean").toString()); - _headJoint->setCurrentText(jointHash.value("jointHead").toString()); - _leftHandJoint->setCurrentText(jointHash.value("jointLeftHand").toString()); - _rightHandJoint->setCurrentText(jointHash.value("jointRightHand").toString()); + setJointText(_rootJoint, jointHash.value("jointRoot").toString()); + setJointText(_leanJoint, jointHash.value("jointLean").toString()); + setJointText(_headJoint, jointHash.value("jointHead").toString()); + setJointText(_leftHandJoint, jointHash.value("jointLeftHand").toString()); + setJointText(_rightHandJoint, jointHash.value("jointRightHand").toString()); while (_freeJoints->count() > 1) { delete _freeJoints->itemAt(0)->widget(); @@ -587,7 +622,7 @@ void ModelPropertiesDialog::createNewFreeJoint(const QString& joint) { QHBoxLayout* freeJointLayout = new QHBoxLayout(); freeJointLayout->setContentsMargins(QMargins()); freeJoint->setLayout(freeJointLayout); - QComboBox* jointBox = createJointBox(); + QComboBox* jointBox = createJointBox(false); jointBox->setCurrentText(joint); freeJointLayout->addWidget(jointBox, 1); QPushButton* deleteJoint = new QPushButton("Delete"); @@ -596,10 +631,22 @@ void ModelPropertiesDialog::createNewFreeJoint(const QString& joint) { _freeJoints->insertWidget(_freeJoints->count() - 1, freeJoint); } -QComboBox* ModelPropertiesDialog::createJointBox() const { +QComboBox* ModelPropertiesDialog::createJointBox(bool withNone) const { QComboBox* box = new QComboBox(); + if (withNone) { + box->addItem("(none)"); + } foreach (const FBXJoint& joint, _geometry.joints) { box->addItem(joint.name); } return box; } + +void ModelPropertiesDialog::insertJointMapping(QVariantHash& joints, const QString& joint, const QString& name) const { + if (_geometry.jointIndices.contains(name)) { + joints.insert(joint, name); + } else { + joints.remove(joint); + } +} + diff --git a/interface/src/ModelUploader.h b/interface/src/ModelUploader.h index a30729d6e2..6526a3d83e 100644 --- a/interface/src/ModelUploader.h +++ b/interface/src/ModelUploader.h @@ -84,7 +84,8 @@ private slots: void createNewFreeJoint(const QString& joint = QString()); private: - QComboBox* createJointBox() const; + QComboBox* createJointBox(bool withNone = true) const; + void insertJointMapping(QVariantHash& joints, const QString& joint, const QString& name) const; bool _isHead; QVariantHash _originalMapping; diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 628296c9f6..cf7153259e 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -1006,9 +1006,25 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) } } QMultiHash blendshapeChannelIndices; - + + FBXGeometry geometry; foreach (const FBXNode& child, node.children) { - if (child.name == "Objects") { + if (child.name == "FBXHeaderExtension") { + foreach (const FBXNode& object, child.children) { + if (object.name == "SceneInfo") { + foreach (const FBXNode& subobject, object.children) { + if (subobject.name == "Properties70") { + foreach (const FBXNode& subsubobject, subobject.children) { + if (subsubobject.name == "P" && subsubobject.properties.size() >= 5 && + subsubobject.properties.at(0) == "Original|ApplicationName") { + geometry.applicationName = subsubobject.properties.at(4).toString(); + } + } + } + } + } + } + } else if (child.name == "Objects") { foreach (const FBXNode& object, child.children) { if (object.name == "Geometry") { if (object.properties.at(2) == "Mesh") { @@ -1317,7 +1333,6 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) } // get offset transform from mapping - FBXGeometry geometry; float offsetScale = mapping.value("scale", 1.0f).toFloat(); glm::quat offsetRotation = glm::quat(glm::radians(glm::vec3(mapping.value("rx").toFloat(), mapping.value("ry").toFloat(), mapping.value("rz").toFloat()))); diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h index f43131e41a..ea8b8f517d 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -174,6 +174,8 @@ public: class FBXGeometry { public: + QString applicationName; ///< the name of the application that generated the model + QVector joints; QHash jointIndices; ///< 1-based, so as to more easily detect missing indices From 02c757c940fd75ad7a73de28be6667d92c09a620 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 1 May 2014 16:13:03 -0700 Subject: [PATCH 4/9] Forgot to remove debugging code. --- interface/src/ModelUploader.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/interface/src/ModelUploader.cpp b/interface/src/ModelUploader.cpp index 09715fde04..604ded8b06 100644 --- a/interface/src/ModelUploader.cpp +++ b/interface/src/ModelUploader.cpp @@ -263,9 +263,6 @@ bool ModelUploader::zip() { } _dataMultiPart->append(textPart); - qDebug() << writeMapping(mapping); - return false; - _readyToSend = true; return true; } From b7e91e4fffccdab53d1743c19be8b10b26747408 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 1 May 2014 16:27:50 -0700 Subject: [PATCH 5/9] Need to open the temporary file in order to get the path. --- interface/src/ModelUploader.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/src/ModelUploader.cpp b/interface/src/ModelUploader.cpp index 604ded8b06..d33f994e4f 100644 --- a/interface/src/ModelUploader.cpp +++ b/interface/src/ModelUploader.cpp @@ -127,6 +127,7 @@ bool ModelUploader::zip() { } } else { fst = new QTemporaryFile(this); + fst->open(QFile::WriteOnly); fbxFile = filename; basePath = QFileInfo(filename).path(); mapping.insert(FILENAME_FIELD, QFileInfo(filename).fileName()); From 322e12b9de6cd6cc4619a0fa09cd61eb00c43dba Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 1 May 2014 17:55:16 -0700 Subject: [PATCH 6/9] Allow users to clear the head URL and have no separate head model. --- interface/src/avatar/MyAvatar.cpp | 4 ++-- interface/src/avatar/MyAvatar.h | 2 +- interface/src/renderer/GeometryCache.cpp | 15 ++++++++++++++- interface/src/renderer/Model.cpp | 3 ++- interface/src/renderer/Model.h | 2 +- libraries/avatars/src/AvatarData.cpp | 2 +- libraries/shared/src/ResourceCache.cpp | 8 ++++++-- 7 files changed, 27 insertions(+), 9 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 40e350dcb7..8f5f46c1e4 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -528,7 +528,7 @@ void MyAvatar::loadData(QSettings* settings) { setScale(_scale); Application::getInstance()->getCamera()->setScale(_scale); - setFaceModelURL(settings->value("faceModelURL").toUrl()); + setFaceModelURL(settings->value("faceModelURL", DEFAULT_HEAD_MODEL_URL).toUrl()); setSkeletonModelURL(settings->value("skeletonModelURL").toUrl()); setDisplayName(settings->value("displayName").toString()); @@ -618,7 +618,7 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { _billboardValid = false; } -void MyAvatar::renderBody(RenderMode renderMode) { +void MyAvatar::renderBody(RenderMode renderMode, float glowLevel) { if (!(_skeletonModel.isRenderable() && getHead()->getFaceModel().isRenderable())) { return; // wait until both models are loaded } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index a5312b0016..96f4ca3e4c 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -42,7 +42,7 @@ public: void moveWithLean(); void render(const glm::vec3& cameraPosition, RenderMode renderMode = NORMAL_RENDER_MODE); - void renderBody(RenderMode renderMode); + void renderBody(RenderMode renderMode, float glowLevel); bool shouldRenderHead(const glm::vec3& cameraPosition, RenderMode renderMode) const; void renderDebugBodyPoints(); void renderHeadMouse() const; diff --git a/interface/src/renderer/GeometryCache.cpp b/interface/src/renderer/GeometryCache.cpp index 3a410ac5e2..6e93fc77af 100644 --- a/interface/src/renderer/GeometryCache.cpp +++ b/interface/src/renderer/GeometryCache.cpp @@ -305,7 +305,6 @@ void GeometryCache::setBlendedVertices(const QPointer& model, const QWeak QSharedPointer GeometryCache::createResource(const QUrl& url, const QSharedPointer& fallback, bool delayLoad, const void* extra) { - QSharedPointer geometry(new NetworkGeometry(url, fallback.staticCast(), delayLoad), &Resource::allReferencesCleared); geometry->setLODParent(geometry); @@ -320,6 +319,20 @@ NetworkGeometry::NetworkGeometry(const QUrl& url, const QSharedPointer(), -1 }; + _geometry.joints.append(joint); + _geometry.leftEyeJointIndex = -1; + _geometry.rightEyeJointIndex = -1; + _geometry.neckJointIndex = -1; + _geometry.rootJointIndex = -1; + _geometry.leanJointIndex = -1; + _geometry.headJointIndex = -1; + _geometry.leftHandJointIndex = -1; + _geometry.rightHandJointIndex = -1; + } } bool NetworkGeometry::isLoadedWithTextures() const { diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 3484ac5fc8..b75739197e 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -39,7 +39,8 @@ Model::Model(QObject* parent) : _boundingShape(), _boundingShapeLocalOffset(0.f), _lodDistance(0.0f), - _pupilDilation(0.0f) { + _pupilDilation(0.0f), + _url("http://invalid.com") { // we may have been created in the network thread, but we live in the main thread moveToThread(Application::getInstance()->thread()); } diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 64295cb915..c6da5438cb 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -53,7 +53,7 @@ public: bool isActive() const { return _geometry && _geometry->isLoaded(); } - bool isRenderable() const { return !_meshStates.isEmpty(); } + bool isRenderable() const { return !_meshStates.isEmpty() || (isActive() && _geometry->getMeshes().isEmpty()); } bool isLoadedWithTextures() const { return _geometry && _geometry->isLoadedWithTextures(); } diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 2e716296ff..2917f1530a 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -640,7 +640,7 @@ bool AvatarData::hasBillboardChangedAfterParsing(const QByteArray& packet) { } void AvatarData::setFaceModelURL(const QUrl& faceModelURL) { - _faceModelURL = faceModelURL.isEmpty() ? DEFAULT_HEAD_MODEL_URL : faceModelURL; + _faceModelURL = faceModelURL; qDebug() << "Changing face model for avatar to" << _faceModelURL.toString(); } diff --git a/libraries/shared/src/ResourceCache.cpp b/libraries/shared/src/ResourceCache.cpp index 04b6265513..2f26e344fd 100644 --- a/libraries/shared/src/ResourceCache.cpp +++ b/libraries/shared/src/ResourceCache.cpp @@ -31,7 +31,7 @@ ResourceCache::~ResourceCache() { } QSharedPointer ResourceCache::getResource(const QUrl& url, const QUrl& fallback, bool delayLoad, void* extra) { - if (!url.isValid() && fallback.isValid()) { + if (!url.isValid() && !url.isEmpty() && fallback.isValid()) { return getResource(fallback, QUrl(), delayLoad); } QSharedPointer resource = _resources.value(url); @@ -114,7 +114,11 @@ Resource::Resource(const QUrl& url, bool delayLoad) : _reply(NULL), _attempts(0) { - if (!(url.isValid() && ResourceCache::getNetworkAccessManager())) { + if (url.isEmpty()) { + _startedLoading = _loaded = true; + return; + + } else if (!(url.isValid() && ResourceCache::getNetworkAccessManager())) { _startedLoading = _failedToLoad = true; return; } From 5e1f18635f2d655cc12c541ec5e30604d2ff17e2 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 1 May 2014 18:05:01 -0700 Subject: [PATCH 7/9] Need to initialize this to an invalid URL, too. --- libraries/avatars/src/AvatarData.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 2917f1530a..b57d5406d5 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -46,7 +46,8 @@ AvatarData::AvatarData() : _isChatCirclingEnabled(false), _hasNewJointRotations(true), _headData(NULL), - _handData(NULL), + _handData(NULL), + _faceModelURL("http://invalid.com"), _displayNameBoundingRect(), _displayNameTargetAlpha(0.0f), _displayNameAlpha(0.0f), From 28b90333732dad6778c73b8ab56ca8da886bba13 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 1 May 2014 18:51:57 -0700 Subject: [PATCH 8/9] Recode textures on upload if they're uncompressed or bigger than our maximum size. --- interface/src/ModelUploader.cpp | 39 ++++++++++++++++++++++++++------- interface/src/ModelUploader.h | 4 ++-- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/interface/src/ModelUploader.cpp b/interface/src/ModelUploader.cpp index d33f994e4f..799301b541 100644 --- a/interface/src/ModelUploader.cpp +++ b/interface/src/ModelUploader.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -51,6 +52,7 @@ static const QString MODEL_URL = "/api/v1/models"; static const QString SETTING_NAME = "LastModelUploadLocation"; static const int MAX_SIZE = 10 * 1024 * 1024; // 10 MB +static const int MAX_TEXTURE_SIZE = 1024; static const int TIMEOUT = 1000; static const int MAX_CHECK = 30; @@ -429,13 +431,13 @@ bool ModelUploader::addTextures(const QString& texdir, const FBXGeometry& geomet foreach (FBXMeshPart part, mesh.parts) { if (!part.diffuseTexture.filename.isEmpty() && part.diffuseTexture.content.isEmpty()) { if (!addPart(texdir + "/" + part.diffuseTexture.filename, - QString("texture%1").arg(++_texturesCount))) { + QString("texture%1").arg(++_texturesCount), true)) { return false; } } if (!part.normalTexture.filename.isEmpty() && part.normalTexture.content.isEmpty()) { if (!addPart(texdir + "/" + part.normalTexture.filename, - QString("texture%1").arg(++_texturesCount))) { + QString("texture%1").arg(++_texturesCount), true)) { return false; } } @@ -445,7 +447,7 @@ bool ModelUploader::addTextures(const QString& texdir, const FBXGeometry& geomet return true; } -bool ModelUploader::addPart(const QString &path, const QString& name) { +bool ModelUploader::addPart(const QString &path, const QString& name, bool isTexture) { QFile file(path); if (!file.open(QIODevice::ReadOnly)) { QMessageBox::warning(NULL, @@ -455,11 +457,29 @@ bool ModelUploader::addPart(const QString &path, const QString& name) { qDebug() << "[Warning] " << QString("Could not open %1").arg(path); return false; } - return addPart(file, file.readAll(), name); + return addPart(file, file.readAll(), name, isTexture); } -bool ModelUploader::addPart(const QFile& file, const QByteArray& contents, const QString& name) { - QByteArray buffer = qCompress(contents); +bool ModelUploader::addPart(const QFile& file, const QByteArray& contents, const QString& name, bool isTexture) { + QFileInfo fileInfo(file); + QByteArray recodedContents = contents; + if (isTexture) { + QString extension = fileInfo.suffix().toLower(); + bool isJpeg = (extension == "jpg"); + bool mustRecode = !(isJpeg || extension == "png"); + QImage image = QImage::fromData(contents); + if (image.width() > MAX_TEXTURE_SIZE || image.height() > MAX_TEXTURE_SIZE) { + image = image.scaled(MAX_TEXTURE_SIZE, MAX_TEXTURE_SIZE, Qt::KeepAspectRatio); + mustRecode = true; + } + if (mustRecode) { + QBuffer buffer; + buffer.open(QIODevice::WriteOnly); + image.save(&buffer, isJpeg ? "JPG" : "PNG"); + recodedContents = buffer.data(); + } + } + QByteArray buffer = qCompress(recodedContents); // Qt's qCompress() default compression level (-1) is the standard zLib compression. // Here remove Qt's custom header that prevent the data server from uncompressing the files with zLib. @@ -475,7 +495,7 @@ bool ModelUploader::addPart(const QFile& file, const QByteArray& contents, const qDebug() << "File " << QFileInfo(file).fileName() << " added to model."; - _totalSize += file.size(); + _totalSize += recodedContents.size(); if (_totalSize > MAX_SIZE) { QMessageBox::warning(NULL, QString("ModelUploader::zip()"), @@ -597,7 +617,10 @@ void ModelPropertiesDialog::reset() { delete _freeJoints->itemAt(0)->widget(); } foreach (const QVariant& joint, _originalMapping.values(FREE_JOINT_FIELD)) { - createNewFreeJoint(joint.toString()); + QString jointName = joint.toString(); + if (_geometry.jointIndices.contains(jointName)) { + createNewFreeJoint(jointName); + } } } } diff --git a/interface/src/ModelUploader.h b/interface/src/ModelUploader.h index 6526a3d83e..11594b3d95 100644 --- a/interface/src/ModelUploader.h +++ b/interface/src/ModelUploader.h @@ -64,8 +64,8 @@ private: bool zip(); bool addTextures(const QString& texdir, const FBXGeometry& geometry); - bool addPart(const QString& path, const QString& name); - bool addPart(const QFile& file, const QByteArray& contents, const QString& name); + bool addPart(const QString& path, const QString& name, bool isTexture = false); + bool addPart(const QFile& file, const QByteArray& contents, const QString& name, bool isTexture = false); }; /// A dialog that allows customization of various model properties. From 44ae29d06ecf19eb084eb2fdbe8c134d3c960f39 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 1 May 2014 20:54:09 -0700 Subject: [PATCH 9/9] Temporary hack for models from Mixamo Fuse until I can figure out the exact problem. --- libraries/fbx/src/FBXReader.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index cf7153259e..9389d0abf8 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -584,13 +584,16 @@ public: }; glm::mat4 getGlobalTransform(const QMultiHash& parentMap, - const QHash& models, QString nodeID) { + const QHash& models, QString nodeID, bool mixamoHack) { glm::mat4 globalTransform; while (!nodeID.isNull()) { const FBXModel& model = models.value(nodeID); globalTransform = glm::translate(model.translation) * model.preTransform * glm::mat4_cast(model.preRotation * model.rotation * model.postRotation) * model.postTransform * globalTransform; - + if (mixamoHack) { + // there's something weird about the models from Mixamo Fuse; they don't skin right with the full transform + return globalTransform; + } QList parentIDs = parentMap.values(nodeID); nodeID = QString(); foreach (const QString& parentID, parentIDs) { @@ -1481,7 +1484,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) // accumulate local transforms QString modelID = models.contains(it.key()) ? it.key() : parentMap.value(it.key()); - glm::mat4 modelTransform = getGlobalTransform(parentMap, models, modelID); + glm::mat4 modelTransform = getGlobalTransform(parentMap, models, modelID, geometry.applicationName == "mixamo.com"); // compute the mesh extents from the transformed vertices foreach (const glm::vec3& vertex, extracted.mesh.vertices) {