3
0
Fork 0
mirror of https://github.com/lubosz/overte.git synced 2025-04-26 07:15:37 +02:00

Merge pull request from ZappoMan/avatarUI

Support for Auto-detecting FST type, and adding type hint to FST files when packaged
This commit is contained in:
Philip Rosedale 2015-03-30 21:31:22 -07:00
commit 5325478940
18 changed files with 281 additions and 103 deletions

View file

@ -1524,7 +1524,7 @@ void Application::idle() {
}
// Normally we check PipelineWarnings, but since idle will often take more than 10ms we only show these idle timing
// details if we're in ExtraDebugging mode. However, the ::update() and it's subcomponents will show their timing
// details if we're in ExtraDebugging mode. However, the ::update() and its subcomponents will show their timing
// details normally.
bool showWarnings = getLogger()->extraDebugging();
PerformanceWarning warn(showWarnings, "idle()");
@ -2540,7 +2540,7 @@ bool Application::isHMDMode() const {
QRect Application::getDesirableApplicationGeometry() {
QRect applicationGeometry = getWindow()->geometry();
// If our parent window is on the HMD, then don't use it's geometry, instead use
// If our parent window is on the HMD, then don't use its geometry, instead use
// the "main screen" geometry.
HMDToolsDialog* hmdTools = DependencyManager::get<DialogsManager>()->getHMDToolsDialog();
if (hmdTools && hmdTools->hasHMDScreen()) {
@ -3376,7 +3376,7 @@ void Application::nodeKilled(SharedNodePointer node) {
void Application::trackIncomingOctreePacket(const QByteArray& packet, const SharedNodePointer& sendingNode, bool wasStatsPacket) {
// Attempt to identify the sender from it's address.
// Attempt to identify the sender from its address.
if (sendingNode) {
QUuid nodeUUID = sendingNode->getUUID();
@ -3445,7 +3445,7 @@ int Application::parseOctreeStats(const QByteArray& packet, const SharedNodePoin
}
// store jurisdiction details for later use
// This is bit of fiddling is because JurisdictionMap assumes it is the owner of the values used to construct it
// but OctreeSceneStats thinks it's just returning a reference to it's contents. So we need to make a copy of the
// but OctreeSceneStats thinks it's just returning a reference to its contents. So we need to make a copy of the
// details from the OctreeSceneStats to construct the JurisdictionMap
JurisdictionMap jurisdictionMap;
jurisdictionMap.copyContents(temp.getJurisdictionRoot(), temp.getJurisdictionEndNodes());
@ -3665,17 +3665,56 @@ bool Application::askToSetAvatarUrl(const QString& url) {
msgBox.exec();
return false;
}
QString message = "Would you like to use this model for part of avatar:\n" + url;
// Download the FST file, to attempt to determine its model type
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkRequest networkRequest = QNetworkRequest(url);
networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
QNetworkReply* reply = networkAccessManager.get(networkRequest);
qDebug() << "Downloading avatar file at " << url;
QEventLoop loop;
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
loop.exec();
QByteArray fstContents = reply->readAll();
delete reply;
QVariantHash fstMapping = FSTReader::readMapping(fstContents);
FSTReader::ModelType modelType = FSTReader::predictModelType(fstMapping);
QMessageBox msgBox;
msgBox.setIcon(QMessageBox::Question);
msgBox.setWindowTitle("Set Avatar");
msgBox.setText(message);
QPushButton* headButton = NULL;
QPushButton* bodyButton = NULL;
QPushButton* bodyAndHeadButton = NULL;
QString message;
QString typeInfo;
switch (modelType) {
case FSTReader::HEAD_MODEL:
message = QString("Would you like to use '") + fstMapping["name"].toString() + QString("' for your avatar head?");
headButton = msgBox.addButton(tr("Yes"), QMessageBox::ActionRole);
break;
QPushButton* headButton = msgBox.addButton(tr("Head"), QMessageBox::ActionRole);
QPushButton* bodyButton = msgBox.addButton(tr("Body"), QMessageBox::ActionRole);
QPushButton* bodyAndHeadButton = msgBox.addButton(tr("Body + Head"), QMessageBox::ActionRole);
case FSTReader::BODY_ONLY_MODEL:
message = QString("Would you like to use '") + fstMapping["name"].toString() + QString("' for your avatar body?");
bodyButton = msgBox.addButton(tr("Yes"), QMessageBox::ActionRole);
break;
case FSTReader::HEAD_AND_BODY_MODEL:
message = QString("Would you like to use '") + fstMapping["name"].toString() + QString("' for your avatar?");
bodyAndHeadButton = msgBox.addButton(tr("Yes"), QMessageBox::ActionRole);
break;
default:
message = QString("Would you like to use '") + fstMapping["name"].toString() + QString("' for some part of your avatar head?");
headButton = msgBox.addButton(tr("Use for Head"), QMessageBox::ActionRole);
bodyButton = msgBox.addButton(tr("Use for Body"), QMessageBox::ActionRole);
bodyAndHeadButton = msgBox.addButton(tr("Use for Body and Head"), QMessageBox::ActionRole);
break;
}
msgBox.setText(message);
msgBox.addButton(QMessageBox::Cancel);
msgBox.exec();
@ -3688,6 +3727,11 @@ bool Application::askToSetAvatarUrl(const QString& url) {
} else if (msgBox.clickedButton() == bodyButton) {
qDebug() << "Chose to use for body: " << url;
_myAvatar->setSkeletonModelURL(url);
// if the head is empty, reset it to the default head.
if (_myAvatar->getFaceModelURLString().isEmpty()) {
_myAvatar->setFaceModelURL(DEFAULT_HEAD_MODEL_URL);
UserActivityLogger::getInstance().changedModel("head", DEFAULT_HEAD_MODEL_URL.toString());
}
UserActivityLogger::getInstance().changedModel("skeleton", url);
_myAvatar->sendIdentityPacket();
} else if (msgBox.clickedButton() == bodyAndHeadButton) {

View file

@ -246,7 +246,7 @@ namespace MenuOption {
const QString ToolWindow = "Tool Window";
const QString TransmitterDrive = "Transmitter Drive";
const QString TurnWithHead = "Turn using Head";
const QString PackageModel = "Package Model";
const QString PackageModel = "Package Model...";
const QString Visage = "Visage";
const QString Wireframe = "Wireframe";
}

View file

@ -85,7 +85,7 @@ bool ModelPackager::loadModel() {
return false;
}
qDebug() << "Reading FST file : " << _modelFile.filePath();
_mapping = readMapping(fst.readAll());
_mapping = FSTReader::readMapping(fst.readAll());
fst.close();
_fbxInfo = QFileInfo(_modelFile.path() + "/" + _mapping.value(FILENAME_FIELD).toString());
@ -119,21 +119,23 @@ bool ModelPackager::editProperties() {
return false;
}
_mapping = properties.getMapping();
// Make sure that a mapping for the root joint has been specified
QVariantHash joints = _mapping.value(JOINT_FIELD).toHash();
if (!joints.contains("jointRoot")) {
qWarning() << QString("%1 root joint not configured for skeleton.").arg(_modelFile.fileName());
if (_modelType == FSTReader::BODY_ONLY_MODEL || _modelType == FSTReader::HEAD_AND_BODY_MODEL) {
// Make sure that a mapping for the root joint has been specified
QVariantHash joints = _mapping.value(JOINT_FIELD).toHash();
if (!joints.contains("jointRoot")) {
qWarning() << QString("%1 root joint not configured for skeleton.").arg(_modelFile.fileName());
QString message = "Your did not configure a root joint for your skeleton model.\n\nThe upload will be canceled.";
QMessageBox msgBox;
msgBox.setWindowTitle("Model Upload");
msgBox.setText(message);
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setIcon(QMessageBox::Warning);
msgBox.exec();
QString message = "Your did not configure a root joint for your skeleton model.\n\nPackaging will be canceled.";
QMessageBox msgBox;
msgBox.setWindowTitle("Model Packager");
msgBox.setText(message);
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setIcon(QMessageBox::Warning);
msgBox.exec();
return false;
return false;
}
}
return true;
@ -183,7 +185,7 @@ bool ModelPackager::zipModel() {
// Copy FST
QFile fst(tempDir.path() + "/" + nameField + ".fst");
if (fst.open(QIODevice::WriteOnly)) {
fst.write(writeMapping(_mapping));
fst.write(FSTReader::writeMapping(_mapping));
fst.close();
} else {
qDebug() << "Couldn't write FST file" << fst.fileName();
@ -204,6 +206,18 @@ bool ModelPackager::zipModel() {
}
void ModelPackager::populateBasicMapping(QVariantHash& mapping, QString filename, const FBXGeometry& geometry) {
bool isBodyType = _modelType == FSTReader::BODY_ONLY_MODEL || _modelType == FSTReader::HEAD_AND_BODY_MODEL;
// mixamo files - in the event that a mixamo file was edited by some other tool, it's likely the applicationName will
// be rewritten, so we detect the existence of several different blendshapes which indicate we're likely a mixamo file
bool likelyMixamoFile = geometry.applicationName == "mixamo.com" ||
(geometry.blendshapeChannelNames.contains("BrowsDown_Right") &&
geometry.blendshapeChannelNames.contains("MouthOpen") &&
geometry.blendshapeChannelNames.contains("Blink_Left") &&
geometry.blendshapeChannelNames.contains("Blink_Right") &&
geometry.blendshapeChannelNames.contains("Squint_Right"));
if (!mapping.contains(NAME_FIELD)) {
mapping.insert(NAME_FIELD, QFileInfo(filename).baseName());
}
@ -232,39 +246,40 @@ void ModelPackager::populateBasicMapping(QVariantHash& mapping, QString filename
if (!joints.contains("jointNeck")) {
joints.insert("jointNeck", geometry.jointIndices.contains("jointNeck") ? "jointNeck" : "Neck");
}
if (!joints.contains("jointRoot")) {
joints.insert("jointRoot", "Hips");
}
if (!joints.contains("jointLean")) {
joints.insert("jointLean", "Spine");
if (isBodyType) {
if (!joints.contains("jointRoot")) {
joints.insert("jointRoot", "Hips");
}
if (!joints.contains("jointLean")) {
joints.insert("jointLean", "Spine");
}
if (!joints.contains("jointLeftHand")) {
joints.insert("jointLeftHand", "LeftHand");
}
if (!joints.contains("jointRightHand")) {
joints.insert("jointRightHand", "RightHand");
}
}
if (!joints.contains("jointHead")) {
const char* topName = (geometry.applicationName == "mixamo.com") ? "HeadTop_End" : "HeadEnd";
const char* topName = likelyMixamoFile ? "HeadTop_End" : "HeadEnd";
joints.insert("jointHead", geometry.jointIndices.contains(topName) ? topName : "Head");
}
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");
if (isBodyType) {
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");
}
}
// mixamo blendshapes - in the event that a mixamo file was edited by some other tool, it's likely the applicationName will
// be rewritten, so we detect the existence of several different blendshapes which indicate we're likely a mixamo file
bool likelyMixamoFile = geometry.applicationName == "mixamo.com" ||
(geometry.blendshapeChannelNames.contains("BrowsDown_Right") &&
geometry.blendshapeChannelNames.contains("MouthOpen") &&
geometry.blendshapeChannelNames.contains("Blink_Left") &&
geometry.blendshapeChannelNames.contains("Blink_Right") &&
geometry.blendshapeChannelNames.contains("Squint_Right"));
// If there are no blendshape mappings, and we detect that this is likely a mixamo file,
// then we can add the default mixamo to "faceshift" mappings
if (!mapping.contains(BLENDSHAPE_FIELD) && likelyMixamoFile) {
QVariantHash blendshapes;
blendshapes.insertMulti("BrowsD_L", QVariantList() << "BrowsDown_Left" << 1.0);

View file

@ -35,7 +35,7 @@ private:
QFileInfo _modelFile;
QFileInfo _fbxInfo;
ModelType _modelType;
FSTReader::ModelType _modelType;
QString _texDir;
QVariantHash _mapping;

View file

@ -25,7 +25,7 @@
#include "ModelPropertiesDialog.h"
ModelPropertiesDialog::ModelPropertiesDialog(ModelType modelType, const QVariantHash& originalMapping,
ModelPropertiesDialog::ModelPropertiesDialog(FSTReader::ModelType modelType, const QVariantHash& originalMapping,
const QString& basePath, const FBXGeometry& geometry) :
_modelType(modelType),
_originalMapping(originalMapping),
@ -46,8 +46,8 @@ _geometry(geometry)
_scale->setMaximum(FLT_MAX);
_scale->setSingleStep(0.01);
if (_modelType != ENTITY_MODEL) {
if (_modelType == ATTACHMENT_MODEL) {
if (_modelType != FSTReader::ENTITY_MODEL) {
if (_modelType == FSTReader::ATTACHMENT_MODEL) {
QHBoxLayout* translation = new QHBoxLayout();
form->addRow("Translation:", translation);
translation->addWidget(_translationX = createTranslationBox());
@ -63,7 +63,7 @@ _geometry(geometry)
form->addRow("Right Eye Joint:", _rightEyeJoint = createJointBox());
form->addRow("Neck Joint:", _neckJoint = createJointBox());
}
if (_modelType == SKELETON_MODEL) {
if (_modelType == FSTReader::BODY_ONLY_MODEL || _modelType == FSTReader::HEAD_AND_BODY_MODEL) {
form->addRow("Root Joint:", _rootJoint = createJointBox());
form->addRow("Lean Joint:", _leanJoint = createJointBox());
form->addRow("Head Joint:", _headJoint = createJointBox());
@ -89,8 +89,14 @@ _geometry(geometry)
reset();
}
QString ModelPropertiesDialog::getType() const {
return FSTReader::getNameFromType(_modelType);
}
QVariantHash ModelPropertiesDialog::getMapping() const {
QVariantHash mapping = _originalMapping;
mapping.insert(TYPE_FIELD, getType());
mapping.insert(NAME_FIELD, _name->text());
mapping.insert(TEXDIR_FIELD, _textureDirectory->text());
mapping.insert(SCALE_FIELD, QString::number(_scale->value()));
@ -102,9 +108,9 @@ QVariantHash ModelPropertiesDialog::getMapping() const {
}
mapping.insert(JOINT_INDEX_FIELD, jointIndices);
if (_modelType != ENTITY_MODEL) {
if (_modelType != FSTReader::ENTITY_MODEL) {
QVariantHash joints = mapping.value(JOINT_FIELD).toHash();
if (_modelType == ATTACHMENT_MODEL) {
if (_modelType == FSTReader::ATTACHMENT_MODEL) {
glm::vec3 pivot;
if (_pivotAboutCenter->isChecked()) {
pivot = (_geometry.meshExtents.minimum + _geometry.meshExtents.maximum) * 0.5f;
@ -121,7 +127,9 @@ QVariantHash ModelPropertiesDialog::getMapping() const {
insertJointMapping(joints, "jointEyeRight", _rightEyeJoint->currentText());
insertJointMapping(joints, "jointNeck", _neckJoint->currentText());
}
if (_modelType == SKELETON_MODEL) {
if (_modelType == FSTReader::BODY_ONLY_MODEL || _modelType == FSTReader::HEAD_AND_BODY_MODEL) {
insertJointMapping(joints, "jointRoot", _rootJoint->currentText());
insertJointMapping(joints, "jointLean", _leanJoint->currentText());
insertJointMapping(joints, "jointHead", _headJoint->currentText());
@ -151,8 +159,8 @@ void ModelPropertiesDialog::reset() {
QVariantHash jointHash = _originalMapping.value(JOINT_FIELD).toHash();
if (_modelType != ENTITY_MODEL) {
if (_modelType == ATTACHMENT_MODEL) {
if (_modelType != FSTReader::ENTITY_MODEL) {
if (_modelType == FSTReader::ATTACHMENT_MODEL) {
_translationX->setValue(_originalMapping.value(TRANSLATION_X_FIELD).toDouble());
_translationY->setValue(_originalMapping.value(TRANSLATION_Y_FIELD).toDouble());
_translationZ->setValue(_originalMapping.value(TRANSLATION_Z_FIELD).toDouble());
@ -164,7 +172,8 @@ void ModelPropertiesDialog::reset() {
setJointText(_rightEyeJoint, jointHash.value("jointEyeRight").toString());
setJointText(_neckJoint, jointHash.value("jointNeck").toString());
}
if (_modelType == SKELETON_MODEL) {
if (_modelType == FSTReader::BODY_ONLY_MODEL || _modelType == FSTReader::HEAD_AND_BODY_MODEL) {
setJointText(_rootJoint, jointHash.value("jointRoot").toString());
setJointText(_leanJoint, jointHash.value("jointLean").toString());
setJointText(_headJoint, jointHash.value("jointHead").toString());

View file

@ -15,6 +15,7 @@
#include <QDialog>
#include <FBXReader.h>
#include <FSTReader.h>
#include "ui/ModelsBrowser.h"
@ -28,7 +29,7 @@ class ModelPropertiesDialog : public QDialog {
Q_OBJECT
public:
ModelPropertiesDialog(ModelType modelType, const QVariantHash& originalMapping,
ModelPropertiesDialog(FSTReader::ModelType modelType, const QVariantHash& originalMapping,
const QString& basePath, const FBXGeometry& geometry);
QVariantHash getMapping() const;
@ -43,8 +44,9 @@ private:
QComboBox* createJointBox(bool withNone = true) const;
QDoubleSpinBox* createTranslationBox() const;
void insertJointMapping(QVariantHash& joints, const QString& joint, const QString& name) const;
QString getType() const;
ModelType _modelType;
FSTReader::ModelType _modelType;
QVariantHash _originalMapping;
QString _basePath;
FBXGeometry _geometry;

View file

@ -18,8 +18,9 @@
#include "ModelSelector.h"
static const QString AVATAR_HEAD_STRING = "Avatar Head";
static const QString AVATAR_BODY_STRING = "Avatar Body";
static const QString AVATAR_HEAD_STRING = "Avatar Head Only";
static const QString AVATAR_BODY_STRING = "Avatar Body Only";
static const QString AVATAR_HEAD_AND_BODY_STRING = "Avatar Body with Head";
static const QString AVATAR_ATTACHEMENT_STRING = "Avatar Attachment";
static const QString ENTITY_MODEL_STRING = "Entity Model";
@ -36,6 +37,7 @@ ModelSelector::ModelSelector() {
_modelType = new QComboBox(this);
_modelType->addItem(AVATAR_HEAD_STRING);
_modelType->addItem(AVATAR_BODY_STRING);
_modelType->addItem(AVATAR_HEAD_AND_BODY_STRING);
_modelType->addItem(AVATAR_ATTACHEMENT_STRING);
_modelType->addItem(ENTITY_MODEL_STRING);
form->addRow("Model Type:", _modelType);
@ -50,17 +52,19 @@ QFileInfo ModelSelector::getFileInfo() const {
return _modelFile;
}
ModelType ModelSelector::getModelType() const {
FSTReader::ModelType ModelSelector::getModelType() const {
QString text = _modelType->currentText();
if (text == AVATAR_HEAD_STRING) {
return HEAD_MODEL;
return FSTReader::HEAD_MODEL;
} else if (text == AVATAR_BODY_STRING) {
return SKELETON_MODEL;
return FSTReader::BODY_ONLY_MODEL;
} else if (text == AVATAR_HEAD_AND_BODY_STRING) {
return FSTReader::HEAD_AND_BODY_MODEL;
} else if (text == AVATAR_ATTACHEMENT_STRING) {
return ATTACHMENT_MODEL;
return FSTReader::ATTACHMENT_MODEL;
} else if (text == ENTITY_MODEL_STRING) {
return ENTITY_MODEL;
return FSTReader::ENTITY_MODEL;
} else {
Q_UNREACHABLE();
}

View file

@ -29,7 +29,7 @@ public:
ModelSelector();
QFileInfo getFileInfo() const;
ModelType getModelType() const;
FSTReader::ModelType getModelType() const;
public slots:
virtual void accept();

View file

@ -638,7 +638,7 @@ QScriptValue WindowScriptingInterface::showBrowse(const QString& title, const QS
/// \param const QString& nameFilter filter to filter filenames
/// \return QScriptValue file path as a string if one was selected, otherwise `QScriptValue::NullValue`
QScriptValue WindowScriptingInterface::showS3Browse(const QString& nameFilter) {
ModelsBrowser browser(ENTITY_MODEL);
ModelsBrowser browser(FSTReader::ENTITY_MODEL);
if (nameFilter != "") {
browser.setNameFilter(nameFilter);
}

View file

@ -164,7 +164,7 @@ AttachmentData AttachmentPanel::getAttachmentData() const {
}
void AttachmentPanel::chooseModelURL() {
ModelsBrowser modelBrowser(ATTACHMENT_MODEL, this);
ModelsBrowser modelBrowser(FSTReader::ATTACHMENT_MODEL, this);
connect(&modelBrowser, SIGNAL(selected(QString)), SLOT(setModelURL(const QString&)));
modelBrowser.browse();
}

View file

@ -28,7 +28,7 @@
#include "ModelsBrowser.h"
const char* MODEL_TYPE_NAMES[] = { "entities", "heads", "skeletons", "attachments" };
const char* MODEL_TYPE_NAMES[] = { "entities", "heads", "skeletons", "skeletons", "attachments" };
static const QString S3_URL = "http://s3.amazonaws.com/hifi-public";
static const QString PUBLIC_URL = "http://public.highfidelity.io";
@ -71,7 +71,7 @@ static const QString propertiesIds[MODEL_METADATA_COUNT] = {
"Tags"
};
ModelsBrowser::ModelsBrowser(ModelType modelsType, QWidget* parent) :
ModelsBrowser::ModelsBrowser(FSTReader::ModelType modelsType, QWidget* parent) :
QWidget(parent, Qt::WindowStaysOnTopHint),
_handler(new ModelHandler(modelsType))
{
@ -184,7 +184,7 @@ void ModelsBrowser::browse() {
}
ModelHandler::ModelHandler(ModelType modelsType, QWidget* parent) :
ModelHandler::ModelHandler(FSTReader::ModelType modelsType, QWidget* parent) :
QObject(parent),
_initiateExit(false),
_type(modelsType),

View file

@ -16,21 +16,16 @@
#include <QStandardItemModel>
#include <QTreeView>
class QNetworkReply;
#include <FSTReader.h>
enum ModelType {
ENTITY_MODEL,
HEAD_MODEL,
SKELETON_MODEL,
ATTACHMENT_MODEL
};
class QNetworkReply;
extern const char* MODEL_TYPE_NAMES[];
class ModelHandler : public QObject {
Q_OBJECT
public:
ModelHandler(ModelType modelsType, QWidget* parent = NULL);
ModelHandler(FSTReader::ModelType modelsType, QWidget* parent = NULL);
void lockModel() { _lock.lockForRead(); }
QStandardItemModel* getModel() { return &_model; }
@ -51,7 +46,7 @@ private slots:
private:
bool _initiateExit;
ModelType _type;
FSTReader::ModelType _type;
QReadWriteLock _lock;
QStandardItemModel _model;
QString _nameFilter;
@ -66,7 +61,7 @@ class ModelsBrowser : public QWidget {
Q_OBJECT
public:
ModelsBrowser(ModelType modelsType, QWidget* parent = NULL);
ModelsBrowser(FSTReader::ModelType modelsType, QWidget* parent = NULL);
QString getSelectedFile() { return _selectedFile; }
signals:

View file

@ -67,13 +67,13 @@ void PreferencesDialog::setSkeletonUrl(QString modelUrl) {
}
void PreferencesDialog::openHeadModelBrowser() {
ModelsBrowser modelBrowser(HEAD_MODEL);
ModelsBrowser modelBrowser(FSTReader::HEAD_MODEL);
connect(&modelBrowser, &ModelsBrowser::selected, this, &PreferencesDialog::setHeadUrl);
modelBrowser.browse();
}
void PreferencesDialog::openBodyModelBrowser() {
ModelsBrowser modelBrowser(SKELETON_MODEL);
ModelsBrowser modelBrowser(FSTReader::HEAD_AND_BODY_MODEL);
connect(&modelBrowser, &ModelsBrowser::selected, this, &PreferencesDialog::setSkeletonUrl);
modelBrowser.browse();
}

View file

@ -13,7 +13,7 @@
#include "FSTReader.h"
QVariantHash parseMapping(QIODevice* device) {
QVariantHash FSTReader::parseMapping(QIODevice* device) {
QVariantHash properties;
QByteArray line;
@ -48,13 +48,13 @@ QVariantHash parseMapping(QIODevice* device) {
return properties;
}
QVariantHash readMapping(const QByteArray& data) {
QVariantHash FSTReader::readMapping(const QByteArray& data) {
QBuffer buffer(const_cast<QByteArray*>(&data));
buffer.open(QIODevice::ReadOnly);
return parseMapping(&buffer);
return FSTReader::parseMapping(&buffer);
}
void writeVariant(QBuffer& buffer, QVariantHash::const_iterator& it) {
void FSTReader::writeVariant(QBuffer& buffer, QVariantHash::const_iterator& it) {
QByteArray key = it.key().toUtf8() + " = ";
QVariantHash hashValue = it.value().toHash();
if (hashValue.isEmpty()) {
@ -76,8 +76,8 @@ void writeVariant(QBuffer& buffer, QVariantHash::const_iterator& it) {
}
}
QByteArray writeMapping(const QVariantHash& mapping) {
static const QStringList PREFERED_ORDER = QStringList() << NAME_FIELD << SCALE_FIELD << FILENAME_FIELD
QByteArray FSTReader::writeMapping(const QVariantHash& mapping) {
static const QStringList PREFERED_ORDER = QStringList() << NAME_FIELD << TYPE_FIELD << SCALE_FIELD << FILENAME_FIELD
<< TEXDIR_FIELD << JOINT_FIELD << FREE_JOINT_FIELD
<< BLENDSHAPE_FIELD << JOINT_INDEX_FIELD;
QBuffer buffer;
@ -96,4 +96,76 @@ QByteArray writeMapping(const QVariantHash& mapping) {
}
}
return buffer.data();
}
}
QHash<FSTReader::ModelType, QString> FSTReader::_typesToNames;
QString FSTReader::getNameFromType(ModelType modelType) {
if (_typesToNames.size() == 0) {
_typesToNames[ENTITY_MODEL] = "entity";
_typesToNames[HEAD_MODEL] = "head";
_typesToNames[BODY_ONLY_MODEL] = "body";
_typesToNames[HEAD_AND_BODY_MODEL] = "body+head";
_typesToNames[ATTACHMENT_MODEL] = "attachment";
}
return _typesToNames[modelType];
}
QHash<QString, FSTReader::ModelType> FSTReader::_namesToTypes;
FSTReader::ModelType FSTReader::getTypeFromName(const QString& name) {
if (_namesToTypes.size() == 0) {
_namesToTypes["entity"] = ENTITY_MODEL;
_namesToTypes["head"] = HEAD_MODEL ;
_namesToTypes["body"] = BODY_ONLY_MODEL;
_namesToTypes["body+head"] = HEAD_AND_BODY_MODEL;
_namesToTypes["attachment"] = ATTACHMENT_MODEL;
}
return _namesToTypes[name];
}
FSTReader::ModelType FSTReader::predictModelType(const QVariantHash& mapping) {
QVariantHash joints;
if (mapping.contains("joint") && mapping["joint"].type() == QVariant::Hash) {
joints = mapping["joint"].toHash();
}
// if the mapping includes the type hint... then we trust the mapping
if (mapping.contains(TYPE_FIELD)) {
return FSTReader::getTypeFromName(mapping[TYPE_FIELD].toString());
}
// check for blendshapes
bool hasBlendshapes = mapping.contains(BLENDSHAPE_FIELD);
// a Head needs to have these minimum fields...
//joint = jointEyeLeft = EyeL = 1
//joint = jointEyeRight = EyeR = 1
//joint = jointNeck = Head = 1
bool hasHeadMinimum = joints.contains("jointNeck") && joints.contains("jointEyeLeft") && joints.contains("jointEyeRight");
// a Body needs to have these minimum fields...
//joint = jointRoot = Hips
//joint = jointLean = Spine
//joint = jointNeck = Neck
//joint = jointHead = HeadTop_End
bool hasBodyMinimumJoints = joints.contains("jointRoot") && joints.contains("jointLean") && joints.contains("jointNeck")
&& joints.contains("jointHead");
bool isLikelyHead = hasBlendshapes || hasHeadMinimum;
if (isLikelyHead && hasBodyMinimumJoints) {
return HEAD_AND_BODY_MODEL;
}
if (isLikelyHead) {
return HEAD_MODEL;
}
if (hasBodyMinimumJoints) {
return BODY_ONLY_MODEL;
}
return ENTITY_MODEL;
}

View file

@ -12,9 +12,11 @@
#ifndef hifi_FSTReader_h
#define hifi_FSTReader_h
#include <QBuffer>
#include <QVariantHash>
static const QString NAME_FIELD = "name";
static const QString TYPE_FIELD = "type";
static const QString FILENAME_FIELD = "filename";
static const QString TEXDIR_FIELD = "texdir";
static const QString LOD_FIELD = "lod";
@ -27,10 +29,35 @@ static const QString JOINT_FIELD = "joint";
static const QString FREE_JOINT_FIELD = "freeJoint";
static const QString BLENDSHAPE_FIELD = "bs";
/// Reads an FST mapping from the supplied data.
QVariantHash readMapping(const QByteArray& data);
class FSTReader {
public:
/// Writes an FST mapping to a byte array.
QByteArray writeMapping(const QVariantHash& mapping);
enum ModelType {
ENTITY_MODEL,
HEAD_MODEL,
BODY_ONLY_MODEL,
HEAD_AND_BODY_MODEL,
ATTACHMENT_MODEL
};
/// Reads an FST mapping from the supplied data.
static QVariantHash readMapping(const QByteArray& data);
/// Writes an FST mapping to a byte array.
static QByteArray writeMapping(const QVariantHash& mapping);
/// Predicts the type of model by examining the mapping
static ModelType predictModelType(const QVariantHash& mapping);
static QString getNameFromType(ModelType modelType);
static FSTReader::ModelType getTypeFromName(const QString& name);
private:
static void writeVariant(QBuffer& buffer, QVariantHash::const_iterator& it);
static QVariantHash parseMapping(QIODevice* device);
static QHash<FSTReader::ModelType, QString> _typesToNames;
static QHash<QString, FSTReader::ModelType> _namesToTypes;
};
#endif // hifi_FSTReader_h

View file

@ -2132,7 +2132,7 @@ void NetworkGeometry::downloadFinished(QNetworkReply* reply) {
QUrl url = reply->url();
if (url.path().toLower().endsWith(".fst")) {
// it's a mapping file; parse it and get the mesh filename
_mapping = readMapping(reply->readAll());
_mapping = FSTReader::readMapping(reply->readAll());
reply->deleteLater();
QString filename = _mapping.value("filename").toString();
if (filename.isNull()) {

View file

@ -143,4 +143,12 @@ QDebug& operator<<(QDebug& dbg, const glm::mat4& m) {
return dbg << " ]}";
}
QDebug& operator<<(QDebug& dbg, const QVariantHash& v) {
dbg.nospace() << "[";
for (QVariantHash::const_iterator it = v.constBegin(); it != v.constEnd(); it++) {
dbg << it.key() << ":" << it.value();
}
return dbg << " ]";
}
#endif // QT_NO_DEBUG_STREAM

View file

@ -15,6 +15,7 @@
#include <iostream>
#include <QByteArray>
#include <QVariantHash>
#include <glm/glm.hpp>
#include <glm/gtx/quaternion.hpp>
@ -54,6 +55,7 @@ QDebug& operator<<(QDebug& s, const glm::vec3& v);
QDebug& operator<<(QDebug& s, const glm::vec4& v);
QDebug& operator<<(QDebug& s, const glm::quat& q);
QDebug& operator<<(QDebug& s, const glm::mat4& m);
QDebug& operator<<(QDebug& dbg, const QVariantHash& v);
#endif // QT_NO_DEBUG_STREAM
#endif // hifi_StreamUtils_h