From aa1aad0a0995fbe7db54209aa7a347e5bf228033 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 7 Sep 2017 10:36:19 -0700 Subject: [PATCH] Add FBXWriter for serializing FBXNode --- libraries/fbx/src/FBX.cpp | 10 + libraries/fbx/src/FBX.h | 333 +++++++++++++++++++++++++++ libraries/fbx/src/FBXReader.cpp | 11 +- libraries/fbx/src/FBXReader.h | 303 +----------------------- libraries/fbx/src/FBXReader_Node.cpp | 20 +- libraries/fbx/src/FBXWriter.cpp | 253 ++++++++++++++++++++ libraries/fbx/src/FBXWriter.h | 28 +++ 7 files changed, 641 insertions(+), 317 deletions(-) create mode 100644 libraries/fbx/src/FBX.cpp create mode 100644 libraries/fbx/src/FBX.h create mode 100644 libraries/fbx/src/FBXWriter.cpp create mode 100644 libraries/fbx/src/FBXWriter.h diff --git a/libraries/fbx/src/FBX.cpp b/libraries/fbx/src/FBX.cpp new file mode 100644 index 0000000000..3f18b3e678 --- /dev/null +++ b/libraries/fbx/src/FBX.cpp @@ -0,0 +1,10 @@ +// +// FBX.cpp +// libraries/fbx/src +// +// Created by Ryan Huffman on 9/5/17. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// diff --git a/libraries/fbx/src/FBX.h b/libraries/fbx/src/FBX.h new file mode 100644 index 0000000000..9e1982f9e0 --- /dev/null +++ b/libraries/fbx/src/FBX.h @@ -0,0 +1,333 @@ +// +// FBX.h +// libraries/fbx/src +// +// Created by Ryan Huffman on 9/5/17. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_FBX_h_ +#define hifi_FBX_h_ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include + +static const QByteArray FBX_BINARY_PROLOG = "Kaydara FBX Binary "; +static const int FBX_HEADER_BYTES_BEFORE_VERSION = 23; +static const quint32 FBX_VERSION_2016 = 7500; + +class FBXNode; +using FBXNodeList = QList; + + +/// A node within an FBX document. +class FBXNode { +public: + QByteArray name; + QVariantList properties; + FBXNodeList children; +}; + + +/// A single blendshape extracted from an FBX document. +class FBXBlendshape { +public: + QVector indices; + QVector vertices; + QVector normals; +}; + +struct FBXJointShapeInfo { + // same units and frame as FBXJoint.translation + glm::vec3 avgPoint; + std::vector dots; + std::vector points; + std::vector debugLines; +}; + +/// A single joint (transformation node) extracted from an FBX document. +class FBXJoint { +public: + + FBXJointShapeInfo shapeInfo; + QVector freeLineage; + bool isFree; + int parentIndex; + float distanceToParent; + + // http://download.autodesk.com/us/fbx/20112/FBX_SDK_HELP/SDKRef/a00209.html + + glm::vec3 translation; // T + glm::mat4 preTransform; // Roff * Rp + glm::quat preRotation; // Rpre + glm::quat rotation; // R + glm::quat postRotation; // Rpost + glm::mat4 postTransform; // Rp-1 * Soff * Sp * S * Sp-1 + + // World = ParentWorld * T * (Roff * Rp) * Rpre * R * Rpost * (Rp-1 * Soff * Sp * S * Sp-1) + + glm::mat4 transform; + glm::vec3 rotationMin; // radians + glm::vec3 rotationMax; // radians + glm::quat inverseDefaultRotation; + glm::quat inverseBindRotation; + glm::mat4 bindTransform; + QString name; + bool isSkeletonJoint; + bool bindTransformFoundInCluster; + + // geometric offset is applied in local space but does NOT affect children. + bool hasGeometricOffset; + glm::vec3 geometricTranslation; + glm::quat geometricRotation; + glm::vec3 geometricScaling; +}; + + +/// A single binding to a joint in an FBX document. +class FBXCluster { +public: + + int jointIndex; + glm::mat4 inverseBindMatrix; +}; + +const int MAX_NUM_PIXELS_FOR_FBX_TEXTURE = 2048 * 2048; + +/// A texture map in an FBX document. +class FBXTexture { +public: + QString name; + QByteArray filename; + QByteArray content; + + Transform transform; + int maxNumPixels { MAX_NUM_PIXELS_FOR_FBX_TEXTURE }; + int texcoordSet; + QString texcoordSetName; + + bool isBumpmap{ false }; + + bool isNull() const { return name.isEmpty() && filename.isEmpty() && content.isEmpty(); } +}; + +/// A single part of a mesh (with the same material). +class FBXMeshPart { +public: + + QVector quadIndices; // original indices from the FBX mesh + QVector quadTrianglesIndices; // original indices from the FBX mesh of the quad converted as triangles + QVector triangleIndices; // original indices from the FBX mesh + + QString materialID; +}; + +class FBXMaterial { +public: + FBXMaterial() {}; + FBXMaterial(const glm::vec3& diffuseColor, const glm::vec3& specularColor, const glm::vec3& emissiveColor, + float shininess, float opacity) : + diffuseColor(diffuseColor), + specularColor(specularColor), + emissiveColor(emissiveColor), + shininess(shininess), + opacity(opacity) {} + + void getTextureNames(QSet& textureList) const; + void setMaxNumPixelsPerTexture(int maxNumPixels); + + glm::vec3 diffuseColor{ 1.0f }; + float diffuseFactor{ 1.0f }; + glm::vec3 specularColor{ 0.02f }; + float specularFactor{ 1.0f }; + + glm::vec3 emissiveColor{ 0.0f }; + float emissiveFactor{ 0.0f }; + + float shininess{ 23.0f }; + float opacity{ 1.0f }; + + float metallic{ 0.0f }; + float roughness{ 1.0f }; + float emissiveIntensity{ 1.0f }; + float ambientFactor{ 1.0f }; + + QString materialID; + QString name; + QString shadingModel; + model::MaterialPointer _material; + + FBXTexture normalTexture; + FBXTexture albedoTexture; + FBXTexture opacityTexture; + FBXTexture glossTexture; + FBXTexture roughnessTexture; + FBXTexture specularTexture; + FBXTexture metallicTexture; + FBXTexture emissiveTexture; + FBXTexture occlusionTexture; + FBXTexture scatteringTexture; + FBXTexture lightmapTexture; + glm::vec2 lightmapParams{ 0.0f, 1.0f }; + + + bool isPBSMaterial{ false }; + // THe use XXXMap are not really used to drive which map are going or not, debug only + bool useNormalMap{ false }; + bool useAlbedoMap{ false }; + bool useOpacityMap{ false }; + bool useRoughnessMap{ false }; + bool useSpecularMap{ false }; + bool useMetallicMap{ false }; + bool useEmissiveMap{ false }; + bool useOcclusionMap{ false }; + + bool needTangentSpace() const; +}; + +/// A single mesh (with optional blendshapes) extracted from an FBX document. +class FBXMesh { +public: + + QVector parts; + + QVector vertices; + QVector normals; + QVector tangents; + QVector colors; + QVector texCoords; + QVector texCoords1; + QVector clusterIndices; + QVector clusterWeights; + + QVector clusters; + + Extents meshExtents; + glm::mat4 modelTransform; + + QVector blendshapes; + + unsigned int meshIndex; // the order the meshes appeared in the object file + + model::MeshPointer _mesh; +}; + +class ExtractedMesh { +public: + FBXMesh mesh; + QMultiHash newIndices; + QVector > blendshapeIndexMaps; + QVector > partMaterialTextures; + QHash texcoordSetMap; +}; + +/// A single animation frame extracted from an FBX document. +class FBXAnimationFrame { +public: + QVector rotations; + QVector translations; +}; + +/// A light in an FBX document. +class FBXLight { +public: + QString name; + Transform transform; + float intensity; + float fogValue; + glm::vec3 color; + + FBXLight() : + name(), + transform(), + intensity(1.0f), + fogValue(0.0f), + color(1.0f) + {} +}; + +Q_DECLARE_METATYPE(FBXAnimationFrame) +Q_DECLARE_METATYPE(QVector) + +/// A set of meshes extracted from an FBX document. +class FBXGeometry { +public: + using Pointer = std::shared_ptr; + + QString originalURL; + QString author; + 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 + bool hasSkeletonJoints; + + QVector meshes; + + QHash materials; + + glm::mat4 offset; // This includes offset, rotation, and scale as specified by the FST file + + int leftEyeJointIndex = -1; + int rightEyeJointIndex = -1; + int neckJointIndex = -1; + int rootJointIndex = -1; + int leanJointIndex = -1; + int headJointIndex = -1; + int leftHandJointIndex = -1; + int rightHandJointIndex = -1; + int leftToeJointIndex = -1; + int rightToeJointIndex = -1; + + float leftEyeSize = 0.0f; // Maximum mesh extents dimension + float rightEyeSize = 0.0f; + + QVector humanIKJointIndices; + + glm::vec3 palmDirection; + + glm::vec3 neckPivot; + + Extents bindExtents; + Extents meshExtents; + + QVector animationFrames; + + int getJointIndex(const QString& name) const { return jointIndices.value(name) - 1; } + QStringList getJointNames() const; + + bool hasBlendedMeshes() const; + + /// Returns the unscaled extents of the model's mesh + Extents getUnscaledMeshExtents() const; + + bool convexHullContains(const glm::vec3& point) const; + + QHash meshIndicesToModelNames; + + /// given a meshIndex this will return the name of the model that mesh belongs to if known + QString getModelNameOfMesh(int meshIndex) const; + + QList blendshapeChannelNames; +}; + +Q_DECLARE_METATYPE(FBXGeometry) +Q_DECLARE_METATYPE(FBXGeometry::Pointer) + +#endif // hifi_FBX_h_ diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 450aa296d8..ea4b00523e 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -168,7 +168,8 @@ QString getID(const QVariantList& properties, int index = 0) { return processID(properties.at(index).toString()); } -const char* HUMANIK_JOINTS[] = { +/// The names of the joints in the Maya HumanIK rig +static const std::array HUMANIK_JOINTS = { "RightHand", "RightForeArm", "RightArm", @@ -184,8 +185,7 @@ const char* HUMANIK_JOINTS[] = { "RightLeg", "LeftLeg", "RightFoot", - "LeftFoot", - "" + "LeftFoot" }; class FBXModel { @@ -512,11 +512,8 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS QVector humanIKJointNames; - for (int i = 0;; i++) { + for (int i = 0; i < HUMANIK_JOINTS.size(); i++) { QByteArray jointName = HUMANIK_JOINTS[i]; - if (jointName.isEmpty()) { - break; - } humanIKJointNames.append(processID(getString(joints.value(jointName, jointName)))); } QVector humanIKJointIDs(humanIKJointNames.size()); diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h index 170bbbf366..a600ac6bab 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -12,6 +12,8 @@ #ifndef hifi_FBXReader_h #define hifi_FBXReader_h +#include "FBX.h" + #include #include #include @@ -31,305 +33,6 @@ class QIODevice; class FBXNode; -typedef QList FBXNodeList; - -/// The names of the joints in the Maya HumanIK rig, terminated with an empty string. -extern const char* HUMANIK_JOINTS[]; - -/// A node within an FBX document. -class FBXNode { -public: - - QByteArray name; - QVariantList properties; - FBXNodeList children; -}; - -/// A single blendshape extracted from an FBX document. -class FBXBlendshape { -public: - - QVector indices; - QVector vertices; - QVector normals; -}; - -struct FBXJointShapeInfo { - // same units and frame as FBXJoint.translation - glm::vec3 avgPoint; - std::vector dots; - std::vector points; - std::vector debugLines; -}; - -/// A single joint (transformation node) extracted from an FBX document. -class FBXJoint { -public: - - FBXJointShapeInfo shapeInfo; - QVector freeLineage; - bool isFree; - int parentIndex; - float distanceToParent; - - // http://download.autodesk.com/us/fbx/20112/FBX_SDK_HELP/SDKRef/a00209.html - - glm::vec3 translation; // T - glm::mat4 preTransform; // Roff * Rp - glm::quat preRotation; // Rpre - glm::quat rotation; // R - glm::quat postRotation; // Rpost - glm::mat4 postTransform; // Rp-1 * Soff * Sp * S * Sp-1 - - // World = ParentWorld * T * (Roff * Rp) * Rpre * R * Rpost * (Rp-1 * Soff * Sp * S * Sp-1) - - glm::mat4 transform; - glm::vec3 rotationMin; // radians - glm::vec3 rotationMax; // radians - glm::quat inverseDefaultRotation; - glm::quat inverseBindRotation; - glm::mat4 bindTransform; - QString name; - bool isSkeletonJoint; - bool bindTransformFoundInCluster; - - // geometric offset is applied in local space but does NOT affect children. - bool hasGeometricOffset; - glm::vec3 geometricTranslation; - glm::quat geometricRotation; - glm::vec3 geometricScaling; -}; - - -/// A single binding to a joint in an FBX document. -class FBXCluster { -public: - - int jointIndex; - glm::mat4 inverseBindMatrix; -}; - -const int MAX_NUM_PIXELS_FOR_FBX_TEXTURE = 2048 * 2048; - -/// A texture map in an FBX document. -class FBXTexture { -public: - QString name; - QByteArray filename; - QByteArray content; - - Transform transform; - int maxNumPixels { MAX_NUM_PIXELS_FOR_FBX_TEXTURE }; - int texcoordSet; - QString texcoordSetName; - - bool isBumpmap{ false }; - - bool isNull() const { return name.isEmpty() && filename.isEmpty() && content.isEmpty(); } -}; - -/// A single part of a mesh (with the same material). -class FBXMeshPart { -public: - - QVector quadIndices; // original indices from the FBX mesh - QVector quadTrianglesIndices; // original indices from the FBX mesh of the quad converted as triangles - QVector triangleIndices; // original indices from the FBX mesh - - QString materialID; -}; - -class FBXMaterial { -public: - FBXMaterial() {}; - FBXMaterial(const glm::vec3& diffuseColor, const glm::vec3& specularColor, const glm::vec3& emissiveColor, - float shininess, float opacity) : - diffuseColor(diffuseColor), - specularColor(specularColor), - emissiveColor(emissiveColor), - shininess(shininess), - opacity(opacity) {} - - void getTextureNames(QSet& textureList) const; - void setMaxNumPixelsPerTexture(int maxNumPixels); - - glm::vec3 diffuseColor{ 1.0f }; - float diffuseFactor{ 1.0f }; - glm::vec3 specularColor{ 0.02f }; - float specularFactor{ 1.0f }; - - glm::vec3 emissiveColor{ 0.0f }; - float emissiveFactor{ 0.0f }; - - float shininess{ 23.0f }; - float opacity{ 1.0f }; - - float metallic{ 0.0f }; - float roughness{ 1.0f }; - float emissiveIntensity{ 1.0f }; - float ambientFactor{ 1.0f }; - - QString materialID; - QString name; - QString shadingModel; - model::MaterialPointer _material; - - FBXTexture normalTexture; - FBXTexture albedoTexture; - FBXTexture opacityTexture; - FBXTexture glossTexture; - FBXTexture roughnessTexture; - FBXTexture specularTexture; - FBXTexture metallicTexture; - FBXTexture emissiveTexture; - FBXTexture occlusionTexture; - FBXTexture scatteringTexture; - FBXTexture lightmapTexture; - glm::vec2 lightmapParams{ 0.0f, 1.0f }; - - - bool isPBSMaterial{ false }; - // THe use XXXMap are not really used to drive which map are going or not, debug only - bool useNormalMap{ false }; - bool useAlbedoMap{ false }; - bool useOpacityMap{ false }; - bool useRoughnessMap{ false }; - bool useSpecularMap{ false }; - bool useMetallicMap{ false }; - bool useEmissiveMap{ false }; - bool useOcclusionMap{ false }; - - bool needTangentSpace() const; -}; - -/// A single mesh (with optional blendshapes) extracted from an FBX document. -class FBXMesh { -public: - - QVector parts; - - QVector vertices; - QVector normals; - QVector tangents; - QVector colors; - QVector texCoords; - QVector texCoords1; - QVector clusterIndices; - QVector clusterWeights; - - QVector clusters; - - Extents meshExtents; - glm::mat4 modelTransform; - - QVector blendshapes; - - unsigned int meshIndex; // the order the meshes appeared in the object file - - model::MeshPointer _mesh; -}; - -class ExtractedMesh { -public: - FBXMesh mesh; - QMultiHash newIndices; - QVector > blendshapeIndexMaps; - QVector > partMaterialTextures; - QHash texcoordSetMap; -}; - -/// A single animation frame extracted from an FBX document. -class FBXAnimationFrame { -public: - QVector rotations; - QVector translations; -}; - -/// A light in an FBX document. -class FBXLight { -public: - QString name; - Transform transform; - float intensity; - float fogValue; - glm::vec3 color; - - FBXLight() : - name(), - transform(), - intensity(1.0f), - fogValue(0.0f), - color(1.0f) - {} -}; - -Q_DECLARE_METATYPE(FBXAnimationFrame) -Q_DECLARE_METATYPE(QVector) - -/// A set of meshes extracted from an FBX document. -class FBXGeometry { -public: - using Pointer = std::shared_ptr; - - QString originalURL; - QString author; - 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 - bool hasSkeletonJoints; - - QVector meshes; - - QHash materials; - - glm::mat4 offset; // This includes offset, rotation, and scale as specified by the FST file - - int leftEyeJointIndex = -1; - int rightEyeJointIndex = -1; - int neckJointIndex = -1; - int rootJointIndex = -1; - int leanJointIndex = -1; - int headJointIndex = -1; - int leftHandJointIndex = -1; - int rightHandJointIndex = -1; - int leftToeJointIndex = -1; - int rightToeJointIndex = -1; - - float leftEyeSize = 0.0f; // Maximum mesh extents dimension - float rightEyeSize = 0.0f; - - QVector humanIKJointIndices; - - glm::vec3 palmDirection; - - glm::vec3 neckPivot; - - Extents bindExtents; - Extents meshExtents; - - QVector animationFrames; - - int getJointIndex(const QString& name) const { return jointIndices.value(name) - 1; } - QStringList getJointNames() const; - - bool hasBlendedMeshes() const; - - /// Returns the unscaled extents of the model's mesh - Extents getUnscaledMeshExtents() const; - - bool convexHullContains(const glm::vec3& point) const; - - QHash meshIndicesToModelNames; - - /// given a meshIndex this will return the name of the model that mesh belongs to if known - QString getModelNameOfMesh(int meshIndex) const; - - QList blendshapeChannelNames; -}; - -Q_DECLARE_METATYPE(FBXGeometry) -Q_DECLARE_METATYPE(FBXGeometry::Pointer) /// Reads FBX geometry from the supplied model and mapping data. /// \exception QString if an error occurs in parsing @@ -402,7 +105,7 @@ class FBXReader { public: FBXGeometry* _fbxGeometry; - FBXNode _fbxNode; + FBXNode _rootNode; static FBXNode parseFBX(QIODevice* device); FBXGeometry* extractFBXGeometry(const QVariantHash& mapping, const QString& url); diff --git a/libraries/fbx/src/FBXReader_Node.cpp b/libraries/fbx/src/FBXReader_Node.cpp index d987f885eb..111b4a295a 100644 --- a/libraries/fbx/src/FBXReader_Node.cpp +++ b/libraries/fbx/src/FBXReader_Node.cpp @@ -24,15 +24,18 @@ #include #include "ModelFormatLogging.h" -template int streamSize() { +template +int streamSize() { return sizeof(T); } -template int streamSize() { +template +int streamSize() { return 1; } -template QVariant readBinaryArray(QDataStream& in, int& position) { +template +QVariant readBinaryArray(QDataStream& in, int& position) { quint32 arrayLength; quint32 encoding; quint32 compressedLength; @@ -350,8 +353,7 @@ FBXNode parseTextFBXNode(Tokenizer& tokenizer) { FBXNode FBXReader::parseFBX(QIODevice* device) { PROFILE_RANGE_EX(resource_parse, __FUNCTION__, 0xff0000ff, device); // verify the prolog - const QByteArray BINARY_PROLOG = "Kaydara FBX Binary "; - if (device->peek(BINARY_PROLOG.size()) != BINARY_PROLOG) { + if (device->peek(FBX_BINARY_PROLOG.size()) != FBX_BINARY_PROLOG) { // parse as a text file FBXNode top; Tokenizer tokenizer(device); @@ -377,15 +379,13 @@ FBXNode FBXReader::parseFBX(QIODevice* device) { // Bytes 0 - 20: Kaydara FBX Binary \x00(file - magic, with 2 spaces at the end, then a NULL terminator). // Bytes 21 - 22: [0x1A, 0x00](unknown but all observed files show these bytes). // Bytes 23 - 26 : unsigned int, the version number. 7300 for version 7.3 for example. - const int HEADER_BEFORE_VERSION = 23; - const quint32 VERSION_FBX2016 = 7500; - in.skipRawData(HEADER_BEFORE_VERSION); - int position = HEADER_BEFORE_VERSION; + in.skipRawData(FBX_HEADER_BYTES_BEFORE_VERSION); + int position = FBX_HEADER_BYTES_BEFORE_VERSION; quint32 fileVersion; in >> fileVersion; position += sizeof(fileVersion); qCDebug(modelformat) << "fileVersion:" << fileVersion; - bool has64BitPositions = (fileVersion >= VERSION_FBX2016); + bool has64BitPositions = (fileVersion >= FBX_VERSION_2016); // parse the top-level node FBXNode top; diff --git a/libraries/fbx/src/FBXWriter.cpp b/libraries/fbx/src/FBXWriter.cpp new file mode 100644 index 0000000000..a084d94a46 --- /dev/null +++ b/libraries/fbx/src/FBXWriter.cpp @@ -0,0 +1,253 @@ +// +// FBXWriter.cpp +// libraries/fbx/src +// +// Created by Ryan Huffman on 9/5/17. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "FBXWriter.h" + +#include + + +QByteArray FBXWriter::encodeFBX(const FBXNode& root) { + QByteArray data; + QDataStream out(&data, QIODevice::WriteOnly); + out.setByteOrder(QDataStream::LittleEndian); + out.setVersion(QDataStream::Qt_4_5); + + out.writeRawData(FBX_BINARY_PROLOG, FBX_BINARY_PROLOG.size()); + auto bytes = QByteArray(FBX_HEADER_BYTES_BEFORE_VERSION - FBX_BINARY_PROLOG.size(), '\0'); + out.writeRawData(bytes, bytes.size()); + + out << FBX_VERSION_2016; + + for (auto& child : root.children) { + encodeNode(out, child); + } + encodeNode(out, FBXNode()); + + return data; +} + +void FBXWriter::encodeNode(QDataStream& out, const FBXNode& node) { + qDebug() << "Encoding " << node.name; + + auto device = out.device(); + auto nodeStartPos = device->pos(); + + // endOffset (temporary, updated later) + out << (qint64)0; + + // Property count + out << (quint64)node.properties.size(); + + // Property list length (temporary, updated later) + out << (quint64)0; + + out << (quint8)node.name.size(); + out.writeRawData(node.name, node.name.size()); + + if (node.name == "Vertices") { + for (auto& prop : node.properties) { + qDebug() << "Properties: " << prop; + } + } + + auto nodePropertiesStartPos = device->pos(); + + for (const auto& prop : node.properties) { + encodeFBXProperty(out, prop); + } + + // Go back and write property list length + auto nodePropertiesEndPos = device->pos(); + device->seek(nodeStartPos + sizeof(qint64) + sizeof(quint64)); + out << (quint64)(nodePropertiesEndPos - nodePropertiesStartPos); + + device->seek(nodePropertiesEndPos); + + for (auto& child : node.children) { + encodeNode(out, child); + } + + if (node.children.length() > 0) { + encodeNode(out, FBXNode()); + } + + // Go back and write actual endOffset + auto nodeEndPos = device->pos(); + device->seek(nodeStartPos); + out << (qint64)(nodeEndPos); + + device->seek(nodeEndPos); +} + +void FBXWriter::encodeFBXProperty(QDataStream& out, const QVariant& prop) { + auto type = prop.userType(); + switch (type) { + case QVariant::Type::Bool: + + out.device()->write("C", 1); + out << prop.toBool(); + break; + + case QMetaType::Int: + out.device()->write("I", 1); + out << prop.toInt(); + break; + + encodeNode(out, FBXNode()); + case QMetaType::Float: + out.device()->write("F", 1); + out << prop.toFloat(); + break; + + case QMetaType::Double: + out.device()->write("D", 1); + out << prop.toDouble(); + break; + + case QMetaType::LongLong: + out.device()->write("L", 1); + out << prop.toLongLong(); + break; + + case QMetaType::QString: + { + auto& bytes = prop.toString().toUtf8(); + out << 'S'; + out << bytes.length(); + out << bytes; + out << (int32_t)bytes.size(); + out.writeRawData(bytes, bytes.size()); + break; + } + + case QMetaType::QByteArray: + { + auto& bytes = prop.toByteArray(); + out.device()->write("S", 1); + out << (int32_t)bytes.size(); + out.writeRawData(bytes, bytes.size()); + break; + } + + // TODO Delete? Do we ever use QList instead of QVector? + case QVariant::Type::List: + { + auto& list = prop.toList(); + auto listType = prop.userType(); + + switch (listType) { + case QMetaType::Float: + out.device()->write("f", 1); + out << (int32_t)list.length(); + out << (int32_t)0; + out << (int32_t)0; + for (auto& innerProp : list) { + out << prop.toFloat(); + } + break; + + case QMetaType::Double: + out.device()->write("d", 1); + out << (int32_t)list.length(); + out << (int32_t)0; + out << (int32_t)0; + for (auto& innerProp : list) { + out << prop.toDouble(); + } + break; + + case QMetaType::LongLong: + out.device()->write("l", 1); + out << (int32_t)list.length(); + out << (int32_t)0; + out << (int32_t)0; + for (auto& innerProp : list) { + out << prop.toLongLong(); + } + break; + + case QMetaType::Int: + out.device()->write("i", 1); + out << (int32_t)list.length(); + out << (int32_t)0; + out << (int32_t)0; + for (auto& innerProp : list) { + out << prop.toInt(); + } + break; + + case QMetaType::Bool: + out.device()->write("b", 1); + out << (int32_t)list.length(); + out << (int32_t)0; + out << (int32_t)0; + for (auto& innerProp : list) { + out << prop.toBool(); + } + break; + } + } + break; + + default: + { + if (prop.canConvert>()) { + auto list = prop.value>(); + out.device()->write("f", 1); + out << (int32_t)list.length(); + out << (int32_t)0; + out << (int32_t)0; + for (auto& value : list) { + out << value; + } + } else if (prop.canConvert>()) { + auto list = prop.value>(); + out.device()->write("d", 1); + out << (int32_t)list.length(); + out << (int32_t)0; + out << (int32_t)0; + for (auto& value : list) { + out << value; + } + } else if (prop.canConvert>()) { + auto list = prop.value>(); + out.device()->write("l", 1); + out << (int32_t)list.length(); + out << (int32_t)0; + out << (int32_t)0; + for (auto& value : list) { + out << value; + } + } else if (prop.canConvert>()) { + auto list = prop.value>(); + out.device()->write("i", 1); + out << (int32_t)list.length(); + out << (int32_t)0; + out << (int32_t)0; + for (auto& value : list) { + out << value; + } + } else if (prop.canConvert>()) { + auto list = prop.value>(); + out.device()->write("b", 1); + out << (int32_t)list.length(); + out << (int32_t)0; + out << (int32_t)0; + for (auto& value : list) { + out << value; + } + } else { + qDebug() << "Unsupported property type in FBXWriter::encodeNode: " << type << prop; + } + } + + } +} diff --git a/libraries/fbx/src/FBXWriter.h b/libraries/fbx/src/FBXWriter.h new file mode 100644 index 0000000000..fa33983345 --- /dev/null +++ b/libraries/fbx/src/FBXWriter.h @@ -0,0 +1,28 @@ +// +// FBXWriter.h +// libraries/fbx/src +// +// Created by Ryan Huffman on 9/5/17. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_FBXWriter_h +#define hifi_FBXWriter_h + +#include "FBX.h" + +#include +#include + +class FBXWriter { +public: + static QByteArray encodeFBX(const FBXNode& root); + + static void encodeNode(QDataStream& out, const FBXNode& node); + static void encodeFBXProperty(QDataStream& out, const QVariant& property); +}; + +#endif // hifi_FBXWriter_h