1714 lines · 89.8 KB · C++
RawDownload
////  FBXSerializer.cpp//  libraries/model-serializers/src////  Created by Andrzej Kapolka on 9/18/13.//  Copyright 2013 High Fidelity, Inc.//  Copyright 2022-2023 Overte e.V.////  Distributed under the Apache License, Version 2.0.//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html// #include "FBXSerializer.h" #include <QBuffer>#include <QRegularExpression> #include <glm/gtc/quaternion.hpp>#include <glm/gtx/quaternion.hpp>#include <glm/gtx/transform.hpp> #include <BlendshapeConstants.h> #include <hfm/ModelFormatLogging.h> // TOOL: Uncomment the following line to enable the filtering of all the unknown fields of a node so we can break point easily while loading a model with problems...//#define DEBUG_FBXSERIALIZER using namespace std; glm::vec3 parseVec3(const QString& string) {    QStringList elements = string.split(',');    if (elements.isEmpty()) {        return glm::vec3();    }    glm::vec3 value;    for (int i = 0; i < 3; i++) {        // duplicate last value if there aren't three elements        value[i] = elements.at(min(i, elements.size() - 1)).trimmed().toFloat();    }    return value;} enum RotationOrder {    OrderXYZ = 0,    OrderXZY,    OrderYZX,    OrderYXZ,    OrderZXY,    OrderZYX,    OrderSphericXYZ}; bool haveReportedUnhandledRotationOrder = false; // Report error only once per FBX file. glm::vec3 convertRotationToXYZ(int rotationOrder, const glm::vec3& rotation) {    // Convert rotation with given rotation order to have order XYZ.    if (rotationOrder == OrderXYZ) {        return rotation;    }     glm::quat xyzRotation;     switch (rotationOrder) {        case OrderXZY:            xyzRotation = glm::quat(glm::radians(glm::vec3(0, rotation.y, 0)))                * (glm::quat(glm::radians(glm::vec3(0, 0, rotation.z))) * glm::quat(glm::radians(glm::vec3(rotation.x, 0, 0))));            break;        case OrderYZX:            xyzRotation = glm::quat(glm::radians(glm::vec3(rotation.x, 0, 0)))                * (glm::quat(glm::radians(glm::vec3(0, 0, rotation.z))) * glm::quat(glm::radians(glm::vec3(0, rotation.y, 0))));            break;        case OrderYXZ:            xyzRotation = glm::quat(glm::radians(glm::vec3(0, 0, rotation.z)))                * (glm::quat(glm::radians(glm::vec3(rotation.x, 0, 0))) * glm::quat(glm::radians(glm::vec3(0, rotation.y, 0))));            break;        case OrderZXY:            xyzRotation = glm::quat(glm::radians(glm::vec3(0, rotation.y, 0)))                * (glm::quat(glm::radians(glm::vec3(rotation.x, 0, 0))) * glm::quat(glm::radians(glm::vec3(0, 0, rotation.z))));            break;        case OrderZYX:            xyzRotation = glm::quat(glm::radians(glm::vec3(rotation.x, 0, 0)))                * (glm::quat(glm::radians(glm::vec3(0, rotation.y, 0))) * glm::quat(glm::radians(glm::vec3(0, 0, rotation.z))));            break;        default:            // FIXME: Handle OrderSphericXYZ.            if (!haveReportedUnhandledRotationOrder) {                qCDebug(modelformat) << "ERROR: Unhandled rotation order in FBX file:" << rotationOrder;                haveReportedUnhandledRotationOrder = true;            }            return rotation;    }     return glm::degrees(safeEulerAngles(xyzRotation));} QString processID(const QString& id) {    // Blender (at least) prepends a type to the ID, so strip it out    return id.mid(id.lastIndexOf(':') + 1);} QString getModelName(const QVariantList& properties) {    QString name;    if (properties.size() == 3) {        name = properties.at(1).toString();        name = processID(name.left(name.indexOf(QChar('\0'))));    } else {        name = processID(properties.at(0).toString());    }    return name;} QString getMaterialName(const QVariantList& properties) {    QString name;    if (properties.size() == 1 || properties.at(1).toString().isEmpty()) {        name = properties.at(0).toString();        name = processID(name.left(name.indexOf(QChar('\0'))));    } else {        name = processID(properties.at(1).toString());    }    return name;} QString getID(const QVariantList& properties, int index = 0) {    return processID(properties.at(index).toString());} class FBXModel {public:    QString name;     int parentIndex;    glm::vec3 translation;    glm::mat4 preTransform;    glm::quat preRotation;    glm::quat rotation;    glm::quat postRotation;    glm::mat4 postTransform;     glm::vec3 rotationMin;  // radians    glm::vec3 rotationMax;  // radians     bool hasGeometricOffset;    glm::vec3 geometricTranslation;    glm::quat geometricRotation;    glm::vec3 geometricScaling;    bool isLimbNode;  // is this FBXModel transform is a "LimbNode" i.e. a joint}; glm::mat4 getGlobalTransform(const QMultiMap<QString, QString>& _connectionParentMap,        const QHash<QString, FBXModel>& fbxModels, QString nodeID, bool mixamoHack, const QString& url) {    glm::mat4 globalTransform;    QVector<QString> visitedNodes; // Used to prevent following a cycle    while (!nodeID.isNull()) {        visitedNodes.append(nodeID); // Append each node we visit         const FBXModel& fbxModel = fbxModels.value(nodeID);        globalTransform = glm::translate(fbxModel.translation) * fbxModel.preTransform * glm::mat4_cast(fbxModel.preRotation *            fbxModel.rotation * fbxModel.postRotation) * fbxModel.postTransform * globalTransform;        if (fbxModel.hasGeometricOffset) {            glm::mat4 geometricOffset = createMatFromScaleQuatAndPos(fbxModel.geometricScaling, fbxModel.geometricRotation, fbxModel.geometricTranslation);            globalTransform = globalTransform * geometricOffset;        }         if (mixamoHack) {            // there's something weird about the models from Mixamo Fuse; they don't skin right with the full transform            return globalTransform;        }        QList<QString> parentIDs = _connectionParentMap.values(nodeID);        nodeID = QString();        foreach (const QString& parentID, parentIDs) {            if (visitedNodes.contains(parentID)) {                qCWarning(modelformat) << "Ignoring loop detected in FBX connection map for" << url;                continue;            }             if (fbxModels.contains(parentID)) {                nodeID = parentID;                break;            }        }    }    return globalTransform;} class ExtractedBlendshape {public:    QString id;    HFMBlendshape blendshape;}; void printNode(const FBXNode& node, int indentLevel) {    int indentLength = 2;    hifi::ByteArray spaces(indentLevel * indentLength, ' ');    QDebug nodeDebug = qDebug(modelformat);     nodeDebug.nospace() << spaces.data() << node.name.data() << ": ";    foreach (const QVariant& property, node.properties) {        nodeDebug << property;    }     foreach (const FBXNode& child, node.children) {        printNode(child, indentLevel + 1);    }} class Cluster {public:    QVector<int> indices;    QVector<double> weights;    glm::mat4 transformLink;}; void appendModelIDs(const QString& parentID, const QMultiMap<QString, QString>& connectionChildMap,        QHash<QString, FBXModel>& fbxModels, QSet<QString>& remainingModels, QVector<QString>& modelIDs, bool isRootNode = false) {    if (remainingModels.contains(parentID)) {        modelIDs.append(parentID);        remainingModels.remove(parentID);    }    int parentIndex = isRootNode ? -1 : modelIDs.size() - 1;    foreach (const QString& childID, connectionChildMap.values(parentID)) {        if (remainingModels.contains(childID)) {            FBXModel& fbxModel = fbxModels[childID];            if (fbxModel.parentIndex == -1) {                fbxModel.parentIndex = parentIndex;                appendModelIDs(childID, connectionChildMap, fbxModels, remainingModels, modelIDs);            }        }    }} HFMBlendshape extractBlendshape(const FBXNode& object) {    HFMBlendshape blendshape;    foreach (const FBXNode& data, object.children) {        if (data.name == "Indexes") {            blendshape.indices = FBXSerializer::getIntVector(data);         } else if (data.name == "Vertices") {            blendshape.vertices = FBXSerializer::createVec3Vector(FBXSerializer::getDoubleVector(data));         } else if (data.name == "Normals") {            blendshape.normals = FBXSerializer::createVec3Vector(FBXSerializer::getDoubleVector(data));        }    }    return blendshape;} QVector<int> getIndices(const QVector<QString> ids, QVector<QString> modelIDs) {    QVector<int> indices;    foreach (const QString& id, ids) {        int index = modelIDs.indexOf(id);        if (index != -1) {            indices.append(index);        }    }    return indices;} typedef QPair<int, float> WeightedIndex; void addBlendshapes(const ExtractedBlendshape& extracted, const QList<WeightedIndex>& indices, ExtractedMesh& extractedMesh) {    foreach (const WeightedIndex& index, indices) {        extractedMesh.mesh.blendshapes.resize(max(extractedMesh.mesh.blendshapes.size(), index.first + 1));        extractedMesh.blendshapeIndexMaps.resize(extractedMesh.mesh.blendshapes.size());        HFMBlendshape& blendshape = extractedMesh.mesh.blendshapes[index.first];        QHash<int, int>& blendshapeIndexMap = extractedMesh.blendshapeIndexMaps[index.first];        for (int i = 0; i < extracted.blendshape.indices.size(); i++) {            int oldIndex = extracted.blendshape.indices.at(i);            for (QMultiHash<int, int>::const_iterator it = extractedMesh.newIndices.constFind(oldIndex);                    it != extractedMesh.newIndices.constEnd() && it.key() == oldIndex; it++) {                QHash<int, int>::iterator blendshapeIndex = blendshapeIndexMap.find(it.value());                if (blendshapeIndex == blendshapeIndexMap.end()) {                    blendshapeIndexMap.insert(it.value(), blendshape.indices.size());                    blendshape.indices.append(it.value());                    blendshape.vertices.append(extracted.blendshape.vertices.at(i) * index.second);                    blendshape.normals.append(extracted.blendshape.normals.at(i) * index.second);                } else {                    blendshape.vertices[*blendshapeIndex] += extracted.blendshape.vertices.at(i) * index.second;                    blendshape.normals[*blendshapeIndex] += extracted.blendshape.normals.at(i) * index.second;                }            }        }    }} QString getTopModelID(const QMultiMap<QString, QString>& connectionParentMap,        const QHash<QString, FBXModel>& fbxModels, const QString& modelID, const QString& url) {    QString topID = modelID;    QVector<QString> visitedNodes; // Used to prevent following a cycle    forever {        visitedNodes.append(topID); // Append each node we visit         foreach (const QString& parentID, connectionParentMap.values(topID)) {            if (visitedNodes.contains(parentID)) {                qCWarning(modelformat) << "Ignoring loop detected in FBX connection map for" << url;                continue;            }             if (fbxModels.contains(parentID)) {                topID = parentID;                goto outerContinue;            }        }        return topID;         outerContinue: ;    }} QString getString(const QVariant& value) {    // if it's a list, return the first entry    QVariantList list = value.toList();    return list.isEmpty() ? value.toString() : list.at(0).toString();} class AnimationCurve {public:    QVector<float> values;}; bool checkMaterialsHaveTextures(const QHash<QString, HFMMaterial>& materials,        const QHash<QString, hifi::ByteArray>& textureFilenames, const QMultiMap<QString, QString>& _connectionChildMap) {    foreach (const QString& materialID, materials.keys()) {        foreach (const QString& childID, _connectionChildMap.values(materialID)) {            if (textureFilenames.contains(childID)) {                return true;            }        }    }    return false;} int matchTextureUVSetToAttributeChannel(const QString& texUVSetName, const QHash<QString, int>& texcoordChannels) {    if (texUVSetName.isEmpty()) {        return 0;    } else {        QHash<QString, int>::const_iterator tcUnit = texcoordChannels.find(texUVSetName);        if (tcUnit != texcoordChannels.end()) {            int channel = (*tcUnit);            if (channel >= 2) {                channel = 0;            }            return channel;        } else {            return 0;        }    }}  HFMLight extractLight(const FBXNode& object) {    HFMLight light;    foreach (const FBXNode& subobject, object.children) {        QString childname = QString(subobject.name);        if (subobject.name == "Properties70") {            foreach (const FBXNode& property, subobject.children) {                int valIndex = 4;                QString propName = QString(property.name);                if (property.name == "P") {                    QString propname = property.properties.at(0).toString();                    if (propname == "Intensity") {                        light.intensity = 0.01f * property.properties.at(valIndex).value<float>();                    } else if (propname == "Color") {                        light.color = FBXSerializer::getVec3(property.properties, valIndex);                    }                }            }        } else if ( subobject.name == "GeometryVersion"                   || subobject.name == "TypeFlags") {        }    }#if defined(DEBUG_FBXSERIALIZER)     QString type = object.properties.at(0).toString();    type = object.properties.at(1).toString();    type = object.properties.at(2).toString();     foreach (const QVariant& prop, object.properties) {        QString proptype = prop.typeName();        QString propval = prop.toString();        if (proptype == "Properties70") {        }    }#endif     return light;} hifi::ByteArray fileOnUrl(const hifi::ByteArray& filepath, const QString& url) {    // in order to match the behaviour when loading models from remote URLs    // we assume that all external textures are right beside the loaded model    // ignoring any relative paths or absolute paths inside of models     return filepath.mid(filepath.lastIndexOf('/') + 1);} HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const QString& url) {    const FBXNode& node = _rootNode;    bool deduplicateIndices = mapping["deduplicateIndices"].toBool();     QMap<QString, ExtractedMesh> meshes;    QHash<QString, QString> modelIDsToNames;    QHash<QString, int> meshIDsToMeshIndices;    QHash<QString, QString> ooChildToParent;     QVector<ExtractedBlendshape> blendshapes;     QHash<QString, FBXModel> fbxModels;    QHash<QString, Cluster> clusters;    QHash<QString, AnimationCurve> animationCurves;     QHash<QString, QString> typeFlags;     QHash<QString, QString> localRotations;    QHash<QString, QString> localTranslations;    QHash<QString, QString> xComponents;    QHash<QString, QString> yComponents;    QHash<QString, QString> zComponents;     std::map<QString, HFMLight> lights;     hifi::VariantMultiHash blendshapeMappings = mapping.value("bs").toHash();     QMultiHash<hifi::ByteArray, WeightedIndex> blendshapeIndices;    for (int i = 0;; i++) {        hifi::ByteArray blendshapeName = BLENDSHAPE_NAMES[i];        if (blendshapeName.isEmpty()) {            break;        }        QList<QVariant> mappings = blendshapeMappings.values(blendshapeName);        if (mappings.isEmpty()) {            blendshapeIndices.insert(blendshapeName, WeightedIndex(i, 1.0f));        } else {            foreach (const QVariant& mapping, mappings) {                QVariantList blendshapeMapping = mapping.toList();                blendshapeIndices.insert(blendshapeMapping.at(0).toByteArray(),                   WeightedIndex(i, blendshapeMapping.at(1).toFloat()));            }        }    }    QMultiHash<QString, WeightedIndex> blendshapeChannelIndices;#if defined(DEBUG_FBXSERIALIZER)    int unknown = 0;#endif    HFMModel* hfmModelPtr = new HFMModel;    HFMModel& hfmModel = *hfmModelPtr;     hfmModel.originalURL = url;     float unitScaleFactor = 1.0f;    glm::quat upAxisZRotation;    bool applyUpAxisZRotation = false;    glm::vec3 ambientColor;    QString hifiGlobalNodeID;    unsigned int meshIndex = 0;    haveReportedUnhandledRotationOrder = false;    int fbxVersionNumber = -1;    bool isBlenderVersionLower280 = false;     foreach (const FBXNode& child, node.children) {        if (child.name == "Creator") {            // Match Blender version lower than 2.80            // Example version string from Blender 2.78: "Blender (stable FBX IO) - 2.78 (sub 0) - 3.7.7"            // Example version string from Blender 2.92: "Blender (stable FBX IO) - 2.92.0 - 4.22.0"            QRegularExpression blenderVersionRegex(".*Blender.*-\\s(\\d+)\\.(\\d+).*-.*");            QRegularExpressionMatch blenderVersionMatch = blenderVersionRegex.match(child.properties.at(0).toString());             if (blenderVersionMatch.hasMatch()) {                int major =  blenderVersionMatch.captured(1).toInt();                int minor =  blenderVersionMatch.captured(2).toInt();                 isBlenderVersionLower280 = major < 2 || (major == 2 && minor < 80);            }        }    }     foreach (const FBXNode& child, node.children) {         if (child.name == "FBXHeaderExtension") {            foreach (const FBXNode& object, child.children) {                if (object.name == "SceneInfo") {                    foreach (const FBXNode& subobject, object.children) {                        if (subobject.name == "MetaData") {                            foreach (const FBXNode& subsubobject, subobject.children) {                                if (subsubobject.name == "Author") {                                    hfmModel.author = subsubobject.properties.at(0).toString();                                }                            }                        } else if (subobject.name == "Properties70") {                            foreach (const FBXNode& subsubobject, subobject.children) {                                static const QVariant APPLICATION_NAME = QVariant(hifi::ByteArray("Original|ApplicationName"));                                if (subsubobject.name == "P" && subsubobject.properties.size() >= 5 &&                                        subsubobject.properties.at(0) == APPLICATION_NAME) {                                    hfmModel.applicationName = subsubobject.properties.at(4).toString();                                }                            }                        }                    }                } else if (object.name == "FBXVersion") {                    fbxVersionNumber = object.properties.at(0).toInt();

Lines 1–500 of 1714.