From 1e9fce2a612bd4b031484e6871e74a0226b5cc5c Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Tue, 25 Aug 2015 22:12:51 -0700 Subject: [PATCH] Drafting the materials for FBXReader --- libraries/fbx/src/FBXReader.cpp | 617 ++++++------------- libraries/fbx/src/FBXReader.h | 121 +++- libraries/fbx/src/FBXReader_Node.cpp | 342 ++++++++++ libraries/fbx/src/OBJReader.cpp | 19 +- libraries/model/src/model/Asset.h | 68 +- libraries/model/src/model/Material.h | 9 + libraries/model/src/model/Material.slh | 29 +- libraries/networking/src/ResourceCache.cpp | 3 +- libraries/render-utils/src/GeometryCache.cpp | 9 +- libraries/render-utils/src/GeometryCache.h | 18 + libraries/render-utils/src/Model.cpp | 10 + libraries/render-utils/src/model.slf | 4 +- libraries/render-utils/src/model.slv | 34 +- 13 files changed, 816 insertions(+), 467 deletions(-) create mode 100644 libraries/fbx/src/FBXReader_Node.cpp diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index f0d13f8792..5d87b5f316 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -38,7 +38,7 @@ //#define DEBUG_FBXREADER using namespace std; - +/* struct TextureParam { glm::vec2 UVTranslation; glm::vec2 UVScaling; @@ -80,22 +80,27 @@ struct TextureParam { {} }; - +*/ bool FBXMesh::hasSpecularTexture() const { - foreach (const FBXMeshPart& part, parts) { +// TODO fix that in model.cpp / payload + /* foreach (const FBXMeshPart& part, parts) { if (!part.specularTexture.filename.isEmpty()) { return true; } } + */ return false; } bool FBXMesh::hasEmissiveTexture() const { + // TODO Fix that in model cpp / payload + /* foreach (const FBXMeshPart& part, parts) { if (!part.emissiveTexture.filename.isEmpty()) { return true; } } + */ return false; } @@ -184,325 +189,7 @@ static int fbxGeometryMetaTypeId = qRegisterMetaType(); static int fbxAnimationFrameMetaTypeId = qRegisterMetaType(); static int fbxAnimationFrameVectorMetaTypeId = qRegisterMetaType >(); -template int streamSize() { - return sizeof(T); -} -template int streamSize() { - return 1; -} - -template QVariant readBinaryArray(QDataStream& in, int& position) { - quint32 arrayLength; - quint32 encoding; - quint32 compressedLength; - - in >> arrayLength; - in >> encoding; - in >> compressedLength; - position += sizeof(quint32) * 3; - - QVector values; - const unsigned int DEFLATE_ENCODING = 1; - if (encoding == DEFLATE_ENCODING) { - // preface encoded data with uncompressed length - QByteArray compressed(sizeof(quint32) + compressedLength, 0); - *((quint32*)compressed.data()) = qToBigEndian(arrayLength * sizeof(T)); - in.readRawData(compressed.data() + sizeof(quint32), compressedLength); - position += compressedLength; - QByteArray uncompressed = qUncompress(compressed); - QDataStream uncompressedIn(uncompressed); - uncompressedIn.setByteOrder(QDataStream::LittleEndian); - uncompressedIn.setVersion(QDataStream::Qt_4_5); // for single/double precision switch - for (quint32 i = 0; i < arrayLength; i++) { - T value; - uncompressedIn >> value; - values.append(value); - } - } else { - for (quint32 i = 0; i < arrayLength; i++) { - T value; - in >> value; - position += streamSize(); - values.append(value); - } - } - return QVariant::fromValue(values); -} - -QVariant parseBinaryFBXProperty(QDataStream& in, int& position) { - char ch; - in.device()->getChar(&ch); - position++; - switch (ch) { - case 'Y': { - qint16 value; - in >> value; - position += sizeof(qint16); - return QVariant::fromValue(value); - } - case 'C': { - bool value; - in >> value; - position++; - return QVariant::fromValue(value); - } - case 'I': { - qint32 value; - in >> value; - position += sizeof(qint32); - return QVariant::fromValue(value); - } - case 'F': { - float value; - in >> value; - position += sizeof(float); - return QVariant::fromValue(value); - } - case 'D': { - double value; - in >> value; - position += sizeof(double); - return QVariant::fromValue(value); - } - case 'L': { - qint64 value; - in >> value; - position += sizeof(qint64); - return QVariant::fromValue(value); - } - case 'f': { - return readBinaryArray(in, position); - } - case 'd': { - return readBinaryArray(in, position); - } - case 'l': { - return readBinaryArray(in, position); - } - case 'i': { - return readBinaryArray(in, position); - } - case 'b': { - return readBinaryArray(in, position); - } - case 'S': - case 'R': { - quint32 length; - in >> length; - position += sizeof(quint32) + length; - return QVariant::fromValue(in.device()->read(length)); - } - default: - throw QString("Unknown property type: ") + ch; - } -} - -FBXNode parseBinaryFBXNode(QDataStream& in, int& position) { - qint32 endOffset; - quint32 propertyCount; - quint32 propertyListLength; - quint8 nameLength; - - in >> endOffset; - in >> propertyCount; - in >> propertyListLength; - in >> nameLength; - position += sizeof(quint32) * 3 + sizeof(quint8); - - FBXNode node; - const int MIN_VALID_OFFSET = 40; - if (endOffset < MIN_VALID_OFFSET || nameLength == 0) { - // use a null name to indicate a null node - return node; - } - node.name = in.device()->read(nameLength); - position += nameLength; - - for (quint32 i = 0; i < propertyCount; i++) { - node.properties.append(parseBinaryFBXProperty(in, position)); - } - - while (endOffset > position) { - FBXNode child = parseBinaryFBXNode(in, position); - if (child.name.isNull()) { - return node; - - } else { - node.children.append(child); - } - } - - return node; -} - -class Tokenizer { -public: - - Tokenizer(QIODevice* device) : _device(device), _pushedBackToken(-1) { } - - enum SpecialToken { - NO_TOKEN = -1, - NO_PUSHBACKED_TOKEN = -1, - DATUM_TOKEN = 0x100 - }; - - int nextToken(); - const QByteArray& getDatum() const { return _datum; } - - void pushBackToken(int token) { _pushedBackToken = token; } - void ungetChar(char ch) { _device->ungetChar(ch); } - -private: - - QIODevice* _device; - QByteArray _datum; - int _pushedBackToken; -}; - -int Tokenizer::nextToken() { - if (_pushedBackToken != NO_PUSHBACKED_TOKEN) { - int token = _pushedBackToken; - _pushedBackToken = NO_PUSHBACKED_TOKEN; - return token; - } - - char ch; - while (_device->getChar(&ch)) { - if (QChar(ch).isSpace()) { - continue; // skip whitespace - } - switch (ch) { - case ';': - _device->readLine(); // skip the comment - break; - - case ':': - case '{': - case '}': - case ',': - return ch; // special punctuation - - case '\"': - _datum = ""; - while (_device->getChar(&ch)) { - if (ch == '\"') { // end on closing quote - break; - } - if (ch == '\\') { // handle escaped quotes - if (_device->getChar(&ch) && ch != '\"') { - _datum.append('\\'); - } - } - _datum.append(ch); - } - return DATUM_TOKEN; - - default: - _datum = ""; - _datum.append(ch); - while (_device->getChar(&ch)) { - if (QChar(ch).isSpace() || ch == ';' || ch == ':' || ch == '{' || ch == '}' || ch == ',' || ch == '\"') { - ungetChar(ch); // read until we encounter a special character, then replace it - break; - } - _datum.append(ch); - } - return DATUM_TOKEN; - } - } - return NO_TOKEN; -} - -FBXNode parseTextFBXNode(Tokenizer& tokenizer) { - FBXNode node; - - if (tokenizer.nextToken() != Tokenizer::DATUM_TOKEN) { - return node; - } - node.name = tokenizer.getDatum(); - - if (tokenizer.nextToken() != ':') { - return node; - } - - int token; - bool expectingDatum = true; - while ((token = tokenizer.nextToken()) != Tokenizer::NO_TOKEN) { - if (token == '{') { - for (FBXNode child = parseTextFBXNode(tokenizer); !child.name.isNull(); child = parseTextFBXNode(tokenizer)) { - node.children.append(child); - } - return node; - } - if (token == ',') { - expectingDatum = true; - - } else if (token == Tokenizer::DATUM_TOKEN && expectingDatum) { - QByteArray datum = tokenizer.getDatum(); - if ((token = tokenizer.nextToken()) == ':') { - tokenizer.ungetChar(':'); - tokenizer.pushBackToken(Tokenizer::DATUM_TOKEN); - return node; - - } else { - tokenizer.pushBackToken(token); - node.properties.append(datum); - expectingDatum = false; - } - } else { - tokenizer.pushBackToken(token); - return node; - } - } - - return node; -} - -FBXNode parseFBX(QIODevice* device) { - // verify the prolog - const QByteArray BINARY_PROLOG = "Kaydara FBX Binary "; - if (device->peek(BINARY_PROLOG.size()) != BINARY_PROLOG) { - // parse as a text file - FBXNode top; - Tokenizer tokenizer(device); - while (device->bytesAvailable()) { - FBXNode next = parseTextFBXNode(tokenizer); - if (next.name.isNull()) { - return top; - - } else { - top.children.append(next); - } - } - return top; - } - QDataStream in(device); - in.setByteOrder(QDataStream::LittleEndian); - in.setVersion(QDataStream::Qt_4_5); // for single/double precision switch - - // see http://code.blender.org/index.php/2013/08/fbx-binary-file-format-specification/ for an explanation - // of the FBX binary format - - // skip the rest of the header - const int HEADER_SIZE = 27; - in.skipRawData(HEADER_SIZE); - int position = HEADER_SIZE; - - // parse the top-level node - FBXNode top; - while (device->bytesAvailable()) { - FBXNode next = parseBinaryFBXNode(in, position); - if (next.name.isNull()) { - return top; - - } else { - top.children.append(next); - } - } - - return top; -} QVector createVec4Vector(const QVector& doubleVector) { QVector values; @@ -693,7 +380,7 @@ public: glm::vec3 rotationMax; // radians }; -glm::mat4 getGlobalTransform(const QMultiHash& parentMap, +glm::mat4 getGlobalTransform(const QMultiHash& _connectionParentMap, const QHash& models, QString nodeID, bool mixamoHack) { glm::mat4 globalTransform; while (!nodeID.isNull()) { @@ -704,7 +391,7 @@ glm::mat4 getGlobalTransform(const QMultiHash& parentMap, // 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); + QList parentIDs = _connectionParentMap.values(nodeID); nodeID = QString(); foreach (const QString& parentID, parentIDs) { if (models.contains(parentID)) { @@ -738,17 +425,6 @@ void printNode(const FBXNode& node, int indentLevel) { } } -class Material { -public: - glm::vec3 diffuse; - glm::vec3 specular; - glm::vec3 emissive; - float shininess; - float opacity; - QString id; - model::MaterialPointer _material; -}; - class Cluster { public: QVector indices; @@ -756,19 +432,19 @@ public: glm::mat4 transformLink; }; -void appendModelIDs(const QString& parentID, const QMultiHash& childMap, +void appendModelIDs(const QString& parentID, const QMultiHash& _connectionChildMap, QHash& models, QSet& remainingModels, QVector& modelIDs) { if (remainingModels.contains(parentID)) { modelIDs.append(parentID); remainingModels.remove(parentID); } int parentIndex = modelIDs.size() - 1; - foreach (const QString& childID, childMap.values(parentID)) { + foreach (const QString& childID, _connectionChildMap.values(parentID)) { if (remainingModels.contains(childID)) { FBXModel& model = models[childID]; if (model.parentIndex == -1) { model.parentIndex = parentIndex; - appendModelIDs(childID, childMap, models, remainingModels, modelIDs); + appendModelIDs(childID, _connectionChildMap, models, remainingModels, modelIDs); } } } @@ -1235,11 +911,11 @@ void addBlendshapes(const ExtractedBlendshape& extracted, const QList& parentMap, +QString getTopModelID(const QMultiHash& _connectionParentMap, const QHash& models, const QString& modelID) { QString topID = modelID; forever { - foreach (const QString& parentID, parentMap.values(topID)) { + foreach (const QString& parentID, _connectionParentMap.values(topID)) { if (models.contains(parentID)) { topID = parentID; goto outerContinue; @@ -1303,10 +979,10 @@ FBXTexture getTexture(const QString& textureID, return texture; } -bool checkMaterialsHaveTextures(const QHash& materials, - const QHash& textureFilenames, const QMultiHash& childMap) { +bool checkMaterialsHaveTextures(const QHash& materials, + const QHash& textureFilenames, const QMultiHash& _connectionChildMap) { foreach (const QString& materialID, materials.keys()) { - foreach (const QString& childID, childMap.values(materialID)) { + foreach (const QString& childID, _connectionChildMap.values(materialID)) { if (textureFilenames.contains(childID)) { return true; } @@ -1530,29 +1206,21 @@ QByteArray fileOnUrl(const QByteArray& filenameString, const QString& url) { return filename; } -FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) { +FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QString& url) { + const FBXNode& node = _fbxNode; QHash meshes; QHash modelIDsToNames; QHash meshIDsToMeshIndices; QHash ooChildToParent; QVector blendshapes; - QMultiHash parentMap; - QMultiHash childMap; + QHash models; QHash clusters; QHash animationCurves; - QHash textureNames; - QHash textureFilenames; - QHash textureParams; - QHash textureContent; - QHash materials; + QHash typeFlags; - QHash diffuseTextures; - QHash bumpTextures; - QHash specularTextures; - QHash emissiveTextures; - QHash ambientTextures; + QHash localRotations; QHash xComponents; QHash yComponents; @@ -1580,8 +1248,7 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping QString jointLeftToeID; QString jointRightToeID; - float lightmapOffset = 0.0f; - + QVector humanIKJointNames; for (int i = 0;; i++) { QByteArray jointName = HUMANIK_JOINTS[i]; @@ -1618,6 +1285,8 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping FBXGeometry* geometryPtr = new FBXGeometry; FBXGeometry& geometry = *geometryPtr; + geometry._asset.reset(new model::Asset()); + float unitScaleFactor = 1.0f; glm::vec3 ambientColor; QString hifiGlobalNodeID; @@ -1942,8 +1611,8 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping textureContent.insert(filename, content); } } else if (object.name == "Material") { - Material material = { glm::vec3(1.0f, 1.0f, 1.0f), glm::vec3(1.0f, 1.0f, 1.0f), glm::vec3(), 96.0f, 1.0f, - QString(""), model::MaterialPointer(NULL)}; + FBXMaterial material = { glm::vec3(1.0f, 1.0f, 1.0f), glm::vec3(1.0f, 1.0f, 1.0f), glm::vec3(), glm::vec2(0.f, 1.0f), 96.0f, 1.0f, + QString(""), model::MaterialTable::INVALID_ID}; foreach (const FBXNode& subobject, object.children) { bool properties = false; QByteArray propertyName; @@ -1962,13 +1631,13 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping foreach (const FBXNode& property, subobject.children) { if (property.name == propertyName) { if (property.properties.at(0) == "DiffuseColor") { - material.diffuse = getVec3(property.properties, index); + material.diffuseColor = getVec3(property.properties, index); } else if (property.properties.at(0) == "SpecularColor") { - material.specular = getVec3(property.properties, index); + material.specularColor = getVec3(property.properties, index); } else if (property.properties.at(0) == "Emissive") { - material.emissive = getVec3(property.properties, index); + material.emissiveColor = getVec3(property.properties, index); } else if (property.properties.at(0) == "Shininess") { material.shininess = property.properties.at(index).value(); @@ -1999,25 +1668,9 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping } #endif } - material.id = getID(object.properties); + material.materialID = getID(object.properties); - material._material = make_shared(); - material._material->setEmissive(material.emissive); - if (glm::all(glm::equal(material.diffuse, glm::vec3(0.0f)))) { - material._material->setDiffuse(material.diffuse); - } else { - material._material->setDiffuse(material.diffuse); - } - material._material->setMetallic(glm::length(material.specular)); - material._material->setGloss(material.shininess); - - if (material.opacity <= 0.0f) { - material._material->setOpacity(1.0f); - } else { - material._material->setOpacity(material.opacity); - } - - materials.insert(material.id, material); + _fbxMaterials.insert(material.materialID, material); } else if (object.name == "NodeAttribute") { #if defined(DEBUG_FBXREADER) @@ -2106,11 +1759,11 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping if (!hifiGlobalNodeID.isEmpty() && (parentID == hifiGlobalNodeID)) { std::map< QString, FBXLight >::iterator lit = lights.find(childID); if (lit != lights.end()) { - lightmapLevel = (*lit).second.intensity; - if (lightmapLevel <= 0.0f) { - loadLightmaps = false; + _lightmapLevel = (*lit).second.intensity; + if (_lightmapLevel <= 0.0f) { + _loadLightmaps = false; } - lightmapOffset = glm::clamp((*lit).second.color.x, 0.f, 1.f); + _lightmapOffset = glm::clamp((*lit).second.color.x, 0.f, 1.f); } } } @@ -2144,18 +1797,18 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping } else if (type.contains("shininess")) { counter++; - } else if (loadLightmaps && type.contains("emissive")) { + } else if (_loadLightmaps && type.contains("emissive")) { emissiveTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); - } else if (loadLightmaps && type.contains("ambient")) { + } else if (_loadLightmaps && type.contains("ambient")) { ambientTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); } else { QString typenam = type.data(); counter++; } } - parentMap.insert(getID(connection.properties, 1), getID(connection.properties, 2)); - childMap.insert(getID(connection.properties, 2), getID(connection.properties, 1)); + _connectionParentMap.insert(getID(connection.properties, 1), getID(connection.properties, 2)); + _connectionChildMap.insert(getID(connection.properties, 2), getID(connection.properties, 1)); } } } @@ -2184,15 +1837,15 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping if (!lights.empty()) { if (hifiGlobalNodeID.isEmpty()) { std::map< QString, FBXLight >::iterator l = lights.begin(); - lightmapLevel = (*l).second.intensity; + _lightmapLevel = (*l).second.intensity; } } // assign the blendshapes to their corresponding meshes foreach (const ExtractedBlendshape& extracted, blendshapes) { - QString blendshapeChannelID = parentMap.value(extracted.id); - QString blendshapeID = parentMap.value(blendshapeChannelID); - QString meshID = parentMap.value(blendshapeID); + QString blendshapeChannelID = _connectionParentMap.value(extracted.id); + QString blendshapeID = _connectionParentMap.value(blendshapeChannelID); + QString meshID = _connectionParentMap.value(blendshapeID); addBlendshapes(extracted, blendshapeChannelIndices.values(blendshapeChannelID), meshes[meshID]); } @@ -2209,23 +1862,23 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping QSet remainingModels; for (QHash::const_iterator model = models.constBegin(); model != models.constEnd(); model++) { // models with clusters must be parented to the cluster top - foreach (const QString& deformerID, childMap.values(model.key())) { - foreach (const QString& clusterID, childMap.values(deformerID)) { + foreach (const QString& deformerID, _connectionChildMap.values(model.key())) { + foreach (const QString& clusterID, _connectionChildMap.values(deformerID)) { if (!clusters.contains(clusterID)) { continue; } - QString topID = getTopModelID(parentMap, models, childMap.value(clusterID)); - childMap.remove(parentMap.take(model.key()), model.key()); - parentMap.insert(model.key(), topID); + QString topID = getTopModelID(_connectionParentMap, models, _connectionChildMap.value(clusterID)); + _connectionChildMap.remove(_connectionParentMap.take(model.key()), model.key()); + _connectionParentMap.insert(model.key(), topID); goto outerBreak; } } outerBreak: // make sure the parent is in the child map - QString parent = parentMap.value(model.key()); - if (!childMap.contains(parent, model.key())) { - childMap.insert(parent, model.key()); + QString parent = _connectionParentMap.value(model.key()); + if (!_connectionChildMap.contains(parent, model.key())) { + _connectionChildMap.insert(parent, model.key()); } remainingModels.insert(model.key()); } @@ -2236,8 +1889,8 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping first = id; } } - QString topID = getTopModelID(parentMap, models, first); - appendModelIDs(parentMap.value(topID), childMap, models, remainingModels, modelIDs); + QString topID = getTopModelID(_connectionParentMap, models, first); + appendModelIDs(_connectionParentMap.value(topID), _connectionChildMap, models, remainingModels, modelIDs); } // figure the number of animation frames from the curves @@ -2298,7 +1951,7 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping joint.inverseBindRotation = joint.inverseDefaultRotation; joint.name = model.name; - foreach (const QString& childID, childMap.values(modelID)) { + foreach (const QString& childID, _connectionChildMap.values(modelID)) { QString type = typeFlags.value(childID); if (!type.isEmpty()) { geometry.hasSkeletonJoints |= (joint.isSkeletonJoint = type.toLower().contains("Skeleton")); @@ -2350,8 +2003,11 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping geometry.bindExtents.reset(); geometry.meshExtents.reset(); + // Create the Material Library + consolidateFBXMaterials(); + // see if any materials have texture children - bool materialsHaveTextures = checkMaterialsHaveTextures(materials, textureFilenames, childMap); + bool materialsHaveTextures = checkMaterialsHaveTextures(_fbxMaterials, textureFilenames, _connectionChildMap); for (QHash::iterator it = meshes.begin(); it != meshes.end(); it++) { ExtractedMesh& extracted = it.value(); @@ -2359,8 +2015,8 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping extracted.mesh.meshExtents.reset(); // accumulate local transforms - QString modelID = models.contains(it.key()) ? it.key() : parentMap.value(it.key()); - glm::mat4 modelTransform = getGlobalTransform(parentMap, models, modelID, geometry.applicationName == "mixamo.com"); + QString modelID = models.contains(it.key()) ? it.key() : _connectionParentMap.value(it.key()); + glm::mat4 modelTransform = getGlobalTransform(_connectionParentMap, models, modelID, geometry.applicationName == "mixamo.com"); // compute the mesh extents from the transformed vertices foreach (const glm::vec3& vertex, extracted.mesh.vertices) { @@ -2374,14 +2030,19 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping } // look for textures, material properties + // allocate the Part material library int materialIndex = 0; int textureIndex = 0; bool generateTangents = false; - QList children = childMap.values(modelID); + QList children = _connectionChildMap.values(modelID); for (int i = children.size() - 1; i >= 0; i--) { + const QString& childID = children.at(i); - if (materials.contains(childID)) { - Material material = materials.value(childID); + if (_fbxMaterials.contains(childID)) { + // the pure material associated with this part + FBXMaterial material = _fbxMaterials.value(childID); + + bool detectDifferentUVs = false; FBXTexture diffuseTexture; QString diffuseTextureID = diffuseTextures.value(childID); @@ -2389,7 +2050,7 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping diffuseTexture = getTexture(diffuseTextureID, textureNames, textureFilenames, textureContent, textureParams); // FBX files generated by 3DSMax have an intermediate texture parent, apparently - foreach (const QString& childTextureID, childMap.values(diffuseTextureID)) { + foreach (const QString& childTextureID, _connectionChildMap.values(diffuseTextureID)) { if (textureFilenames.contains(childTextureID)) { diffuseTexture = getTexture(diffuseTextureID, textureNames, textureFilenames, textureContent, textureParams); } @@ -2420,12 +2081,12 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping FBXTexture emissiveTexture; glm::vec2 emissiveParams(0.f, 1.f); - emissiveParams.x = lightmapOffset; - emissiveParams.y = lightmapLevel; + emissiveParams.x = _lightmapOffset; + emissiveParams.y = _lightmapLevel; QString emissiveTextureID = emissiveTextures.value(childID); QString ambientTextureID = ambientTextures.value(childID); - if (loadLightmaps && (!emissiveTextureID.isNull() || !ambientTextureID.isNull())) { + if (_loadLightmaps && (!emissiveTextureID.isNull() || !ambientTextureID.isNull())) { if (!emissiveTextureID.isNull()) { emissiveTexture = getTexture(emissiveTextureID, textureNames, textureFilenames, textureContent, textureParams); @@ -2447,10 +2108,10 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping if (extracted.partMaterialTextures.at(j).first == materialIndex) { FBXMeshPart& part = extracted.mesh.parts[j]; - part._material = material._material; - part.diffuseColor = material.diffuse; - part.specularColor = material.specular; - part.emissiveColor = material.emissive; + /* part._material = material._material; + part.diffuseColor = material.diffuseColor; + part.specularColor = material.specularColor; + part.emissiveColor = material.emissiveColor; part.shininess = material.shininess; part.opacity = material.opacity; if (!diffuseTexture.filename.isNull()) { @@ -2466,8 +2127,8 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping part.emissiveTexture = emissiveTexture; } part.emissiveParams = emissiveParams; - - part.materialID = material.id; + */ + part.materialID = material.materialID; } } materialIndex++; @@ -2477,7 +2138,7 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping for (int j = 0; j < extracted.partMaterialTextures.size(); j++) { int partTexture = extracted.partMaterialTextures.at(j).second; if (partTexture == textureIndex && !(partTexture == 0 && materialsHaveTextures)) { - extracted.mesh.parts[j].diffuseTexture = texture; + // extracted.mesh.parts[j].diffuseTexture = texture; } } textureIndex++; @@ -2509,8 +2170,8 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping // find the clusters with which the mesh is associated QVector clusterIDs; - foreach (const QString& childID, childMap.values(it.key())) { - foreach (const QString& clusterID, childMap.values(childID)) { + foreach (const QString& childID, _connectionChildMap.values(it.key())) { + foreach (const QString& clusterID, _connectionChildMap.values(childID)) { if (!clusters.contains(clusterID)) { continue; } @@ -2520,7 +2181,7 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping // see http://stackoverflow.com/questions/13566608/loading-skinning-information-from-fbx for a discussion // of skinning information in FBX - QString jointID = childMap.value(clusterID); + QString jointID = _connectionChildMap.value(clusterID); fbxCluster.jointIndex = modelIDs.indexOf(jointID); if (fbxCluster.jointIndex == -1) { qCDebug(modelformat) << "Joint not in model list: " << jointID; @@ -2773,5 +2434,113 @@ FBXGeometry* readFBX(const QByteArray& model, const QVariantHash& mapping, const } FBXGeometry* readFBX(QIODevice* device, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) { - return extractFBXGeometry(parseFBX(device), mapping, url, loadLightmaps, lightmapLevel); + FBXReader reader; + reader._fbxNode = FBXReader::parseFBX(device); + reader._loadLightmaps = loadLightmaps; + reader._lightmapLevel = lightmapLevel; + + return reader.extractFBXGeometry(mapping, url); +} + + +bool FBXMaterial::needTangentSpace() const { + return !normalTexture.isNull(); +} + + +void FBXReader::consolidateFBXMaterials() { + + // foreach (const QString& materialID, materials) { + for (QHash::iterator it = _fbxMaterials.begin(); it != _fbxMaterials.end(); it++) { + FBXMaterial& material = (*it); + // the pure material associated with this part + bool detectDifferentUVs = false; + FBXTexture diffuseTexture; + QString diffuseTextureID = diffuseTextures.value(material.materialID); + if (!diffuseTextureID.isNull()) { + diffuseTexture = getTexture(diffuseTextureID, textureNames, textureFilenames, textureContent, textureParams); + + // FBX files generated by 3DSMax have an intermediate texture parent, apparently + foreach (const QString& childTextureID, _connectionChildMap.values(diffuseTextureID)) { + if (textureFilenames.contains(childTextureID)) { + diffuseTexture = getTexture(diffuseTextureID, textureNames, textureFilenames, textureContent, textureParams); + } + } + + // TODO associate this per part + //diffuseTexture.texcoordSet = matchTextureUVSetToAttributeChannel(diffuseTexture.texcoordSetName, extracted.texcoordSetMap); + + material.diffuseTexture = diffuseTexture; + + detectDifferentUVs = (diffuseTexture.texcoordSet != 0) || (!diffuseTexture.transform.isIdentity()); + } + + FBXTexture normalTexture; + QString bumpTextureID = bumpTextures.value(material.materialID); + if (!bumpTextureID.isNull()) { + normalTexture = getTexture(bumpTextureID, textureNames, textureFilenames, textureContent, textureParams); + + // TODO Need to generate tangent space at association per part + //generateTangents = true; + + // TODO at per part association time + // normalTexture.texcoordSet = matchTextureUVSetToAttributeChannel(normalTexture.texcoordSetName, extracted.texcoordSetMap); + + material.normalTexture = normalTexture; + + detectDifferentUVs |= (normalTexture.texcoordSet != 0) || (!normalTexture.transform.isIdentity()); + } + + FBXTexture specularTexture; + QString specularTextureID = specularTextures.value(material.materialID); + if (!specularTextureID.isNull()) { + specularTexture = getTexture(specularTextureID, textureNames, textureFilenames, textureContent, textureParams); + // TODO at per part association time + // specularTexture.texcoordSet = matchTextureUVSetToAttributeChannel(specularTexture.texcoordSetName, extracted.texcoordSetMap); + detectDifferentUVs |= (specularTexture.texcoordSet != 0) || (!specularTexture.transform.isIdentity()); + } + + FBXTexture emissiveTexture; + glm::vec2 emissiveParams(0.f, 1.f); + emissiveParams.x = _lightmapOffset; + emissiveParams.y = _lightmapLevel; + + QString emissiveTextureID = emissiveTextures.value(material.materialID); + QString ambientTextureID = ambientTextures.value(material.materialID); + if (_loadLightmaps && (!emissiveTextureID.isNull() || !ambientTextureID.isNull())) { + + if (!emissiveTextureID.isNull()) { + emissiveTexture = getTexture(emissiveTextureID, textureNames, textureFilenames, textureContent, textureParams); + emissiveParams.y = 4.0f; + } else if (!ambientTextureID.isNull()) { + emissiveTexture = getTexture(ambientTextureID, textureNames, textureFilenames, textureContent, textureParams); + } + + // TODO : do this at per part association + //emissiveTexture.texcoordSet = matchTextureUVSetToAttributeChannel(emissiveTexture.texcoordSetName, extracted.texcoordSetMap); + + material.emissiveParams = emissiveParams; + material.emissiveTexture = emissiveTexture; + + + detectDifferentUVs |= (emissiveTexture.texcoordSet != 0) || (!emissiveTexture.transform.isIdentity()); + } + + // Finally create the true material representation + material._material = make_shared(); + material._material->setEmissive(material.emissiveColor); + if (glm::all(glm::equal(material.diffuseColor, glm::vec3(0.0f)))) { + material._material->setDiffuse(material.diffuseColor); + } else { + material._material->setDiffuse(material.diffuseColor); + } + material._material->setMetallic(glm::length(material.specularColor)); + material._material->setGloss(material.shininess); + + if (material.opacity <= 0.0f) { + material._material->setOpacity(1.0f); + } else { + material._material->setOpacity(material.opacity); + } + } } diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h index 471a9c1777..26ded3e9f5 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -28,6 +28,7 @@ #include #include +#include class QIODevice; class FBXNode; @@ -89,6 +90,15 @@ public: glm::mat4 inverseBindMatrix; }; + +// The true texture image which can be used for different textures +class FBXTextureImage { +public: + QString name; + QByteArray filename; + QByteArray content; +}; + /// A texture map in an FBX document. class FBXTexture { public: @@ -99,6 +109,8 @@ public: Transform transform; int texcoordSet; QString texcoordSetName; + + bool isNull() const { return name.isEmpty() && filename.isEmpty() && content.isEmpty(); } }; /// A single part of a mesh (with the same material). @@ -109,7 +121,7 @@ public: QVector triangleIndices; // original indices from the FBX mesh mutable gpu::BufferPointer quadsAsTrianglesIndicesBuffer; - glm::vec3 diffuseColor; + /* glm::vec3 diffuseColor; glm::vec3 specularColor; glm::vec3 emissiveColor; glm::vec2 emissiveParams; @@ -120,15 +132,38 @@ public: FBXTexture normalTexture; FBXTexture specularTexture; FBXTexture emissiveTexture; - +*/ QString materialID; - model::MaterialPointer _material; + // model::MaterialPointer _material; mutable bool trianglesForQuadsAvailable = false; mutable int trianglesForQuadsIndicesCount = 0; gpu::BufferPointer getTrianglesForQuads() const; }; +class FBXMaterial { +public: + glm::vec3 diffuseColor; + glm::vec3 specularColor; + glm::vec3 emissiveColor; + glm::vec2 emissiveParams; + float shininess; + float opacity; + + QString materialID; + model::MaterialTable::ID _modelMaterialID; + model::MaterialPointer _material; + + FBXTexture diffuseTexture; + FBXTexture opacityTexture; + FBXTexture normalTexture; + FBXTexture specularTexture; + FBXTexture emissiveTexture; + + bool needTangentSpace() const; + +}; + /// A single mesh (with optional blendshapes) extracted from an FBX document. class FBXMesh { public: @@ -220,7 +255,7 @@ public: bool hasSkeletonJoints; QVector meshes; - + glm::mat4 offset; int leftEyeJointIndex = -1; @@ -266,6 +301,8 @@ public: QString getModelNameOfMesh(int meshIndex) const; QList blendshapeChannelNames; + + model::AssetPointer _asset; }; Q_DECLARE_METATYPE(FBXGeometry) @@ -278,4 +315,80 @@ FBXGeometry* readFBX(const QByteArray& model, const QVariantHash& mapping, const /// \exception QString if an error occurs in parsing FBXGeometry* readFBX(QIODevice* device, const QVariantHash& mapping, const QString& url = "", bool loadLightmaps = true, float lightmapLevel = 1.0f); +struct TextureParam { + glm::vec2 UVTranslation; + glm::vec2 UVScaling; + glm::vec4 cropping; + QString UVSet; + + glm::vec3 translation; + glm::vec3 rotation; + glm::vec3 scaling; + uint8_t alphaSource; + uint8_t currentTextureBlendMode; + bool useMaterial; + + template + bool assign(T& ref, const T& v) { + if (ref == v) { + return false; + } else { + ref = v; + isDefault = false; + return true; + } + } + + bool isDefault; + + TextureParam() : + UVTranslation(0.0f), + UVScaling(1.0f), + cropping(0.0f), + UVSet("map1"), + translation(0.0f), + rotation(0.0f), + scaling(1.0f), + alphaSource(0), + currentTextureBlendMode(0), + useMaterial(true), + isDefault(true) + {} +}; + +class FBXReader { +public: + FBXGeometry* _fbxGeometry; + + FBXNode _fbxNode; + static FBXNode parseFBX(QIODevice* device); + + FBXGeometry* extractFBXGeometry(const QVariantHash& mapping, const QString& url); + + QHash _textureImages; + + QHash textureNames; + QHash textureFilenames; + QHash textureContent; + QHash textureParams; + + + QHash diffuseTextures; + QHash bumpTextures; + QHash specularTextures; + QHash emissiveTextures; + QHash ambientTextures; + + QHash _fbxMaterials; + + void consolidateFBXMaterials(); + + bool _loadLightmaps = true; + float _lightmapOffset = 0.0f; + float _lightmapLevel; + + QMultiHash _connectionParentMap; + QMultiHash _connectionChildMap; +}; + #endif // hifi_FBXReader_h diff --git a/libraries/fbx/src/FBXReader_Node.cpp b/libraries/fbx/src/FBXReader_Node.cpp new file mode 100644 index 0000000000..32c3595075 --- /dev/null +++ b/libraries/fbx/src/FBXReader_Node.cpp @@ -0,0 +1,342 @@ +// +// FBXReader.cpp +// interface/src/renderer +// +// Created by Andrzej Kapolka on 9/18/13. +// Copyright 2013 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include "FBXReader.h" + +template int streamSize() { + return sizeof(T); +} + +template int streamSize() { + return 1; +} + +template QVariant readBinaryArray(QDataStream& in, int& position) { + quint32 arrayLength; + quint32 encoding; + quint32 compressedLength; + + in >> arrayLength; + in >> encoding; + in >> compressedLength; + position += sizeof(quint32) * 3; + + QVector values; + const unsigned int DEFLATE_ENCODING = 1; + if (encoding == DEFLATE_ENCODING) { + // preface encoded data with uncompressed length + QByteArray compressed(sizeof(quint32) + compressedLength, 0); + *((quint32*)compressed.data()) = qToBigEndian(arrayLength * sizeof(T)); + in.readRawData(compressed.data() + sizeof(quint32), compressedLength); + position += compressedLength; + QByteArray uncompressed = qUncompress(compressed); + QDataStream uncompressedIn(uncompressed); + uncompressedIn.setByteOrder(QDataStream::LittleEndian); + uncompressedIn.setVersion(QDataStream::Qt_4_5); // for single/double precision switch + for (quint32 i = 0; i < arrayLength; i++) { + T value; + uncompressedIn >> value; + values.append(value); + } + } else { + for (quint32 i = 0; i < arrayLength; i++) { + T value; + in >> value; + position += streamSize(); + values.append(value); + } + } + return QVariant::fromValue(values); +} + +QVariant parseBinaryFBXProperty(QDataStream& in, int& position) { + char ch; + in.device()->getChar(&ch); + position++; + switch (ch) { + case 'Y': { + qint16 value; + in >> value; + position += sizeof(qint16); + return QVariant::fromValue(value); + } + case 'C': { + bool value; + in >> value; + position++; + return QVariant::fromValue(value); + } + case 'I': { + qint32 value; + in >> value; + position += sizeof(qint32); + return QVariant::fromValue(value); + } + case 'F': { + float value; + in >> value; + position += sizeof(float); + return QVariant::fromValue(value); + } + case 'D': { + double value; + in >> value; + position += sizeof(double); + return QVariant::fromValue(value); + } + case 'L': { + qint64 value; + in >> value; + position += sizeof(qint64); + return QVariant::fromValue(value); + } + case 'f': { + return readBinaryArray(in, position); + } + case 'd': { + return readBinaryArray(in, position); + } + case 'l': { + return readBinaryArray(in, position); + } + case 'i': { + return readBinaryArray(in, position); + } + case 'b': { + return readBinaryArray(in, position); + } + case 'S': + case 'R': { + quint32 length; + in >> length; + position += sizeof(quint32) + length; + return QVariant::fromValue(in.device()->read(length)); + } + default: + throw QString("Unknown property type: ") + ch; + } +} + +FBXNode parseBinaryFBXNode(QDataStream& in, int& position) { + qint32 endOffset; + quint32 propertyCount; + quint32 propertyListLength; + quint8 nameLength; + + in >> endOffset; + in >> propertyCount; + in >> propertyListLength; + in >> nameLength; + position += sizeof(quint32) * 3 + sizeof(quint8); + + FBXNode node; + const int MIN_VALID_OFFSET = 40; + if (endOffset < MIN_VALID_OFFSET || nameLength == 0) { + // use a null name to indicate a null node + return node; + } + node.name = in.device()->read(nameLength); + position += nameLength; + + for (quint32 i = 0; i < propertyCount; i++) { + node.properties.append(parseBinaryFBXProperty(in, position)); + } + + while (endOffset > position) { + FBXNode child = parseBinaryFBXNode(in, position); + if (child.name.isNull()) { + return node; + + } else { + node.children.append(child); + } + } + + return node; +} + +class Tokenizer { +public: + + Tokenizer(QIODevice* device) : _device(device), _pushedBackToken(-1) { } + + enum SpecialToken { + NO_TOKEN = -1, + NO_PUSHBACKED_TOKEN = -1, + DATUM_TOKEN = 0x100 + }; + + int nextToken(); + const QByteArray& getDatum() const { return _datum; } + + void pushBackToken(int token) { _pushedBackToken = token; } + void ungetChar(char ch) { _device->ungetChar(ch); } + +private: + + QIODevice* _device; + QByteArray _datum; + int _pushedBackToken; +}; + +int Tokenizer::nextToken() { + if (_pushedBackToken != NO_PUSHBACKED_TOKEN) { + int token = _pushedBackToken; + _pushedBackToken = NO_PUSHBACKED_TOKEN; + return token; + } + + char ch; + while (_device->getChar(&ch)) { + if (QChar(ch).isSpace()) { + continue; // skip whitespace + } + switch (ch) { + case ';': + _device->readLine(); // skip the comment + break; + + case ':': + case '{': + case '}': + case ',': + return ch; // special punctuation + + case '\"': + _datum = ""; + while (_device->getChar(&ch)) { + if (ch == '\"') { // end on closing quote + break; + } + if (ch == '\\') { // handle escaped quotes + if (_device->getChar(&ch) && ch != '\"') { + _datum.append('\\'); + } + } + _datum.append(ch); + } + return DATUM_TOKEN; + + default: + _datum = ""; + _datum.append(ch); + while (_device->getChar(&ch)) { + if (QChar(ch).isSpace() || ch == ';' || ch == ':' || ch == '{' || ch == '}' || ch == ',' || ch == '\"') { + ungetChar(ch); // read until we encounter a special character, then replace it + break; + } + _datum.append(ch); + } + return DATUM_TOKEN; + } + } + return NO_TOKEN; +} + +FBXNode parseTextFBXNode(Tokenizer& tokenizer) { + FBXNode node; + + if (tokenizer.nextToken() != Tokenizer::DATUM_TOKEN) { + return node; + } + node.name = tokenizer.getDatum(); + + if (tokenizer.nextToken() != ':') { + return node; + } + + int token; + bool expectingDatum = true; + while ((token = tokenizer.nextToken()) != Tokenizer::NO_TOKEN) { + if (token == '{') { + for (FBXNode child = parseTextFBXNode(tokenizer); !child.name.isNull(); child = parseTextFBXNode(tokenizer)) { + node.children.append(child); + } + return node; + } + if (token == ',') { + expectingDatum = true; + + } else if (token == Tokenizer::DATUM_TOKEN && expectingDatum) { + QByteArray datum = tokenizer.getDatum(); + if ((token = tokenizer.nextToken()) == ':') { + tokenizer.ungetChar(':'); + tokenizer.pushBackToken(Tokenizer::DATUM_TOKEN); + return node; + + } else { + tokenizer.pushBackToken(token); + node.properties.append(datum); + expectingDatum = false; + } + } else { + tokenizer.pushBackToken(token); + return node; + } + } + + return node; +} + +FBXNode FBXReader::parseFBX(QIODevice* device) { + // verify the prolog + const QByteArray BINARY_PROLOG = "Kaydara FBX Binary "; + if (device->peek(BINARY_PROLOG.size()) != BINARY_PROLOG) { + // parse as a text file + FBXNode top; + Tokenizer tokenizer(device); + while (device->bytesAvailable()) { + FBXNode next = parseTextFBXNode(tokenizer); + if (next.name.isNull()) { + return top; + + } else { + top.children.append(next); + } + } + return top; + } + QDataStream in(device); + in.setByteOrder(QDataStream::LittleEndian); + in.setVersion(QDataStream::Qt_4_5); // for single/double precision switch + + // see http://code.blender.org/index.php/2013/08/fbx-binary-file-format-specification/ for an explanation + // of the FBX binary format + + // skip the rest of the header + const int HEADER_SIZE = 27; + in.skipRawData(HEADER_SIZE); + int position = HEADER_SIZE; + + // parse the top-level node + FBXNode top; + while (device->bytesAvailable()) { + FBXNode next = parseBinaryFBXNode(in, position); + if (next.name.isNull()) { + return top; + + } else { + top.children.append(next); + } + } + + return top; +} + diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index 841fdcfad9..148d103aeb 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -121,21 +121,21 @@ glm::vec2 OBJTokenizer::getVec2() { void setMeshPartDefaults(FBXMeshPart& meshPart, QString materialID) { - meshPart.diffuseColor = glm::vec3(1, 1, 1); + /* meshPart.diffuseColor = glm::vec3(1, 1, 1); meshPart.specularColor = glm::vec3(1, 1, 1); meshPart.emissiveColor = glm::vec3(0, 0, 0); meshPart.emissiveParams = glm::vec2(0, 1); meshPart.shininess = 40; - meshPart.opacity = 1; + meshPart.opacity = 1;*/ meshPart.materialID = materialID; - meshPart.opacity = 1.0; + /* meshPart.opacity = 1.0; meshPart._material = std::make_shared(); meshPart._material->setDiffuse(glm::vec3(1.0, 1.0, 1.0)); meshPart._material->setOpacity(1.0); meshPart._material->setMetallic(0.0); meshPart._material->setGloss(96.0); - meshPart._material->setEmissive(glm::vec3(0.0, 0.0, 0.0)); + meshPart._material->setEmissive(glm::vec3(0.0, 0.0, 0.0));*/ } // OBJFace @@ -486,7 +486,10 @@ FBXGeometry* OBJReader::readOBJ(QIODevice* device, const QVariantHash& mapping, } if (!groupMaterialName.isEmpty()) { OBJMaterial* material = &materials[groupMaterialName]; - // The code behind this is in transition. Some things are set directly in the FXBMeshPart... + + // TODO Fix this once the transision is understood + + /*// The code behind this is in transition. Some things are set directly in the FXBMeshPart... meshPart.materialID = groupMaterialName; meshPart.diffuseTexture.filename = material->diffuseTextureFilename; meshPart.specularTexture.filename = material->specularTextureFilename; @@ -495,6 +498,7 @@ FBXGeometry* OBJReader::readOBJ(QIODevice* device, const QVariantHash& mapping, meshPart._material->setMetallic(glm::length(material->specularColor)); meshPart._material->setGloss(material->shininess); meshPart._material->setOpacity(material->opacity); + */ } // qCDebug(modelformat) << "OBJ Reader part:" << meshPartCount << "name:" << leadFace.groupName << "material:" << groupMaterialName << "diffuse:" << meshPart._material->getDiffuse() << "faces:" << faceGroup.count() << "triangle indices will start with:" << mesh.vertices.count(); foreach(OBJFace face, faceGroup) { @@ -576,15 +580,18 @@ void fbxDebugDump(const FBXGeometry& fbxgeo) { foreach (FBXMeshPart meshPart, mesh.parts) { qCDebug(modelformat) << " quadIndices.count() =" << meshPart.quadIndices.count(); qCDebug(modelformat) << " triangleIndices.count() =" << meshPart.triangleIndices.count(); + /* qCDebug(modelformat) << " diffuseColor =" << meshPart.diffuseColor << "mat =" << meshPart._material->getDiffuse(); qCDebug(modelformat) << " specularColor =" << meshPart.specularColor << "mat =" << meshPart._material->getMetallic(); qCDebug(modelformat) << " emissiveColor =" << meshPart.emissiveColor << "mat =" << meshPart._material->getEmissive(); qCDebug(modelformat) << " emissiveParams =" << meshPart.emissiveParams; qCDebug(modelformat) << " gloss =" << meshPart.shininess << "mat =" << meshPart._material->getGloss(); qCDebug(modelformat) << " opacity =" << meshPart.opacity << "mat =" << meshPart._material->getOpacity(); + */ qCDebug(modelformat) << " materialID =" << meshPart.materialID; - qCDebug(modelformat) << " diffuse texture =" << meshPart.diffuseTexture.filename; + /* qCDebug(modelformat) << " diffuse texture =" << meshPart.diffuseTexture.filename; qCDebug(modelformat) << " specular texture =" << meshPart.specularTexture.filename; + */ } qCDebug(modelformat) << " clusters.count() =" << mesh.clusters.count(); foreach (FBXCluster cluster, mesh.clusters) { diff --git a/libraries/model/src/model/Asset.h b/libraries/model/src/model/Asset.h index 28e1caf4aa..1d5919d008 100644 --- a/libraries/model/src/model/Asset.h +++ b/libraries/model/src/model/Asset.h @@ -25,14 +25,29 @@ public: typedef std::vector< T > Vector; typedef int ID; - const ID INVALID_ID = 0; + static const ID INVALID_ID = 0; + typedef size_t Index; enum Version { DRAFT = 0, FINAL, NUM_VERSIONS, }; + static Version evalVersionFromID(ID id) { + if (ID <= 0) { + return DRAFT; + } else (ID > 0) { + return FINAL; + } + } + static Index evalIndexFromID(ID id) { + return Index(id < 0 ? -id : id) - 1; + } + static ID evalID(Index index, Version version) { + return (version == DRAFT ? -int(index + 1) : int(index + 1)); + } + Table() { for (auto e : _elements) { e.resize(0); @@ -40,29 +55,54 @@ public: } ~Table() {} - ID add(const T& element, Version v = FINAL) { - switch (v) { - case DRAFT: { - _elements[DRAFT].push_back(element); - return ID(-(_elements[DRAFT].size() - 1)); - break; + Index getNumElements() const { + return _elements[DRAFT].size(); + } + + ID add(const T& element) { + for (auto e : _elements) { + e.push_back(element); } - case FINAL: { - _elements[FINAL].push_back(element); - return ID(_elements[FINAL].size() - 1); - break; + return evalID(_elements[DRAFT].size(), DRAFT); + } + + void set(ID id, const T& element) { + Index index = evalIndexFromID(id); + if (index < getNumElements()) { + _elements[DRAFT][index] = element; } + } + + const T& get(ID id, const T& element) const { + Index index = evalIndexFromID(id); + if (index < getNumElements()) { + return _elements[DRAFT][index]; } - return INVALID_ID; + return _default; } protected: Vector _elements[NUM_VERSIONS]; + T _default; }; typedef Table< MaterialPointer > MaterialTable; +typedef Table< TextureChannelPointer > TextureChannelTable; + typedef Table< MeshPointer > MeshTable; + +class Shape { +public: + + MeshTable::ID _meshID{ MeshTable::INVALID_ID }; + int _partID = 0; + + MaterialTable::ID _materialID{ MaterialTable::INVALID_ID }; +}; + +typedef Table< Shape > ShapeTable; + class Asset { public: @@ -76,10 +116,14 @@ public: MaterialTable& editMaterials() { return _materials; } const MaterialTable& getMaterials() const { return _materials; } + ShapeTable& editShapes() { return _shapes; } + const ShapeTable& getShapes() const { return _shapes; } + protected: MeshTable _meshes; MaterialTable _materials; + ShapeTable _shapes; }; diff --git a/libraries/model/src/model/Material.h b/libraries/model/src/model/Material.h index e729eac603..8d548ad641 100755 --- a/libraries/model/src/model/Material.h +++ b/libraries/model/src/model/Material.h @@ -198,6 +198,15 @@ public: }; }; +class TextureChannel { +public: + TextureChannel() {} + + gpu::TextureView _texture; +}; +typedef std::shared_ptr< TextureChannel > TextureChannelPointer; + + class Material { public: typedef gpu::BufferView UniformBufferView; diff --git a/libraries/model/src/model/Material.slh b/libraries/model/src/model/Material.slh index f2fa0a2a25..2f75c2b41e 100644 --- a/libraries/model/src/model/Material.slh +++ b/libraries/model/src/model/Material.slh @@ -25,20 +25,18 @@ uniform materialBuffer { Material getMaterial() { return _mat; } - +/* float componentSRGBToLinear(float cs) { - /* sRGB to linear conversion - { cs / 12.92, cs <= 0.04045 - cl = { - { ((cs + 0.055)/1.055)^2.4, cs > 0.04045 - - constants: - T = 0.04045 - A = 1 / 1.055 = 0.94786729857 - B = 0.055 * A = 0.05213270142 - C = 1 / 12.92 = 0.0773993808 - G = 2.4 - */ + // sRGB to linear conversion + // { cs / 12.92, cs <= 0.04045 + // cl = { + // { ((cs + 0.055)/1.055)^2.4, cs > 0.04045 + // constants: + // T = 0.04045 + // A = 1 / 1.055 = 0.94786729857 + // B = 0.055 * A = 0.05213270142 + // C = 1 / 12.92 = 0.0773993808 + // G = 2.4 const float T = 0.04045; const float A = 0.947867; const float B = 0.052132; @@ -55,9 +53,10 @@ float componentSRGBToLinear(float cs) { vec3 SRGBToLinear(vec3 srgb) { return vec3(componentSRGBToLinear(srgb.x),componentSRGBToLinear(srgb.y),componentSRGBToLinear(srgb.z)); } - +vec3 getMaterialDiffuse(Material m) { return (gl_FragCoord.x < 800 ? SRGBToLinear(m._diffuse.rgb) : m._diffuse.rgb); } +*/ float getMaterialOpacity(Material m) { return m._diffuse.a; } -vec3 getMaterialDiffuse(Material m) { return (gl_FragCoord.x > 800 ? SRGBToLinear(m._diffuse.rgb) : m._diffuse.rgb); } +vec3 getMaterialDiffuse(Material m) { return m._diffuse.rgb; } vec3 getMaterialSpecular(Material m) { return m._specular.rgb; } float getMaterialShininess(Material m) { return m._specular.a; } diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 75028abe93..389466069a 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -412,8 +412,9 @@ void Resource::handleReplyFinished() { ResourceCache::requestCompleted(this); finishedLoading(true); - emit loaded(*reply); downloadFinished(reply); + // Signal the VERY end of loading AND processing a resource, at this point even the specialized class is finalized + emit loaded(*reply); } diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 2f81fe8b84..ec1f037f7e 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -1723,7 +1723,8 @@ void GeometryReader::run() { NetworkGeometry::NetworkGeometry(const QUrl& url, bool delayLoad, const QVariantHash& mapping, const QUrl& textureBaseUrl) : _url(url), _mapping(mapping), - _textureBaseUrl(textureBaseUrl.isValid() ? textureBaseUrl : url) { + _textureBaseUrl(textureBaseUrl.isValid() ? textureBaseUrl : url), + _asset() { if (delayLoad) { _state = DelayState; @@ -1910,6 +1911,9 @@ static NetworkMesh* buildNetworkMesh(const FBXMesh& mesh, const QUrl& textureBas int totalIndices = 0; bool checkForTexcoordLightmap = false; + // process material parts + foreach (const FBXMaterial& mat, ) { + // process network parts foreach (const FBXMeshPart& part, mesh.parts) { NetworkMeshPart* networkPart = new NetworkMeshPart(); @@ -2051,11 +2055,14 @@ static NetworkMesh* buildNetworkMesh(const FBXMesh& mesh, const QUrl& textureBas void NetworkGeometry::modelParseSuccess(FBXGeometry* geometry) { // assume owner ship of geometry pointer _geometry.reset(geometry); + _asset = _geometry->_asset; foreach(const FBXMesh& mesh, _geometry->meshes) { _meshes.emplace_back(buildNetworkMesh(mesh, _textureBaseUrl)); } + foreach(const FBXMaterial& material, _geometry-> + _state = SuccessState; emit onSuccess(*this, *_geometry.get()); diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h index 71fa35c054..c83c884aaa 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -25,9 +25,12 @@ #include +#include + class NetworkGeometry; class NetworkMesh; class NetworkTexture; +class NetworkMaterial; typedef glm::vec3 Vec3Key; @@ -329,6 +332,7 @@ public: // WARNING: only valid when isLoaded returns true. const FBXGeometry& getFBXGeometry() const { return *_geometry; } const std::vector>& getMeshes() const { return _meshes; } + const model::AssetPointer getAsset() const { return _asset; } void setTextureWithNameToURL(const QString& name, const QUrl& url); QStringList getTextureNames() const; @@ -357,6 +361,8 @@ protected slots: void modelParseSuccess(FBXGeometry* geometry); void modelParseError(int error, QString str); + void oneTextureLoaded(); + protected: void attemptRequestInternal(); void requestMapping(const QUrl& url); @@ -378,6 +384,9 @@ protected: std::unique_ptr _geometry; std::vector> _meshes; + // The model asset created from this NetworkGeometry + model::AssetPointer _asset; + // cache for isLoadedWithTextures() mutable bool _isLoadedWithTextures = false; }; @@ -413,6 +422,15 @@ public: bool isTranslucent() const; }; +class NetworkMaterial { +public: + model::MaterialTable::ID _materialID; + + typedef std::map TextureChannelIDs; + TextureChannelIDs _textureChannelIDs; + +}; + /// The state associated with a single mesh. class NetworkMesh { public: diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 8a704486b1..d05320c24c 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -768,6 +768,16 @@ public: QUrl url; int meshIndex; int partIndex; + + // Core definition of a Shape = transform + model/mesh/part + material + model::AssetPointer _asset; + model::ShapeTable::ID _shapeID; + + Transform _transform; + model::MeshPointer _mesh; + int _part; + model::MaterialPointer _material; + }; namespace render { diff --git a/libraries/render-utils/src/model.slf b/libraries/render-utils/src/model.slf index 93a4a29988..f455030f6f 100755 --- a/libraries/render-utils/src/model.slf +++ b/libraries/render-utils/src/model.slf @@ -28,14 +28,12 @@ void main(void) { // Fetch diffuse map vec4 diffuse = texture(diffuseMap, _texCoord0); - vec3 vertexColor = (gl_FragCoord.y > 400 ? SRGBToLinear(_color.rgb) : _color.rgb); - Material mat = getMaterial(); packDeferredFragment( normalize(_normal.xyz), evalOpaqueFinalAlpha(getMaterialOpacity(mat), diffuse.a), - getMaterialDiffuse(mat) * diffuse.rgb * vertexColor, + getMaterialDiffuse(mat) * diffuse.rgb * _color, getMaterialSpecular(mat), getMaterialShininess(mat)); } diff --git a/libraries/render-utils/src/model.slv b/libraries/render-utils/src/model.slv index 4319e9f9e0..75237b52b7 100755 --- a/libraries/render-utils/src/model.slv +++ b/libraries/render-utils/src/model.slv @@ -17,6 +17,36 @@ <$declareStandardTransform()$> + +/* +float componentSRGBToLinear(float cs) { + // sRGB to linear conversion + // { cs / 12.92, cs <= 0.04045 + // cl = { + // { ((cs + 0.055)/1.055)^2.4, cs > 0.04045 + // constants: + // T = 0.04045 + // A = 1 / 1.055 = 0.94786729857 + // B = 0.055 * A = 0.05213270142 + // C = 1 / 12.92 = 0.0773993808 + // G = 2.4 + const float T = 0.04045; + const float A = 0.947867; + const float B = 0.052132; + const float C = 0.077399; + const float G = 2.4; + + if (cs > T) { + return pow((cs * A + B), G); + } else { + return cs * C; + } +} + +vec3 SRGBToLinear(vec3 srgb) { + return vec3(componentSRGBToLinear(srgb.x),componentSRGBToLinear(srgb.y),componentSRGBToLinear(srgb.z)); +} +*/ const int MAX_TEXCOORDS = 2; uniform mat4 texcoordMatrices[MAX_TEXCOORDS]; @@ -30,7 +60,9 @@ void main(void) { // pass along the diffuse color _color = inColor.xyz; - + // _color = SRGBToLinear(inColor.xyz); + + // and the texture coordinates _texCoord0 = (texcoordMatrices[0] * vec4(inTexCoord0.st, 0.0, 1.0)).st;