From 5edb312346d67877736ffc762eef004045ff9508 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 23 Sep 2019 10:22:13 -0700 Subject: [PATCH] gltf wip --- libraries/fbx/src/GLTFSerializer.cpp | 1950 ++++++++++++++------------ libraries/fbx/src/GLTFSerializer.h | 77 +- libraries/hfm/src/hfm/HFM.h | 6 +- tests-manual/fbx/CMakeLists.txt | 11 + tests-manual/fbx/src/main.cpp | 77 + 5 files changed, 1199 insertions(+), 922 deletions(-) create mode 100644 tests-manual/fbx/CMakeLists.txt create mode 100644 tests-manual/fbx/src/main.cpp diff --git a/libraries/fbx/src/GLTFSerializer.cpp b/libraries/fbx/src/GLTFSerializer.cpp index 4f1d871158..fe63159543 100755 --- a/libraries/fbx/src/GLTFSerializer.cpp +++ b/libraries/fbx/src/GLTFSerializer.cpp @@ -25,9 +25,13 @@ #include #include +#include +#include #include #include +#include + #include #include #include @@ -37,30 +41,57 @@ #include "FBXSerializer.h" -#define GLTF_GET_INDICIES(accCount) int index1 = (indices[n + 0] * accCount); int index2 = (indices[n + 1] * accCount); int index3 = (indices[n + 2] * accCount); +#define GLTF_GET_INDICIES(accCount) \ + int index1 = (indices[n + 0] * accCount); \ + int index2 = (indices[n + 1] * accCount); \ + int index3 = (indices[n + 2] * accCount); -#define GLTF_APPEND_ARRAY_1(newArray, oldArray) GLTF_GET_INDICIES(1) \ -newArray.append(oldArray[index1]); \ -newArray.append(oldArray[index2]); \ -newArray.append(oldArray[index3]); +#define GLTF_APPEND_ARRAY_1(newArray, oldArray) \ + GLTF_GET_INDICIES(1) \ + newArray.append(oldArray[index1]); \ + newArray.append(oldArray[index2]); \ + newArray.append(oldArray[index3]); -#define GLTF_APPEND_ARRAY_2(newArray, oldArray) GLTF_GET_INDICIES(2) \ -newArray.append(oldArray[index1]); newArray.append(oldArray[index1 + 1]); \ -newArray.append(oldArray[index2]); newArray.append(oldArray[index2 + 1]); \ -newArray.append(oldArray[index3]); newArray.append(oldArray[index3 + 1]); +#define GLTF_APPEND_ARRAY_2(newArray, oldArray) \ + GLTF_GET_INDICIES(2) \ + newArray.append(oldArray[index1]); \ + newArray.append(oldArray[index1 + 1]); \ + newArray.append(oldArray[index2]); \ + newArray.append(oldArray[index2 + 1]); \ + newArray.append(oldArray[index3]); \ + newArray.append(oldArray[index3 + 1]); -#define GLTF_APPEND_ARRAY_3(newArray, oldArray) GLTF_GET_INDICIES(3) \ -newArray.append(oldArray[index1]); newArray.append(oldArray[index1 + 1]); newArray.append(oldArray[index1 + 2]); \ -newArray.append(oldArray[index2]); newArray.append(oldArray[index2 + 1]); newArray.append(oldArray[index2 + 2]); \ -newArray.append(oldArray[index3]); newArray.append(oldArray[index3 + 1]); newArray.append(oldArray[index3 + 2]); +#define GLTF_APPEND_ARRAY_3(newArray, oldArray) \ + GLTF_GET_INDICIES(3) \ + newArray.append(oldArray[index1]); \ + newArray.append(oldArray[index1 + 1]); \ + newArray.append(oldArray[index1 + 2]); \ + newArray.append(oldArray[index2]); \ + newArray.append(oldArray[index2 + 1]); \ + newArray.append(oldArray[index2 + 2]); \ + newArray.append(oldArray[index3]); \ + newArray.append(oldArray[index3 + 1]); \ + newArray.append(oldArray[index3 + 2]); -#define GLTF_APPEND_ARRAY_4(newArray, oldArray) GLTF_GET_INDICIES(4) \ -newArray.append(oldArray[index1]); newArray.append(oldArray[index1 + 1]); newArray.append(oldArray[index1 + 2]); newArray.append(oldArray[index1 + 3]); \ -newArray.append(oldArray[index2]); newArray.append(oldArray[index2 + 1]); newArray.append(oldArray[index2 + 2]); newArray.append(oldArray[index2 + 3]); \ -newArray.append(oldArray[index3]); newArray.append(oldArray[index3 + 1]); newArray.append(oldArray[index3 + 2]); newArray.append(oldArray[index3 + 3]); +#define GLTF_APPEND_ARRAY_4(newArray, oldArray) \ + GLTF_GET_INDICIES(4) \ + newArray.append(oldArray[index1]); \ + newArray.append(oldArray[index1 + 1]); \ + newArray.append(oldArray[index1 + 2]); \ + newArray.append(oldArray[index1 + 3]); \ + newArray.append(oldArray[index2]); \ + newArray.append(oldArray[index2 + 1]); \ + newArray.append(oldArray[index2 + 2]); \ + newArray.append(oldArray[index2 + 3]); \ + newArray.append(oldArray[index3]); \ + newArray.append(oldArray[index3 + 1]); \ + newArray.append(oldArray[index3 + 2]); \ + newArray.append(oldArray[index3 + 3]); -bool GLTFSerializer::getStringVal(const QJsonObject& object, const QString& fieldname, - QString& value, QMap& defined) { +bool GLTFSerializer::getStringVal(const QJsonObject& object, + const QString& fieldname, + QString& value, + QMap& defined) { bool _defined = (object.contains(fieldname) && object[fieldname].isString()); if (_defined) { value = object[fieldname].toString(); @@ -69,8 +100,10 @@ bool GLTFSerializer::getStringVal(const QJsonObject& object, const QString& fiel return _defined; } -bool GLTFSerializer::getBoolVal(const QJsonObject& object, const QString& fieldname, - bool& value, QMap& defined) { +bool GLTFSerializer::getBoolVal(const QJsonObject& object, + const QString& fieldname, + bool& value, + QMap& defined) { bool _defined = (object.contains(fieldname) && object[fieldname].isBool()); if (_defined) { value = object[fieldname].toBool(); @@ -79,8 +112,7 @@ bool GLTFSerializer::getBoolVal(const QJsonObject& object, const QString& fieldn return _defined; } -bool GLTFSerializer::getIntVal(const QJsonObject& object, const QString& fieldname, - int& value, QMap& defined) { +bool GLTFSerializer::getIntVal(const QJsonObject& object, const QString& fieldname, int& value, QMap& defined) { bool _defined = (object.contains(fieldname) && !object[fieldname].isNull()); if (_defined) { value = object[fieldname].toInt(); @@ -89,8 +121,10 @@ bool GLTFSerializer::getIntVal(const QJsonObject& object, const QString& fieldna return _defined; } -bool GLTFSerializer::getDoubleVal(const QJsonObject& object, const QString& fieldname, - double& value, QMap& defined) { +bool GLTFSerializer::getDoubleVal(const QJsonObject& object, + const QString& fieldname, + double& value, + QMap& defined) { bool _defined = (object.contains(fieldname) && object[fieldname].isDouble()); if (_defined) { value = object[fieldname].toDouble(); @@ -98,8 +132,10 @@ bool GLTFSerializer::getDoubleVal(const QJsonObject& object, const QString& fiel defined.insert(fieldname, _defined); return _defined; } -bool GLTFSerializer::getObjectVal(const QJsonObject& object, const QString& fieldname, - QJsonObject& value, QMap& defined) { +bool GLTFSerializer::getObjectVal(const QJsonObject& object, + const QString& fieldname, + QJsonObject& value, + QMap& defined) { bool _defined = (object.contains(fieldname) && object[fieldname].isObject()); if (_defined) { value = object[fieldname].toObject(); @@ -108,12 +144,14 @@ bool GLTFSerializer::getObjectVal(const QJsonObject& object, const QString& fiel return _defined; } -bool GLTFSerializer::getIntArrayVal(const QJsonObject& object, const QString& fieldname, - QVector& values, QMap& defined) { +bool GLTFSerializer::getIntArrayVal(const QJsonObject& object, + const QString& fieldname, + QVector& values, + QMap& defined) { bool _defined = (object.contains(fieldname) && object[fieldname].isArray()); if (_defined) { QJsonArray arr = object[fieldname].toArray(); - foreach(const QJsonValue & v, arr) { + for (const QJsonValue& v : arr) { if (!v.isNull()) { values.push_back(v.toInt()); } @@ -123,12 +161,14 @@ bool GLTFSerializer::getIntArrayVal(const QJsonObject& object, const QString& fi return _defined; } -bool GLTFSerializer::getDoubleArrayVal(const QJsonObject& object, const QString& fieldname, - QVector& values, QMap& defined) { +bool GLTFSerializer::getDoubleArrayVal(const QJsonObject& object, + const QString& fieldname, + QVector& values, + QMap& defined) { bool _defined = (object.contains(fieldname) && object[fieldname].isArray()); if (_defined) { QJsonArray arr = object[fieldname].toArray(); - foreach(const QJsonValue & v, arr) { + for (const QJsonValue& v : arr) { if (v.isDouble()) { values.push_back(v.toDouble()); } @@ -138,8 +178,10 @@ bool GLTFSerializer::getDoubleArrayVal(const QJsonObject& object, const QString& return _defined; } -bool GLTFSerializer::getObjectArrayVal(const QJsonObject& object, const QString& fieldname, - QJsonArray& objects, QMap& defined) { +bool GLTFSerializer::getObjectArrayVal(const QJsonObject& object, + const QString& fieldname, + QJsonArray& objects, + QMap& defined) { bool _defined = (object.contains(fieldname) && object[fieldname].isArray()); if (_defined) { objects = object[fieldname].toArray(); @@ -149,7 +191,7 @@ bool GLTFSerializer::getObjectArrayVal(const QJsonObject& object, const QString& } hifi::ByteArray GLTFSerializer::setGLBChunks(const hifi::ByteArray& data) { - int byte = 4; + int byte = 4; int jsonStart = data.indexOf("JSON", Qt::CaseSensitive); int binStart = data.indexOf("BIN", Qt::CaseSensitive); int jsonLength, binLength; @@ -173,8 +215,7 @@ hifi::ByteArray GLTFSerializer::setGLBChunks(const hifi::ByteArray& data) { return jsonChunk; } -int GLTFSerializer::getMeshPrimitiveRenderingMode(const QString& type) -{ +int GLTFSerializer::getMeshPrimitiveRenderingMode(const QString& type) { if (type == "POINTS") { return GLTFMeshPrimitivesRenderingMode::POINTS; } @@ -199,8 +240,7 @@ int GLTFSerializer::getMeshPrimitiveRenderingMode(const QString& type) return GLTFMeshPrimitivesRenderingMode::TRIANGLES; } -int GLTFSerializer::getAccessorType(const QString& type) -{ +int GLTFSerializer::getAccessorType(const QString& type) { if (type == "SCALAR") { return GLTFAccessorType::SCALAR; } @@ -225,8 +265,7 @@ int GLTFSerializer::getAccessorType(const QString& type) return GLTFAccessorType::SCALAR; } -int GLTFSerializer::getMaterialAlphaMode(const QString& type) -{ +int GLTFSerializer::getMaterialAlphaMode(const QString& type) { if (type == "OPAQUE") { return GLTFMaterialAlphaMode::OPAQUE; } @@ -239,8 +278,7 @@ int GLTFSerializer::getMaterialAlphaMode(const QString& type) return GLTFMaterialAlphaMode::OPAQUE; } -int GLTFSerializer::getCameraType(const QString& type) -{ +int GLTFSerializer::getCameraType(const QString& type) { if (type == "orthographic") { return GLTFCameraTypes::ORTHOGRAPHIC; } @@ -250,8 +288,7 @@ int GLTFSerializer::getCameraType(const QString& type) return GLTFCameraTypes::PERSPECTIVE; } -int GLTFSerializer::getImageMimeType(const QString& mime) -{ +int GLTFSerializer::getImageMimeType(const QString& mime) { if (mime == "image/jpeg") { return GLTFImageMimetype::JPEG; } @@ -261,8 +298,7 @@ int GLTFSerializer::getImageMimeType(const QString& mime) return GLTFImageMimetype::JPEG; } -int GLTFSerializer::getAnimationSamplerInterpolation(const QString& interpolation) -{ +int GLTFSerializer::getAnimationSamplerInterpolation(const QString& interpolation) { if (interpolation == "LINEAR") { return GLTFAnimationSamplerInterpolation::LINEAR; } @@ -273,8 +309,7 @@ bool GLTFSerializer::setAsset(const QJsonObject& object) { QJsonObject jsAsset; bool isAssetDefined = getObjectVal(object, "asset", jsAsset, _file.defined); if (isAssetDefined) { - if (!getStringVal(jsAsset, "version", _file.asset.version, - _file.asset.defined) || _file.asset.version != "2.0") { + if (!getStringVal(jsAsset, "version", _file.asset.version, _file.asset.defined) || _file.asset.version != "2.0") { return false; } getStringVal(jsAsset, "generator", _file.asset.generator, _file.asset.defined); @@ -283,7 +318,8 @@ bool GLTFSerializer::setAsset(const QJsonObject& object) { return isAssetDefined; } -GLTFAccessor::GLTFAccessorSparse::GLTFAccessorSparseIndices GLTFSerializer::createAccessorSparseIndices(const QJsonObject& object) { +GLTFAccessor::GLTFAccessorSparse::GLTFAccessorSparseIndices GLTFSerializer::createAccessorSparseIndices( + const QJsonObject& object) { GLTFAccessor::GLTFAccessorSparse::GLTFAccessorSparseIndices accessorSparseIndices; getIntVal(object, "bufferView", accessorSparseIndices.bufferView, accessorSparseIndices.defined); @@ -293,7 +329,8 @@ GLTFAccessor::GLTFAccessorSparse::GLTFAccessorSparseIndices GLTFSerializer::crea return accessorSparseIndices; } -GLTFAccessor::GLTFAccessorSparse::GLTFAccessorSparseValues GLTFSerializer::createAccessorSparseValues(const QJsonObject& object) { +GLTFAccessor::GLTFAccessorSparse::GLTFAccessorSparseValues GLTFSerializer::createAccessorSparseValues( + const QJsonObject& object) { GLTFAccessor::GLTFAccessorSparse::GLTFAccessorSparseValues accessorSparseValues; getIntVal(object, "bufferView", accessorSparseValues.bufferView, accessorSparseValues.defined); @@ -320,7 +357,7 @@ GLTFAccessor::GLTFAccessorSparse GLTFSerializer::createAccessorSparse(const QJso bool GLTFSerializer::addAccessor(const QJsonObject& object) { GLTFAccessor accessor; - + getIntVal(object, "bufferView", accessor.bufferView, accessor.defined); getIntVal(object, "byteOffset", accessor.byteOffset, accessor.defined); getIntVal(object, "componentType", accessor.componentType, accessor.defined); @@ -346,10 +383,10 @@ bool GLTFSerializer::addAccessor(const QJsonObject& object) { bool GLTFSerializer::addAnimation(const QJsonObject& object) { GLTFAnimation animation; - + QJsonArray channels; if (getObjectArrayVal(object, "channels", channels, animation.defined)) { - foreach(const QJsonValue & v, channels) { + for (const QJsonValue& v : channels) { if (v.isObject()) { GLTFChannel channel; getIntVal(v.toObject(), "sampler", channel.sampler, channel.defined); @@ -357,14 +394,14 @@ bool GLTFSerializer::addAnimation(const QJsonObject& object) { if (getObjectVal(v.toObject(), "target", jsChannel, channel.defined)) { getIntVal(jsChannel, "node", channel.target.node, channel.target.defined); getIntVal(jsChannel, "path", channel.target.path, channel.target.defined); - } + } } } } QJsonArray samplers; if (getObjectArrayVal(object, "samplers", samplers, animation.defined)) { - foreach(const QJsonValue & v, samplers) { + for (const QJsonValue& v : samplers) { if (v.isObject()) { GLTFAnimationSampler sampler; getIntVal(v.toObject(), "input", sampler.input, sampler.defined); @@ -376,7 +413,7 @@ bool GLTFSerializer::addAnimation(const QJsonObject& object) { } } } - + _file.animations.push_back(animation); return true; @@ -384,20 +421,20 @@ bool GLTFSerializer::addAnimation(const QJsonObject& object) { bool GLTFSerializer::addBufferView(const QJsonObject& object) { GLTFBufferView bufferview; - + getIntVal(object, "buffer", bufferview.buffer, bufferview.defined); getIntVal(object, "byteLength", bufferview.byteLength, bufferview.defined); getIntVal(object, "byteOffset", bufferview.byteOffset, bufferview.defined); getIntVal(object, "target", bufferview.target, bufferview.defined); - + _file.bufferviews.push_back(bufferview); - + return true; } bool GLTFSerializer::addBuffer(const QJsonObject& object) { GLTFBuffer buffer; - + getIntVal(object, "byteLength", buffer.byteLength, buffer.defined); if (_url.toString().endsWith("glb")) { @@ -413,13 +450,13 @@ bool GLTFSerializer::addBuffer(const QJsonObject& object) { } } _file.buffers.push_back(buffer); - + return true; } bool GLTFSerializer::addCamera(const QJsonObject& object) { GLTFCamera camera; - + QJsonObject jsPerspective; QJsonObject jsOrthographic; QString type; @@ -439,15 +476,15 @@ bool GLTFSerializer::addCamera(const QJsonObject& object) { } else if (getStringVal(object, "type", type, camera.defined)) { camera.type = getCameraType(type); } - + _file.cameras.push_back(camera); - + return true; } bool GLTFSerializer::addImage(const QJsonObject& object) { GLTFImage image; - + QString mime; getStringVal(object, "uri", image.uri, image.defined); if (image.uri.contains("data:image/png;base64,")) { @@ -457,16 +494,18 @@ bool GLTFSerializer::addImage(const QJsonObject& object) { } if (getStringVal(object, "mimeType", mime, image.defined)) { image.mimeType = getImageMimeType(mime); - } + } getIntVal(object, "bufferView", image.bufferView, image.defined); - + _file.images.push_back(image); return true; } -bool GLTFSerializer::getIndexFromObject(const QJsonObject& object, const QString& field, - int& outidx, QMap& defined) { +bool GLTFSerializer::getIndexFromObject(const QJsonObject& object, + const QString& field, + int& outidx, + QMap& defined) { QJsonObject subobject; if (getObjectVal(object, field, subobject, defined)) { QMap tmpdefined = QMap(); @@ -491,23 +530,18 @@ bool GLTFSerializer::addMaterial(const QJsonObject& object) { getDoubleVal(object, "alphaCutoff", material.alphaCutoff, material.defined); QJsonObject jsMetallicRoughness; if (getObjectVal(object, "pbrMetallicRoughness", jsMetallicRoughness, material.defined)) { - getDoubleArrayVal(jsMetallicRoughness, "baseColorFactor", - material.pbrMetallicRoughness.baseColorFactor, + getDoubleArrayVal(jsMetallicRoughness, "baseColorFactor", material.pbrMetallicRoughness.baseColorFactor, material.pbrMetallicRoughness.defined); - getIndexFromObject(jsMetallicRoughness, "baseColorTexture", - material.pbrMetallicRoughness.baseColorTexture, + getIndexFromObject(jsMetallicRoughness, "baseColorTexture", material.pbrMetallicRoughness.baseColorTexture, material.pbrMetallicRoughness.defined); - getDoubleVal(jsMetallicRoughness, "metallicFactor", - material.pbrMetallicRoughness.metallicFactor, + getDoubleVal(jsMetallicRoughness, "metallicFactor", material.pbrMetallicRoughness.metallicFactor, material.pbrMetallicRoughness.defined); - getDoubleVal(jsMetallicRoughness, "roughnessFactor", - material.pbrMetallicRoughness.roughnessFactor, + getDoubleVal(jsMetallicRoughness, "roughnessFactor", material.pbrMetallicRoughness.roughnessFactor, material.pbrMetallicRoughness.defined); - getIndexFromObject(jsMetallicRoughness, "metallicRoughnessTexture", - material.pbrMetallicRoughness.metallicRoughnessTexture, - material.pbrMetallicRoughness.defined); + getIndexFromObject(jsMetallicRoughness, "metallicRoughnessTexture", + material.pbrMetallicRoughness.metallicRoughnessTexture, material.pbrMetallicRoughness.defined); } - _file.materials.push_back(material); + _file.materials.push_back(material); return true; } @@ -519,18 +553,18 @@ bool GLTFSerializer::addMesh(const QJsonObject& object) { QJsonArray jsPrimitives; object.keys(); if (getObjectArrayVal(object, "primitives", jsPrimitives, mesh.defined)) { - foreach(const QJsonValue & prim, jsPrimitives) { + for (const QJsonValue& prim : jsPrimitives) { if (prim.isObject()) { GLTFMeshPrimitive primitive; QJsonObject jsPrimitive = prim.toObject(); getIntVal(jsPrimitive, "mode", primitive.mode, primitive.defined); getIntVal(jsPrimitive, "indices", primitive.indices, primitive.defined); getIntVal(jsPrimitive, "material", primitive.material, primitive.defined); - + QJsonObject jsAttributes; if (getObjectVal(jsPrimitive, "attributes", jsAttributes, primitive.defined)) { QStringList attrKeys = jsAttributes.keys(); - foreach(const QString & attrKey, attrKeys) { + for (const QString& attrKey : attrKeys) { int attrVal; getIntVal(jsAttributes, attrKey, attrVal, primitive.attributes.defined); primitive.attributes.values.insert(attrKey, attrVal); @@ -538,14 +572,13 @@ bool GLTFSerializer::addMesh(const QJsonObject& object) { } QJsonArray jsTargets; - if (getObjectArrayVal(jsPrimitive, "targets", jsTargets, primitive.defined)) - { - foreach(const QJsonValue & tar, jsTargets) { + if (getObjectArrayVal(jsPrimitive, "targets", jsTargets, primitive.defined)) { + for (const QJsonValue& tar : jsTargets) { if (tar.isObject()) { QJsonObject jsTarget = tar.toObject(); QStringList tarKeys = jsTarget.keys(); GLTFMeshPrimitiveAttr target; - foreach(const QString & tarKey, tarKeys) { + for (const QString& tarKey : tarKeys) { int tarVal; getIntVal(jsTarget, tarKey, tarVal, target.defined); target.values.insert(tarKey, tarVal); @@ -553,7 +586,7 @@ bool GLTFSerializer::addMesh(const QJsonObject& object) { primitive.targets.push_back(target); } } - } + } mesh.primitives.push_back(primitive); } } @@ -564,9 +597,7 @@ bool GLTFSerializer::addMesh(const QJsonObject& object) { if (getObjectVal(object, "extras", jsExtras, mesh.defined)) { QJsonArray jsTargetNames; if (getObjectArrayVal(jsExtras, "targetNames", jsTargetNames, extras.defined)) { - foreach (const QJsonValue& tarName, jsTargetNames) { - extras.targetNames.push_back(tarName.toString()); - } + foreach (const QJsonValue& tarName, jsTargetNames) { extras.targetNames.push_back(tarName.toString()); } } mesh.extras = extras; } @@ -578,7 +609,7 @@ bool GLTFSerializer::addMesh(const QJsonObject& object) { bool GLTFSerializer::addNode(const QJsonObject& object) { GLTFNode node; - + getStringVal(object, "name", node.name, node.defined); getIntVal(object, "camera", node.camera, node.defined); getIntVal(object, "mesh", node.mesh, node.defined); @@ -607,7 +638,6 @@ bool GLTFSerializer::addSampler(const QJsonObject& object) { _file.samplers.push_back(sampler); return true; - } bool GLTFSerializer::addScene(const QJsonObject& object) { @@ -633,10 +663,10 @@ bool GLTFSerializer::addSkin(const QJsonObject& object) { } bool GLTFSerializer::addTexture(const QJsonObject& object) { - GLTFTexture texture; + GLTFTexture texture; getIntVal(object, "sampler", texture.sampler, texture.defined); getIntVal(object, "source", texture.source, texture.defined); - + _file.textures.push_back(texture); return true; @@ -649,8 +679,8 @@ bool GLTFSerializer::parseGLTF(const hifi::ByteArray& data) { if (_url.toString().endsWith("glb") && data.indexOf("glTF") == 0 && data.contains("JSON")) { jsonChunk = setGLBChunks(data); - } - + } + QJsonDocument d = QJsonDocument::fromJson(jsonChunk); QJsonObject jsFile = d.object(); @@ -658,7 +688,7 @@ bool GLTFSerializer::parseGLTF(const hifi::ByteArray& data) { if (success) { QJsonArray accessors; if (getObjectArrayVal(jsFile, "accessors", accessors, _file.defined)) { - foreach(const QJsonValue & accVal, accessors) { + for (const QJsonValue& accVal : accessors) { if (accVal.isObject()) { success = success && addAccessor(accVal.toObject()); } @@ -667,7 +697,7 @@ bool GLTFSerializer::parseGLTF(const hifi::ByteArray& data) { QJsonArray animations; if (getObjectArrayVal(jsFile, "animations", animations, _file.defined)) { - foreach(const QJsonValue & animVal, accessors) { + for (const QJsonValue& animVal : accessors) { if (animVal.isObject()) { success = success && addAnimation(animVal.toObject()); } @@ -676,7 +706,7 @@ bool GLTFSerializer::parseGLTF(const hifi::ByteArray& data) { QJsonArray bufferViews; if (getObjectArrayVal(jsFile, "bufferViews", bufferViews, _file.defined)) { - foreach(const QJsonValue & bufviewVal, bufferViews) { + for (const QJsonValue& bufviewVal : bufferViews) { if (bufviewVal.isObject()) { success = success && addBufferView(bufviewVal.toObject()); } @@ -685,7 +715,7 @@ bool GLTFSerializer::parseGLTF(const hifi::ByteArray& data) { QJsonArray buffers; if (getObjectArrayVal(jsFile, "buffers", buffers, _file.defined)) { - foreach(const QJsonValue & bufVal, buffers) { + for (const QJsonValue& bufVal : buffers) { if (bufVal.isObject()) { success = success && addBuffer(bufVal.toObject()); } @@ -694,7 +724,7 @@ bool GLTFSerializer::parseGLTF(const hifi::ByteArray& data) { QJsonArray cameras; if (getObjectArrayVal(jsFile, "cameras", cameras, _file.defined)) { - foreach(const QJsonValue & camVal, cameras) { + for (const QJsonValue& camVal : cameras) { if (camVal.isObject()) { success = success && addCamera(camVal.toObject()); } @@ -703,7 +733,7 @@ bool GLTFSerializer::parseGLTF(const hifi::ByteArray& data) { QJsonArray images; if (getObjectArrayVal(jsFile, "images", images, _file.defined)) { - foreach(const QJsonValue & imgVal, images) { + for (const QJsonValue& imgVal : images) { if (imgVal.isObject()) { success = success && addImage(imgVal.toObject()); } @@ -712,7 +742,7 @@ bool GLTFSerializer::parseGLTF(const hifi::ByteArray& data) { QJsonArray materials; if (getObjectArrayVal(jsFile, "materials", materials, _file.defined)) { - foreach(const QJsonValue & matVal, materials) { + for (const QJsonValue& matVal : materials) { if (matVal.isObject()) { success = success && addMaterial(matVal.toObject()); } @@ -721,7 +751,7 @@ bool GLTFSerializer::parseGLTF(const hifi::ByteArray& data) { QJsonArray meshes; if (getObjectArrayVal(jsFile, "meshes", meshes, _file.defined)) { - foreach(const QJsonValue & meshVal, meshes) { + for (const QJsonValue& meshVal : meshes) { if (meshVal.isObject()) { success = success && addMesh(meshVal.toObject()); } @@ -730,7 +760,7 @@ bool GLTFSerializer::parseGLTF(const hifi::ByteArray& data) { QJsonArray nodes; if (getObjectArrayVal(jsFile, "nodes", nodes, _file.defined)) { - foreach(const QJsonValue & nodeVal, nodes) { + for (const QJsonValue& nodeVal : nodes) { if (nodeVal.isObject()) { success = success && addNode(nodeVal.toObject()); } @@ -739,7 +769,7 @@ bool GLTFSerializer::parseGLTF(const hifi::ByteArray& data) { QJsonArray samplers; if (getObjectArrayVal(jsFile, "samplers", samplers, _file.defined)) { - foreach(const QJsonValue & samVal, samplers) { + for (const QJsonValue& samVal : samplers) { if (samVal.isObject()) { success = success && addSampler(samVal.toObject()); } @@ -748,7 +778,7 @@ bool GLTFSerializer::parseGLTF(const hifi::ByteArray& data) { QJsonArray scenes; if (getObjectArrayVal(jsFile, "scenes", scenes, _file.defined)) { - foreach(const QJsonValue & sceneVal, scenes) { + for (const QJsonValue& sceneVal : scenes) { if (sceneVal.isObject()) { success = success && addScene(sceneVal.toObject()); } @@ -757,7 +787,7 @@ bool GLTFSerializer::parseGLTF(const hifi::ByteArray& data) { QJsonArray skins; if (getObjectArrayVal(jsFile, "skins", skins, _file.defined)) { - foreach(const QJsonValue & skinVal, skins) { + for (const QJsonValue& skinVal : skins) { if (skinVal.isObject()) { success = success && addSkin(skinVal.toObject()); } @@ -766,51 +796,22 @@ bool GLTFSerializer::parseGLTF(const hifi::ByteArray& data) { QJsonArray textures; if (getObjectArrayVal(jsFile, "textures", textures, _file.defined)) { - foreach(const QJsonValue & texVal, textures) { + for (const QJsonValue& texVal : textures) { if (texVal.isObject()) { success = success && addTexture(texVal.toObject()); } } } - } + } return success; } -glm::mat4 GLTFSerializer::getModelTransform(const GLTFNode& node) { - glm::mat4 tmat = glm::mat4(1.0); - - if (node.defined["matrix"] && node.matrix.size() == 16) { - tmat = glm::mat4(node.matrix[0], node.matrix[1], node.matrix[2], node.matrix[3], - node.matrix[4], node.matrix[5], node.matrix[6], node.matrix[7], - node.matrix[8], node.matrix[9], node.matrix[10], node.matrix[11], - node.matrix[12], node.matrix[13], node.matrix[14], node.matrix[15]); - } else { - - if (node.defined["scale"] && node.scale.size() == 3) { - glm::vec3 scale = glm::vec3(node.scale[0], node.scale[1], node.scale[2]); - glm::mat4 s = glm::mat4(1.0); - s = glm::scale(s, scale); - tmat = s * tmat; - } - - if (node.defined["rotation"] && node.rotation.size() == 4) { - //quat(x,y,z,w) to quat(w,x,y,z) - glm::quat rotquat = glm::quat(node.rotation[3], node.rotation[0], node.rotation[1], node.rotation[2]); - tmat = glm::mat4_cast(rotquat) * tmat; - } - - if (node.defined["translation"] && node.translation.size() == 3) { - glm::vec3 trans = glm::vec3(node.translation[0], node.translation[1], node.translation[2]); - glm::mat4 t = glm::mat4(1.0); - t = glm::translate(t, trans); - tmat = t * tmat; - } - } - return tmat; +const glm::mat4& GLTFSerializer::getModelTransform(const GLTFNode& node) { + return node.transform; } void GLTFSerializer::getSkinInverseBindMatrices(std::vector>& inverseBindMatrixValues) { - for (auto &skin : _file.skins) { + for (auto& skin : _file.skins) { GLTFAccessor& indicesAccessor = _file.accessors[skin.inverseBindMatrices]; QVector matrices; addArrayFromAccessor(indicesAccessor, matrices); @@ -827,78 +828,193 @@ void GLTFSerializer::generateTargetData(int index, float weight, QVector; +ParentIndexMap findParentIndices(const QVector& nodes) { + ParentIndexMap parentIndices; + int numNodes = nodes.size(); + for (int nodeIndex = 0; nodeIndex < numNodes; ++nodeIndex) { + auto& gltfNode = nodes[nodeIndex]; + for (const auto& childIndex : gltfNode.children) { + parentIndices[childIndex] = nodeIndex; + } + } + return parentIndices; +} + +bool requiresNodeReordering(const ParentIndexMap& map) { + for (const auto& entry : map) { + if (entry.first < entry.second) { + return true; + } + } + return false; +} + +int findEdgeCount(const ParentIndexMap& parentIndices, int nodeIndex) { + auto parentsEnd = parentIndices.end(); + ParentIndexMap::const_iterator itr; + int result = 0; + while (parentsEnd != (itr = parentIndices.find(nodeIndex))) { + nodeIndex = itr->second; + ++result; + } + return result; +} + +using IndexBag = std::unordered_set; +using EdgeCountMap = std::map; +EdgeCountMap findEdgeCounts(int numNodes, const ParentIndexMap& map) { + EdgeCountMap edgeCounts; + // For each item, determine how many tranversals to a root node + for (int nodeIndex = 0; nodeIndex < numNodes; ++nodeIndex) { + // How many steps between this node and a root node? + int edgeCount = findEdgeCount(map, nodeIndex); + // Populate the result map + edgeCounts[edgeCount].insert(nodeIndex); + } + return edgeCounts; +} + +using ReorderMap = std::unordered_map; +ReorderMap buildReorderMap(const EdgeCountMap& map) { + ReorderMap result; + int newIndex = 0; + for (const auto& entry : map) { + const IndexBag& oldIndices = entry.second; + for (const auto& oldIndex : oldIndices) { + result.insert({ oldIndex, newIndex }); + ++newIndex; + } + } + return result; +} + +void reorderNodeIndices(QVector& indices, const ReorderMap& oldToNewIndexMap) { + for (auto& index : indices) { + index = oldToNewIndexMap.at(index); + } +} + +} // namespace gltf + +void GLTFFile::populateMaterialNames() { + // Build material names + QSet usedNames; + for (const auto& material : materials) { + if (!material.name.isEmpty()) { + usedNames.insert(material.name); + } + } + + int ukcount = 0; + const QString unknown{ "Default_%1" }; + for (auto& material : materials) { + QString generatedName = unknown.arg(ukcount++); + while (usedNames.contains(generatedName)) { + generatedName = unknown.arg(ukcount++); + } + material.name = generatedName; + material.defined.insert("name", true); + usedNames.insert(generatedName); + } +} + +void GLTFFile::reorderNodes(const std::unordered_map& oldToNewIndexMap) { + int numNodes = nodes.size(); + assert(numNodes == oldToNewIndexMap.size()); + QVector newNodes; + newNodes.resize(numNodes); + for (int oldIndex = 0; oldIndex < numNodes; ++oldIndex) { + const auto& oldNode = nodes[oldIndex]; + int newIndex = oldToNewIndexMap.at(oldIndex); + auto& newNode = newNodes[newIndex]; + // Write the new node + newNode = oldNode; + // Fixup the child indices + gltf::reorderNodeIndices(newNode.children, oldToNewIndexMap); + } + newNodes.swap(nodes); + + for (auto& subScene : scenes) { + gltf::reorderNodeIndices(subScene.nodes, oldToNewIndexMap); + } +} + +// Ensure that the GLTF nodes are ordered so +void GLTFFile::sortNodes() { + // Find all the parents + auto parentIndices = gltf::findParentIndices(nodes); + // If the nodes are already in a good order, we're done + if (!gltf::requiresNodeReordering(parentIndices)) { + return; + } + + auto edgeCounts = gltf::findEdgeCounts(nodes.size(), parentIndices); + auto oldToNewIndexMap = gltf::buildReorderMap(edgeCounts); + reorderNodes(oldToNewIndexMap); + assert(!gltf::requiresNodeReordering(gltf::findParentIndices(nodes))); +} + +void GLTFNode::normalizeTransform() { + if (defined["matrix"] && matrix.size() == 16) { + transform = glm::make_mat4(matrix.constData()); + } else { + transform = glm::mat4(1.0); + if (defined["scale"] && scale.size() == 3) { + glm::vec3 scaleVec = glm::make_vec3(scale.data()); + transform = glm::scale(transform, scaleVec); + } + + if (defined["rotation"] && rotation.size() == 4) { + glm::quat rotQ = glm::make_quat(rotation.data()); + transform = glm::mat4_cast(rotQ) * transform; + } + + if (defined["translation"] && translation.size() == 3) { + glm::vec3 transV = glm::make_vec3(translation.data()); + transform = glm::translate(glm::mat4(1.0), transV) * transform; + } + } +} + +void GLTFFile::normalizeNodeTransforms() { + for (auto& node : nodes) { + node.normalizeTransform(); + } +} + bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::VariantHash& mapping, const hifi::URL& url) { int numNodes = _file.nodes.size(); - - //Build dependencies - QVector parents; - QVector sortedNodes; - parents.fill(-1, numNodes); - sortedNodes.reserve(numNodes); - int nodecount = 0; - foreach(auto &node, _file.nodes) { - foreach(int child, node.children) { - parents[child] = nodecount; + hfmModel.transforms.resize(numNodes); + + auto parentIndices = gltf::findParentIndices(_file.nodes); + const auto parentsEnd = parentIndices.end(); + for (int nodeIndex = 0; nodeIndex < numNodes; ++nodeIndex) { + auto& gltfNode = _file.nodes[nodeIndex]; + auto& hmfTransform = hfmModel.transforms[nodeIndex]; + auto parentItr = parentIndices.find(nodeIndex); + if (parentItr != parentsEnd ) { + hmfTransform.parent = parentItr->second; } - sortedNodes.push_back(nodecount); - ++nodecount; + hmfTransform.transform = getModelTransform(gltfNode); } // Build transforms - nodecount = 0; - foreach(auto &node, _file.nodes) { - // collect node transform - _file.nodes[nodecount].transforms.push_back(getModelTransform(node)); - int parentIndex = parents[nodecount]; - while (parentIndex != -1) { - const auto& parentNode = _file.nodes[parentIndex]; - // collect transforms for a node's parents, grandparents, etc. - _file.nodes[nodecount].transforms.push_back(getModelTransform(parentNode)); - parentIndex = parents[parentIndex]; - } - ++nodecount; - } - - - // since parent indices must exist in the sorted list before any of their children, sortedNodes might not be initialized in the correct order - // therefore we need to re-initialize the order in which nodes will be parsed - QVector hasBeenSorted; - hasBeenSorted.fill(false, numNodes); - int i = 0; // initial index - while (i < numNodes) { - int currentNode = sortedNodes[i]; - int parentIndex = parents[currentNode]; - if (parentIndex == -1 || hasBeenSorted[parentIndex]) { - hasBeenSorted[currentNode] = true; - ++i; - } else { - int j = i + 1; // index of node to be sorted - while (j < numNodes) { - int nextNode = sortedNodes[j]; - parentIndex = parents[nextNode]; - if (parentIndex == -1 || hasBeenSorted[parentIndex]) { - // swap with currentNode - hasBeenSorted[nextNode] = true; - sortedNodes[i] = nextNode; - sortedNodes[j] = currentNode; - ++i; - currentNode = sortedNodes[i]; - } - ++j; - } + for (int nodeIndex = 0; nodeIndex < numNodes; ++nodeIndex) { + auto& gltfNode = _file.nodes[nodeIndex]; + //gltfNode.transforms.push_back(getModelTransform(gltfNode)); + gltf::ParentIndexMap::const_iterator parentItr; + int curNode = nodeIndex; + while (parentsEnd != (parentItr = parentIndices.find(curNode))) { + curNode = parentItr->second; + auto& ancestorNode = _file.nodes[curNode]; + //gltfNode.transforms.push_back(getModelTransform(ancestorNode)); } } - - // Build map from original to new indices - QVector originalToNewNodeIndexMap; - originalToNewNodeIndexMap.fill(-1, numNodes); - for (int i = 0; i < numNodes; ++i) { - originalToNewNodeIndexMap[sortedNodes[i]] = i; - } - - // Build joints HFMJoint joint; joint.distanceToParent = 0; @@ -906,24 +1022,24 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::VariantHash& QVector globalTransforms; globalTransforms.resize(numNodes); - for (int nodeIndex : sortedNodes) { + for (int nodeIndex = 0; nodeIndex < numNodes; ++nodeIndex) { auto& node = _file.nodes[nodeIndex]; - - joint.parentIndex = parents[nodeIndex]; - if (joint.parentIndex != -1) { - joint.parentIndex = originalToNewNodeIndexMap[joint.parentIndex]; + auto parentItr = parentIndices.find(nodeIndex); + if (parentsEnd == parentItr) { + joint.parentIndex = -1; + } else { + joint.parentIndex = parentItr->second; } - joint.transform = node.transforms.first(); + + joint.transform = getModelTransform(node); joint.translation = extractTranslation(joint.transform); joint.rotation = glmExtractRotation(joint.transform); glm::vec3 scale = extractScale(joint.transform); joint.postTransform = glm::scale(glm::mat4(), scale); - - joint.parentIndex = parents[nodeIndex]; globalTransforms[nodeIndex] = joint.transform; + if (joint.parentIndex != -1) { globalTransforms[nodeIndex] = globalTransforms[joint.parentIndex] * globalTransforms[nodeIndex]; - joint.parentIndex = originalToNewNodeIndexMap[joint.parentIndex]; } joint.name = node.name; @@ -932,7 +1048,6 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::VariantHash& } hfmModel.shapeVertices.resize(hfmModel.joints.size()); - // get offset transform from mapping float unitScaleFactor = 1.0f; float offsetScale = mapping.value("scale", 1.0f).toFloat() * unitScaleFactor; @@ -953,7 +1068,7 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::VariantHash& getSkinInverseBindMatrices(inverseBindValues); for (int jointIndex = 0; jointIndex < numNodes; ++jointIndex) { - int nodeIndex = sortedNodes[jointIndex]; + int nodeIndex = jointIndex; auto joint = hfmModel.joints[jointIndex]; for (int s = 0; s < _file.skins.size(); ++s) { @@ -984,638 +1099,559 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::VariantHash& } } - - // Build materials - QVector materialIDs; - QString unknown = "Default"; - int ukcount = 0; - foreach(auto material, _file.materials) { - if (!material.defined["name"]) { - QString name = unknown + QString::number(++ukcount); - material.name = name; - material.defined.insert("name", true); - } - - QString mid = material.name; - materialIDs.push_back(mid); - } - - for (int i = 0; i < materialIDs.size(); ++i) { - QString& matid = materialIDs[i]; + for (const auto& material : _file.materials) { + const QString& matid = material.name; hfmModel.materials.emplace_back(); HFMMaterial& hfmMaterial = hfmModel.materials.back(); hfmMaterial._material = std::make_shared(); - hfmMaterial.name = hfmMaterial.materialID = matid; - setHFMMaterial(hfmMaterial, _file.materials[i]); + hfmMaterial.materialID = hfmMaterial.name; + setHFMMaterial(hfmMaterial, material); } - - // Build meshes - nodecount = 0; + int meshCount = _file.meshes.size(); + hfmModel.meshes.resize(meshCount); hfmModel.meshExtents.reset(); - for (int nodeIndex : sortedNodes) { - auto& node = _file.nodes[nodeIndex]; - if (node.defined["mesh"]) { - hfmModel.meshes.push_back(HFMMesh()); - HFMMesh& mesh = hfmModel.meshes[hfmModel.meshes.size() - 1]; - if (!hfmModel.hasSkeletonJoints) { + hfmModel.meshes.resize(meshCount); + for (int meshIndex = 0; meshIndex < meshCount; ++meshIndex) { + const auto& gltfMesh = _file.meshes[meshIndex]; + auto& mesh = hfmModel.meshes[meshIndex]; + mesh.meshIndex = meshIndex; +#if 0 + if (!hfmModel.hasSkeletonJoints) { + HFMCluster cluster; + cluster.jointIndex = nodeIndex; + cluster.inverseBindMatrix = glm::mat4(); + cluster.inverseBindTransform = Transform(cluster.inverseBindMatrix); + mesh.clusters.append(cluster); + } else { // skinned model + for (int j = 0; j < numNodes; ++j) { HFMCluster cluster; - cluster.jointIndex = nodecount; - cluster.inverseBindMatrix = glm::mat4(); + cluster.jointIndex = j; + cluster.inverseBindMatrix = jointInverseBindTransforms[j]; cluster.inverseBindTransform = Transform(cluster.inverseBindMatrix); mesh.clusters.append(cluster); - } else { // skinned model - for (int j = 0; j < numNodes; ++j) { - HFMCluster cluster; - cluster.jointIndex = j; - cluster.inverseBindMatrix = jointInverseBindTransforms[j]; - cluster.inverseBindTransform = Transform(cluster.inverseBindMatrix); - mesh.clusters.append(cluster); - } } - HFMCluster root; - root.jointIndex = 0; - root.inverseBindMatrix = jointInverseBindTransforms[root.jointIndex]; - root.inverseBindTransform = Transform(root.inverseBindMatrix); - mesh.clusters.append(root); + } + HFMCluster root; + root.jointIndex = 0; + root.inverseBindMatrix = jointInverseBindTransforms[root.jointIndex]; + root.inverseBindTransform = Transform(root.inverseBindMatrix); + mesh.clusters.append(root); +#endif - QList meshAttributes; - foreach(auto &primitive, _file.meshes[node.mesh].primitives) { - QList keys = primitive.attributes.values.keys(); - foreach (auto &key, keys) { - if (!meshAttributes.contains(key)) { - meshAttributes.push_back(key); - } - } + QSet meshAttributes; + for(const auto &primitive : gltfMesh.primitives) { + for (const auto& attribute : primitive.attributes.values.keys()) { + meshAttributes.insert(attribute); + } + } + + for(auto &primitive : gltfMesh.primitives) { + HFMMeshPart part = HFMMeshPart(); + + int indicesAccessorIdx = primitive.indices; + + GLTFAccessor& indicesAccessor = _file.accessors[indicesAccessorIdx]; + + // Buffers + constexpr int VERTEX_STRIDE = 3; + constexpr int NORMAL_STRIDE = 3; + constexpr int TEX_COORD_STRIDE = 2; + + QVector indices; + QVector vertices; + QVector normals; + QVector tangents; + QVector texcoords; + QVector texcoords2; + QVector colors; + QVector joints; + QVector weights; + + static int tangentStride = 4; + static int colorStride = 3; + static int jointStride = 4; + static int weightStride = 4; + + bool success = addArrayFromAccessor(indicesAccessor, indices); + + if (!success) { + qWarning(modelformat) << "There was a problem reading glTF INDICES data for model " << _url; + continue; } - foreach(auto &primitive, _file.meshes[node.mesh].primitives) { - HFMMeshPart part = HFMMeshPart(); + // Increment the triangle indices by the current mesh vertex count so each mesh part can all reference the same buffers within the mesh + int prevMeshVerticesCount = mesh.vertices.count(); - int indicesAccessorIdx = primitive.indices; + QList keys = primitive.attributes.values.keys(); + QVector clusterJoints; + QVector clusterWeights; - GLTFAccessor& indicesAccessor = _file.accessors[indicesAccessorIdx]; + for(auto &key : keys) { + int accessorIdx = primitive.attributes.values[key]; + GLTFAccessor& accessor = _file.accessors[accessorIdx]; + const auto vertexAttribute = GLTFVertexAttribute::fromString(key); + switch (vertexAttribute) { + case GLTFVertexAttribute::POSITION: + success = addArrayFromAttribute(vertexAttribute, accessor, vertices); + break; - // Buffers - QVector indices; - QVector vertices; - int verticesStride = 3; - QVector normals; - int normalStride = 3; - QVector tangents; - int tangentStride = 4; - QVector texcoords; - int texCoordStride = 2; - QVector texcoords2; - int texCoord2Stride = 2; - QVector colors; - int colorStride = 3; - QVector joints; - int jointStride = 4; - QVector weights; - int weightStride = 4; + case GLTFVertexAttribute::NORMAL: + success = addArrayFromAttribute(vertexAttribute, accessor, normals); + break; - bool success = addArrayFromAccessor(indicesAccessor, indices); + case GLTFVertexAttribute::TANGENT: + success = addArrayFromAttribute(vertexAttribute, accessor, tangents); + tangentStride = GLTFAccessorType::count((GLTFAccessorType::Value)accessor.type); + break; + case GLTFVertexAttribute::TEXCOORD_0: + success = addArrayFromAttribute(vertexAttribute, accessor, texcoords); + break; + + case GLTFVertexAttribute::TEXCOORD_1: + success = addArrayFromAttribute(vertexAttribute, accessor, texcoords2); + break; + + case GLTFVertexAttribute::COLOR_0: + success = addArrayFromAttribute(vertexAttribute, accessor, colors); + colorStride = GLTFAccessorType::count((GLTFAccessorType::Value)accessor.type); + break; + + case GLTFVertexAttribute::JOINTS_0: + success = addArrayFromAttribute(vertexAttribute, accessor, colors); + jointStride = GLTFAccessorType::count((GLTFAccessorType::Value)accessor.type); + break; + + case GLTFVertexAttribute::WEIGHTS_0: + success = addArrayFromAttribute(vertexAttribute, accessor, colors); + weightStride = GLTFAccessorType::count((GLTFAccessorType::Value)accessor.type); + break; + } if (!success) { - qWarning(modelformat) << "There was a problem reading glTF INDICES data for model " << _url; continue; } + } - // Increment the triangle indices by the current mesh vertex count so each mesh part can all reference the same buffers within the mesh - int prevMeshVerticesCount = mesh.vertices.count(); + // Validation stage + if (indices.count() == 0) { + qWarning(modelformat) << "Missing indices for model " << _url; + continue; + } + if (vertices.count() == 0) { + qWarning(modelformat) << "Missing vertices for model " << _url; + continue; + } - QList keys = primitive.attributes.values.keys(); - QVector clusterJoints; - QVector clusterWeights; + int partVerticesCount = vertices.size() / 3; - foreach(auto &key, keys) { - int accessorIdx = primitive.attributes.values[key]; + // generate the normals if they don't exist + // FIXME move to GLTF post-load processing + if (normals.size() == 0) { + QVector newIndices; + QVector newVertices; + QVector newNormals; + QVector newTexcoords; + QVector newTexcoords2; + QVector newColors; + QVector newJoints; + QVector newWeights; - GLTFAccessor& accessor = _file.accessors[accessorIdx]; + for (int n = 0; n < indices.size(); n = n + 3) { + int v1_index = (indices[n + 0] * 3); + int v2_index = (indices[n + 1] * 3); + int v3_index = (indices[n + 2] * 3); - if (key == "POSITION") { - if (accessor.type != GLTFAccessorType::VEC3) { - qWarning(modelformat) << "Invalid accessor type on glTF POSITION data for model " << _url; - continue; - } + glm::vec3 v1 = glm::vec3(vertices[v1_index], vertices[v1_index + 1], vertices[v1_index + 2]); + glm::vec3 v2 = glm::vec3(vertices[v2_index], vertices[v2_index + 1], vertices[v2_index + 2]); + glm::vec3 v3 = glm::vec3(vertices[v3_index], vertices[v3_index + 1], vertices[v3_index + 2]); - success = addArrayFromAccessor(accessor, vertices); - if (!success) { - qWarning(modelformat) << "There was a problem reading glTF POSITION data for model " << _url; - continue; - } - } else if (key == "NORMAL") { - if (accessor.type != GLTFAccessorType::VEC3) { - qWarning(modelformat) << "Invalid accessor type on glTF NORMAL data for model " << _url; - continue; - } + newVertices.append(v1.x); + newVertices.append(v1.y); + newVertices.append(v1.z); + newVertices.append(v2.x); + newVertices.append(v2.y); + newVertices.append(v2.z); + newVertices.append(v3.x); + newVertices.append(v3.y); + newVertices.append(v3.z); - success = addArrayFromAccessor(accessor, normals); - if (!success) { - qWarning(modelformat) << "There was a problem reading glTF NORMAL data for model " << _url; - continue; - } - } else if (key == "TANGENT") { - if (accessor.type == GLTFAccessorType::VEC4) { - tangentStride = 4; - } else if (accessor.type == GLTFAccessorType::VEC3) { - tangentStride = 3; - } else { - qWarning(modelformat) << "Invalid accessor type on glTF TANGENT data for model " << _url; - continue; - } + glm::vec3 norm = glm::normalize(glm::cross(v2 - v1, v3 - v1)); - success = addArrayFromAccessor(accessor, tangents); - if (!success) { - qWarning(modelformat) << "There was a problem reading glTF TANGENT data for model " << _url; - tangentStride = 0; - continue; - } - } else if (key == "TEXCOORD_0") { - success = addArrayFromAccessor(accessor, texcoords); - if (!success) { - qWarning(modelformat) << "There was a problem reading glTF TEXCOORD_0 data for model " << _url; - continue; - } + newNormals.append(norm.x); + newNormals.append(norm.y); + newNormals.append(norm.z); + newNormals.append(norm.x); + newNormals.append(norm.y); + newNormals.append(norm.z); + newNormals.append(norm.x); + newNormals.append(norm.y); + newNormals.append(norm.z); - if (accessor.type != GLTFAccessorType::VEC2) { - qWarning(modelformat) << "Invalid accessor type on glTF TEXCOORD_0 data for model " << _url; - continue; - } - } else if (key == "TEXCOORD_1") { - success = addArrayFromAccessor(accessor, texcoords2); - if (!success) { - qWarning(modelformat) << "There was a problem reading glTF TEXCOORD_1 data for model " << _url; - continue; - } - - if (accessor.type != GLTFAccessorType::VEC2) { - qWarning(modelformat) << "Invalid accessor type on glTF TEXCOORD_1 data for model " << _url; - continue; - } - } else if (key == "COLOR_0") { - if (accessor.type == GLTFAccessorType::VEC4) { - colorStride = 4; - } else if (accessor.type == GLTFAccessorType::VEC3) { - colorStride = 3; - } else { - qWarning(modelformat) << "Invalid accessor type on glTF COLOR_0 data for model " << _url; - continue; - } - - success = addArrayFromAccessor(accessor, colors); - if (!success) { - qWarning(modelformat) << "There was a problem reading glTF COLOR_0 data for model " << _url; - continue; - } - } else if (key == "JOINTS_0") { - if (accessor.type == GLTFAccessorType::VEC4) { - jointStride = 4; - } else if (accessor.type == GLTFAccessorType::VEC3) { - jointStride = 3; - } else if (accessor.type == GLTFAccessorType::VEC2) { - jointStride = 2; - } else if (accessor.type == GLTFAccessorType::SCALAR) { - jointStride = 1; - } else { - qWarning(modelformat) << "Invalid accessor type on glTF JOINTS_0 data for model " << _url; - continue; - } - - success = addArrayFromAccessor(accessor, joints); - if (!success) { - qWarning(modelformat) << "There was a problem reading glTF JOINTS_0 data for model " << _url; - continue; - } - } else if (key == "WEIGHTS_0") { - if (accessor.type == GLTFAccessorType::VEC4) { - weightStride = 4; - } else if (accessor.type == GLTFAccessorType::VEC3) { - weightStride = 3; - } else if (accessor.type == GLTFAccessorType::VEC2) { - weightStride = 2; - } else if (accessor.type == GLTFAccessorType::SCALAR) { - weightStride = 1; - } else { - qWarning(modelformat) << "Invalid accessor type on glTF WEIGHTS_0 data for model " << _url; - continue; - } - - success = addArrayFromAccessor(accessor, weights); - if (!success) { - qWarning(modelformat) << "There was a problem reading glTF WEIGHTS_0 data for model " << _url; - continue; - } - } - } - - // Validation stage - if (indices.count() == 0) { - qWarning(modelformat) << "Missing indices for model " << _url; - continue; - } - if (vertices.count() == 0) { - qWarning(modelformat) << "Missing vertices for model " << _url; - continue; - } - - int partVerticesCount = vertices.size() / 3; - - // generate the normals if they don't exist - if (normals.size() == 0) { - QVector newIndices; - QVector newVertices; - QVector newNormals; - QVector newTexcoords; - QVector newTexcoords2; - QVector newColors; - QVector newJoints; - QVector newWeights; - - for (int n = 0; n < indices.size(); n = n + 3) { - int v1_index = (indices[n + 0] * 3); - int v2_index = (indices[n + 1] * 3); - int v3_index = (indices[n + 2] * 3); - - glm::vec3 v1 = glm::vec3(vertices[v1_index], vertices[v1_index + 1], vertices[v1_index + 2]); - glm::vec3 v2 = glm::vec3(vertices[v2_index], vertices[v2_index + 1], vertices[v2_index + 2]); - glm::vec3 v3 = glm::vec3(vertices[v3_index], vertices[v3_index + 1], vertices[v3_index + 2]); - - newVertices.append(v1.x); - newVertices.append(v1.y); - newVertices.append(v1.z); - newVertices.append(v2.x); - newVertices.append(v2.y); - newVertices.append(v2.z); - newVertices.append(v3.x); - newVertices.append(v3.y); - newVertices.append(v3.z); - - glm::vec3 norm = glm::normalize(glm::cross(v2 - v1, v3 - v1)); - - newNormals.append(norm.x); - newNormals.append(norm.y); - newNormals.append(norm.z); - newNormals.append(norm.x); - newNormals.append(norm.y); - newNormals.append(norm.z); - newNormals.append(norm.x); - newNormals.append(norm.y); - newNormals.append(norm.z); - - if (texcoords.size() == partVerticesCount * texCoordStride) { - GLTF_APPEND_ARRAY_2(newTexcoords, texcoords) - } - - if (texcoords2.size() == partVerticesCount * texCoord2Stride) { - GLTF_APPEND_ARRAY_2(newTexcoords2, texcoords2) - } - - if (colors.size() == partVerticesCount * colorStride) { - if (colorStride == 4) { - GLTF_APPEND_ARRAY_4(newColors, colors) - } else { - GLTF_APPEND_ARRAY_3(newColors, colors) - } - } - - if (joints.size() == partVerticesCount * jointStride) { - if (jointStride == 4) { - GLTF_APPEND_ARRAY_4(newJoints, joints) - } else if (jointStride == 3) { - GLTF_APPEND_ARRAY_3(newJoints, joints) - } else if (jointStride == 2) { - GLTF_APPEND_ARRAY_2(newJoints, joints) - } else { - GLTF_APPEND_ARRAY_1(newJoints, joints) - } - } - - if (weights.size() == partVerticesCount * weightStride) { - if (weightStride == 4) { - GLTF_APPEND_ARRAY_4(newWeights, weights) - } else if (weightStride == 3) { - GLTF_APPEND_ARRAY_3(newWeights, weights) - } else if (weightStride == 2) { - GLTF_APPEND_ARRAY_2(newWeights, weights) - } else { - GLTF_APPEND_ARRAY_1(newWeights, weights) - } - } - newIndices.append(n); - newIndices.append(n + 1); - newIndices.append(n + 2); + if (texcoords.size() == partVerticesCount * TEX_COORD_STRIDE) { + GLTF_APPEND_ARRAY_2(newTexcoords, texcoords) } - vertices = newVertices; - normals = newNormals; - tangents = QVector(); - texcoords = newTexcoords; - texcoords2 = newTexcoords2; - colors = newColors; - joints = newJoints; - weights = newWeights; - indices = newIndices; + if (texcoords2.size() == partVerticesCount * TEX_COORD_STRIDE) { + GLTF_APPEND_ARRAY_2(newTexcoords2, texcoords2) + } - partVerticesCount = vertices.size() / 3; + if (colors.size() == partVerticesCount * colorStride) { + if (colorStride == 4) { + GLTF_APPEND_ARRAY_4(newColors, colors) + } else { + GLTF_APPEND_ARRAY_3(newColors, colors) + } + } + + if (joints.size() == partVerticesCount * jointStride) { + if (jointStride == 4) { + GLTF_APPEND_ARRAY_4(newJoints, joints) + } else if (jointStride == 3) { + GLTF_APPEND_ARRAY_3(newJoints, joints) + } else if (jointStride == 2) { + GLTF_APPEND_ARRAY_2(newJoints, joints) + } else { + GLTF_APPEND_ARRAY_1(newJoints, joints) + } + } + + if (weights.size() == partVerticesCount * weightStride) { + if (weightStride == 4) { + GLTF_APPEND_ARRAY_4(newWeights, weights) + } else if (weightStride == 3) { + GLTF_APPEND_ARRAY_3(newWeights, weights) + } else if (weightStride == 2) { + GLTF_APPEND_ARRAY_2(newWeights, weights) + } else { + GLTF_APPEND_ARRAY_1(newWeights, weights) + } + } + newIndices.append(n); + newIndices.append(n + 1); + newIndices.append(n + 2); } - QVector validatedIndices; - for (int n = 0; n < indices.count(); ++n) { - if (indices[n] < partVerticesCount) { - validatedIndices.push_back(indices[n] + prevMeshVerticesCount); + vertices = newVertices; + normals = newNormals; + tangents = QVector(); + texcoords = newTexcoords; + texcoords2 = newTexcoords2; + colors = newColors; + joints = newJoints; + weights = newWeights; + indices = newIndices; + + partVerticesCount = vertices.size() / 3; + } + + QVector validatedIndices; + for (int n = 0; n < indices.count(); ++n) { + if (indices[n] < partVerticesCount) { + validatedIndices.push_back(indices[n] + prevMeshVerticesCount); + } else { + validatedIndices = QVector(); + break; + } + } + + if (validatedIndices.size() == 0) { + qWarning(modelformat) << "Indices out of range for model " << _url; + continue; + } + + part.triangleIndices.append(validatedIndices); + + mesh.vertices.reserve(partVerticesCount); + for (int n = 0; n < vertices.size(); n = n + VERTEX_STRIDE) { + mesh.vertices.push_back(glm::vec3(vertices[n], vertices[n + 1], vertices[n + 2])); + } + + mesh.normals.reserve(partVerticesCount); + for (int n = 0; n < normals.size(); n = n + NORMAL_STRIDE) { + mesh.normals.push_back(glm::vec3(normals[n], normals[n + 1], normals[n + 2])); + } + + // TODO: add correct tangent generation + if (tangents.size() == partVerticesCount * tangentStride) { + mesh.tangents.reserve(partVerticesCount); + for (int n = 0; n < tangents.size(); n += tangentStride) { + float tanW = tangentStride == 4 ? tangents[n + 3] : 1; + mesh.tangents.push_back(glm::vec3(tanW * tangents[n], tangents[n + 1], tanW * tangents[n + 2])); + } + } else if (meshAttributes.contains("TANGENT")) { + mesh.tangents.resize(partVerticesCount); + } + + if (texcoords.size() == partVerticesCount * TEX_COORD_STRIDE) { + mesh.texCoords.reserve(partVerticesCount); + for (int n = 0; n < texcoords.size(); n = n + 2) { + mesh.texCoords.push_back(glm::vec2(texcoords[n], texcoords[n + 1])); + } + } else if (meshAttributes.contains("TEXCOORD_0")) { + mesh.texCoords.resize(partVerticesCount); + } + + if (texcoords2.size() == partVerticesCount * TEX_COORD_STRIDE) { + mesh.texCoords1.reserve(partVerticesCount); + for (int n = 0; n < texcoords2.size(); n = n + 2) { + mesh.texCoords1.push_back(glm::vec2(texcoords2[n], texcoords2[n + 1])); + } + } else if (meshAttributes.contains("TEXCOORD_1")) { + mesh.texCoords1.resize(partVerticesCount); + } + + if (colors.size() == partVerticesCount * colorStride) { + mesh.colors.reserve(partVerticesCount); + for (int n = 0; n < colors.size(); n += colorStride) { + mesh.colors.push_back(glm::vec3(colors[n], colors[n + 1], colors[n + 2])); + } + } else if (meshAttributes.contains("COLOR_0")) { + mesh.colors.reserve(partVerticesCount); + for (int i = 0; i < partVerticesCount; ++i) { + mesh.colors.push_back(glm::vec3(1.0f, 1.0f, 1.0f)); + } + } + + if (joints.size() == partVerticesCount * jointStride) { + for (int n = 0; n < joints.size(); n += jointStride) { + clusterJoints.push_back(joints[n]); + if (jointStride > 1) { + clusterJoints.push_back(joints[n + 1]); + if (jointStride > 2) { + clusterJoints.push_back(joints[n + 2]); + if (jointStride > 3) { + clusterJoints.push_back(joints[n + 3]); + } else { + clusterJoints.push_back(0); + } + } else { + clusterJoints.push_back(0); + clusterJoints.push_back(0); + } } else { - validatedIndices = QVector(); + clusterJoints.push_back(0); + clusterJoints.push_back(0); + clusterJoints.push_back(0); + } + } + } else if (meshAttributes.contains("JOINTS_0")) { + for (int i = 0; i < partVerticesCount; ++i) { + for (int j = 0; j < 4; ++j) { + clusterJoints.push_back(0); + } + } + } + + if (weights.size() == partVerticesCount * weightStride) { + for (int n = 0; n < weights.size(); n += weightStride) { + clusterWeights.push_back(weights[n]); + if (weightStride > 1) { + clusterWeights.push_back(weights[n + 1]); + if (weightStride > 2) { + clusterWeights.push_back(weights[n + 2]); + if (weightStride > 3) { + clusterWeights.push_back(weights[n + 3]); + } else { + clusterWeights.push_back(0.0f); + } + } else { + clusterWeights.push_back(0.0f); + clusterWeights.push_back(0.0f); + } + } else { + clusterWeights.push_back(0.0f); + clusterWeights.push_back(0.0f); + clusterWeights.push_back(0.0f); + } + } + } else if (meshAttributes.contains("WEIGHTS_0")) { + for (int i = 0; i < partVerticesCount; ++i) { + clusterWeights.push_back(1.0f); + for (int j = 1; j < 4; ++j) { + clusterWeights.push_back(0.0f); + } + } + } + +#if 0 + // Build weights (adapted from FBXSerializer.cpp) + if (hfmModel.hasSkeletonJoints) { + int prevMeshClusterIndexCount = mesh.clusterIndices.count(); + int prevMeshClusterWeightCount = mesh.clusterWeights.count(); + const int WEIGHTS_PER_VERTEX = 4; + const float ALMOST_HALF = 0.499f; + int numVertices = mesh.vertices.size() - prevMeshVerticesCount; + + // Append new cluster indices and weights for this mesh part + for (int i = 0; i < numVertices * WEIGHTS_PER_VERTEX; ++i) { + mesh.clusterIndices.push_back(mesh.clusters.size() - 1); + mesh.clusterWeights.push_back(0); + } + + for (int c = 0; c < clusterJoints.size(); ++c) { + mesh.clusterIndices[prevMeshClusterIndexCount + c] = + originalToNewNodeIndexMap[_file.skins[node.skin].joints[clusterJoints[c]]]; + } + + // normalize and compress to 16-bits + for (int i = 0; i < numVertices; ++i) { + int j = i * WEIGHTS_PER_VERTEX; + + float totalWeight = 0.0f; + for (int k = j; k < j + WEIGHTS_PER_VERTEX; ++k) { + totalWeight += clusterWeights[k]; + } + if (totalWeight > 0.0f) { + float weightScalingFactor = (float)(UINT16_MAX) / totalWeight; + for (int k = j; k < j + WEIGHTS_PER_VERTEX; ++k) { + mesh.clusterWeights[prevMeshClusterWeightCount + k] = (uint16_t)(weightScalingFactor * clusterWeights[k] + ALMOST_HALF); + } + } else { + mesh.clusterWeights[prevMeshClusterWeightCount + j] = (uint16_t)((float)(UINT16_MAX) + ALMOST_HALF); + } + for (int clusterIndex = 0; clusterIndex < mesh.clusters.size() - 1; ++clusterIndex) { + ShapeVertices& points = hfmModel.shapeVertices.at(clusterIndex); + glm::vec3 globalMeshScale = extractScale(globalTransforms[nodeIndex]); + const glm::mat4 meshToJoint = glm::scale(glm::mat4(), globalMeshScale) * jointInverseBindTransforms[clusterIndex]; + + const float EXPANSION_WEIGHT_THRESHOLD = 0.25f; + if (mesh.clusterWeights[j] >= EXPANSION_WEIGHT_THRESHOLD) { + // TODO: fix transformed vertices being pushed back + auto& vertex = mesh.vertices[i]; + const glm::mat4 vertexTransform = meshToJoint * (glm::translate(glm::mat4(), vertex)); + glm::vec3 transformedVertex = hfmModel.joints[clusterIndex].translation * (extractTranslation(vertexTransform)); + points.push_back(transformedVertex); + } + } + } + } +#endif + +#if 0 + if (primitive.defined["material"]) { + part.materialID = materialIDs[primitive.material]; + } +#endif + + mesh.parts.push_back(part); + + // populate the texture coordinates if they don't exist + if (mesh.texCoords.size() == 0 && !hfmModel.hasSkeletonJoints) { + for (int i = 0; i < part.triangleIndices.size(); ++i) { mesh.texCoords.push_back(glm::vec2(0.0, 1.0)); } + } + + // Build morph targets (blend shapes) + if (!primitive.targets.isEmpty()) { + + // Build list of blendshapes from FST + typedef QPair WeightedIndex; + hifi::VariantHash blendshapeMappings = mapping.value("bs").toHash(); + QMultiHash blendshapeIndices; + + for (int i = 0;; ++i) { + hifi::ByteArray blendshapeName = FACESHIFT_BLENDSHAPES[i]; + if (blendshapeName.isEmpty()) { break; } - } - - if (validatedIndices.size() == 0) { - qWarning(modelformat) << "Indices out of range for model " << _url; - continue; - } - - part.triangleIndices.append(validatedIndices); - - for (int n = 0; n < vertices.size(); n = n + verticesStride) { - mesh.vertices.push_back(glm::vec3(vertices[n], vertices[n + 1], vertices[n + 2])); - } - - for (int n = 0; n < normals.size(); n = n + normalStride) { - mesh.normals.push_back(glm::vec3(normals[n], normals[n + 1], normals[n + 2])); - } - - // TODO: add correct tangent generation - if (tangents.size() == partVerticesCount * tangentStride) { - for (int n = 0; n < tangents.size(); n += tangentStride) { - float tanW = tangentStride == 4 ? tangents[n + 3] : 1; - mesh.tangents.push_back(glm::vec3(tanW * tangents[n], tangents[n + 1], tanW * tangents[n + 2])); + QList mappings = blendshapeMappings.values(blendshapeName); + foreach (const QVariant& mapping, mappings) { + QVariantList blendshapeMapping = mapping.toList(); + blendshapeIndices.insert(blendshapeMapping.at(0).toByteArray(), WeightedIndex(i, blendshapeMapping.at(1).toFloat())); } - } else { - if (meshAttributes.contains("TANGENT")) { - for (int i = 0; i < partVerticesCount; ++i) { - mesh.tangents.push_back(glm::vec3(0.0f, 0.0f, 0.0f)); + } + + // glTF morph targets may or may not have names. if they are labeled, add them based on + // the corresponding names from the FST. otherwise, just add them in the order they are given + mesh.blendshapes.resize(blendshapeMappings.size()); + auto values = blendshapeIndices.values(); + auto keys = blendshapeIndices.keys(); + auto names = gltfMesh.extras.targetNames; + QVector weights = gltfMesh.weights; + + for (int weightedIndex = 0; weightedIndex < values.size(); ++weightedIndex) { + float weight = 0.1f; + int indexFromMapping = weightedIndex; + int targetIndex = weightedIndex; + hfmModel.blendshapeChannelNames.push_back("target_" + QString::number(weightedIndex)); + + if (!names.isEmpty()) { + targetIndex = names.indexOf(keys[weightedIndex]); + indexFromMapping = values[weightedIndex].first; + weight = weight * values[weightedIndex].second; + hfmModel.blendshapeChannelNames[weightedIndex] = keys[weightedIndex]; + } + HFMBlendshape& blendshape = mesh.blendshapes[indexFromMapping]; + blendshape.indices = part.triangleIndices; + auto target = primitive.targets[targetIndex]; + + QVector normals; + QVector vertices; + + if (weights.size() == primitive.targets.size()) { + int targetWeight = weights[targetIndex]; + if (targetWeight != 0) { + weight = weight * targetWeight; } } - } - if (texcoords.size() == partVerticesCount * texCoordStride) { - for (int n = 0; n < texcoords.size(); n = n + 2) { - mesh.texCoords.push_back(glm::vec2(texcoords[n], texcoords[n + 1])); + if (target.values.contains((QString) "NORMAL")) { + generateTargetData(target.values.value((QString) "NORMAL"), weight, normals); } - } else { - if (meshAttributes.contains("TEXCOORD_0")) { - for (int i = 0; i < partVerticesCount; ++i) { - mesh.texCoords.push_back(glm::vec2(0.0f, 0.0f)); - } + if (target.values.contains((QString) "POSITION")) { + generateTargetData(target.values.value((QString) "POSITION"), weight, vertices); } - } - - if (texcoords2.size() == partVerticesCount * texCoord2Stride) { - for (int n = 0; n < texcoords2.size(); n = n + 2) { - mesh.texCoords1.push_back(glm::vec2(texcoords2[n], texcoords2[n + 1])); - } - } else { - if (meshAttributes.contains("TEXCOORD_1")) { - for (int i = 0; i < partVerticesCount; ++i) { - mesh.texCoords1.push_back(glm::vec2(0.0f, 0.0f)); - } - } - } - - if (colors.size() == partVerticesCount * colorStride) { - for (int n = 0; n < colors.size(); n += colorStride) { - mesh.colors.push_back(glm::vec3(colors[n], colors[n + 1], colors[n + 2])); - } - } else { - if (meshAttributes.contains("COLOR_0")) { - for (int i = 0; i < partVerticesCount; ++i) { - mesh.colors.push_back(glm::vec3(1.0f, 1.0f, 1.0f)); - } - } - } - - if (joints.size() == partVerticesCount * jointStride) { - for (int n = 0; n < joints.size(); n += jointStride) { - clusterJoints.push_back(joints[n]); - if (jointStride > 1) { - clusterJoints.push_back(joints[n + 1]); - if (jointStride > 2) { - clusterJoints.push_back(joints[n + 2]); - if (jointStride > 3) { - clusterJoints.push_back(joints[n + 3]); - } else { - clusterJoints.push_back(0); - } - } else { - clusterJoints.push_back(0); - clusterJoints.push_back(0); - } + bool isNewBlendshape = blendshape.vertices.size() < vertices.size(); + int count = 0; + for (int i : blendshape.indices) { + if (isNewBlendshape) { + blendshape.vertices.push_back(vertices[i]); + blendshape.normals.push_back(normals[i]); } else { - clusterJoints.push_back(0); - clusterJoints.push_back(0); - clusterJoints.push_back(0); - } - } - } else { - if (meshAttributes.contains("JOINTS_0")) { - for (int i = 0; i < partVerticesCount; ++i) { - for (int j = 0; j < 4; ++j) { - clusterJoints.push_back(0); - } + blendshape.vertices[count] = blendshape.vertices[count] + vertices[i]; + blendshape.normals[count] = blendshape.normals[count] + normals[i]; + ++count; } } } - - if (weights.size() == partVerticesCount * weightStride) { - for (int n = 0; n < weights.size(); n += weightStride) { - clusterWeights.push_back(weights[n]); - if (weightStride > 1) { - clusterWeights.push_back(weights[n + 1]); - if (weightStride > 2) { - clusterWeights.push_back(weights[n + 2]); - if (weightStride > 3) { - clusterWeights.push_back(weights[n + 3]); - } else { - clusterWeights.push_back(0.0f); - } - } else { - clusterWeights.push_back(0.0f); - clusterWeights.push_back(0.0f); - } - } else { - clusterWeights.push_back(0.0f); - clusterWeights.push_back(0.0f); - clusterWeights.push_back(0.0f); - } - } - } else { - if (meshAttributes.contains("WEIGHTS_0")) { - for (int i = 0; i < partVerticesCount; ++i) { - clusterWeights.push_back(1.0f); - for (int j = 1; j < 4; ++j) { - clusterWeights.push_back(0.0f); - } - } - } - } - - // Build weights (adapted from FBXSerializer.cpp) - if (hfmModel.hasSkeletonJoints) { - int prevMeshClusterIndexCount = mesh.clusterIndices.count(); - int prevMeshClusterWeightCount = mesh.clusterWeights.count(); - const int WEIGHTS_PER_VERTEX = 4; - const float ALMOST_HALF = 0.499f; - int numVertices = mesh.vertices.size() - prevMeshVerticesCount; - - // Append new cluster indices and weights for this mesh part - for (int i = 0; i < numVertices * WEIGHTS_PER_VERTEX; ++i) { - mesh.clusterIndices.push_back(mesh.clusters.size() - 1); - mesh.clusterWeights.push_back(0); - } - - for (int c = 0; c < clusterJoints.size(); ++c) { - mesh.clusterIndices[prevMeshClusterIndexCount + c] = - originalToNewNodeIndexMap[_file.skins[node.skin].joints[clusterJoints[c]]]; - } - - // normalize and compress to 16-bits - for (int i = 0; i < numVertices; ++i) { - int j = i * WEIGHTS_PER_VERTEX; - - float totalWeight = 0.0f; - for (int k = j; k < j + WEIGHTS_PER_VERTEX; ++k) { - totalWeight += clusterWeights[k]; - } - if (totalWeight > 0.0f) { - float weightScalingFactor = (float)(UINT16_MAX) / totalWeight; - for (int k = j; k < j + WEIGHTS_PER_VERTEX; ++k) { - mesh.clusterWeights[prevMeshClusterWeightCount + k] = (uint16_t)(weightScalingFactor * clusterWeights[k] + ALMOST_HALF); - } - } else { - mesh.clusterWeights[prevMeshClusterWeightCount + j] = (uint16_t)((float)(UINT16_MAX) + ALMOST_HALF); - } - for (int clusterIndex = 0; clusterIndex < mesh.clusters.size() - 1; ++clusterIndex) { - ShapeVertices& points = hfmModel.shapeVertices.at(clusterIndex); - glm::vec3 globalMeshScale = extractScale(globalTransforms[nodeIndex]); - const glm::mat4 meshToJoint = glm::scale(glm::mat4(), globalMeshScale) * jointInverseBindTransforms[clusterIndex]; - - const float EXPANSION_WEIGHT_THRESHOLD = 0.25f; - if (mesh.clusterWeights[j] >= EXPANSION_WEIGHT_THRESHOLD) { - // TODO: fix transformed vertices being pushed back - auto& vertex = mesh.vertices[i]; - const glm::mat4 vertexTransform = meshToJoint * (glm::translate(glm::mat4(), vertex)); - glm::vec3 transformedVertex = hfmModel.joints[clusterIndex].translation * (extractTranslation(vertexTransform)); - points.push_back(transformedVertex); - } - } - } - } - - if (primitive.defined["material"]) { - part.materialID = materialIDs[primitive.material]; - } - mesh.parts.push_back(part); - - // populate the texture coordinates if they don't exist - if (mesh.texCoords.size() == 0 && !hfmModel.hasSkeletonJoints) { - for (int i = 0; i < part.triangleIndices.size(); ++i) { mesh.texCoords.push_back(glm::vec2(0.0, 1.0)); } - } - - // Build morph targets (blend shapes) - if (!primitive.targets.isEmpty()) { - - // Build list of blendshapes from FST - typedef QPair WeightedIndex; - hifi::VariantHash blendshapeMappings = mapping.value("bs").toHash(); - QMultiHash blendshapeIndices; - - for (int i = 0;; ++i) { - hifi::ByteArray blendshapeName = FACESHIFT_BLENDSHAPES[i]; - if (blendshapeName.isEmpty()) { - break; - } - QList mappings = blendshapeMappings.values(blendshapeName); - foreach (const QVariant& mapping, mappings) { - QVariantList blendshapeMapping = mapping.toList(); - blendshapeIndices.insert(blendshapeMapping.at(0).toByteArray(), WeightedIndex(i, blendshapeMapping.at(1).toFloat())); - } - } - - // glTF morph targets may or may not have names. if they are labeled, add them based on - // the corresponding names from the FST. otherwise, just add them in the order they are given - mesh.blendshapes.resize(blendshapeMappings.size()); - auto values = blendshapeIndices.values(); - auto keys = blendshapeIndices.keys(); - auto names = _file.meshes[node.mesh].extras.targetNames; - QVector weights = _file.meshes[node.mesh].weights; - - for (int weightedIndex = 0; weightedIndex < values.size(); ++weightedIndex) { - float weight = 0.1f; - int indexFromMapping = weightedIndex; - int targetIndex = weightedIndex; - hfmModel.blendshapeChannelNames.push_back("target_" + QString::number(weightedIndex)); - - if (!names.isEmpty()) { - targetIndex = names.indexOf(keys[weightedIndex]); - indexFromMapping = values[weightedIndex].first; - weight = weight * values[weightedIndex].second; - hfmModel.blendshapeChannelNames[weightedIndex] = keys[weightedIndex]; - } - HFMBlendshape& blendshape = mesh.blendshapes[indexFromMapping]; - blendshape.indices = part.triangleIndices; - auto target = primitive.targets[targetIndex]; - - QVector normals; - QVector vertices; - - if (weights.size() == primitive.targets.size()) { - int targetWeight = weights[targetIndex]; - if (targetWeight != 0) { - weight = weight * targetWeight; - } - } - - if (target.values.contains((QString) "NORMAL")) { - generateTargetData(target.values.value((QString) "NORMAL"), weight, normals); - } - if (target.values.contains((QString) "POSITION")) { - generateTargetData(target.values.value((QString) "POSITION"), weight, vertices); - } - bool isNewBlendshape = blendshape.vertices.size() < vertices.size(); - int count = 0; - for (int i : blendshape.indices) { - if (isNewBlendshape) { - blendshape.vertices.push_back(vertices[i]); - blendshape.normals.push_back(normals[i]); - } else { - blendshape.vertices[count] = blendshape.vertices[count] + vertices[i]; - blendshape.normals[count] = blendshape.normals[count] + normals[i]; - ++count; - } - } - } - } - - foreach(const glm::vec3& vertex, mesh.vertices) { - glm::vec3 transformedVertex = glm::vec3(globalTransforms[nodeIndex] * glm::vec4(vertex, 1.0f)); - mesh.meshExtents.addPoint(transformedVertex); - hfmModel.meshExtents.addPoint(transformedVertex); - } } - // Add epsilon to mesh extents to compensate for planar meshes - mesh.meshExtents.minimum -= glm::vec3(EPSILON, EPSILON, EPSILON); - mesh.meshExtents.maximum += glm::vec3(EPSILON, EPSILON, EPSILON); - hfmModel.meshExtents.minimum -= glm::vec3(EPSILON, EPSILON, EPSILON); - hfmModel.meshExtents.maximum += glm::vec3(EPSILON, EPSILON, EPSILON); - - mesh.meshIndex = (int)hfmModel.meshes.size(); +#if 0 + for(const glm::vec3& vertex : mesh.vertices) { + glm::vec3 transformedVertex = glm::vec3(globalTransforms[nodeIndex] * glm::vec4(vertex, 1.0f)); + mesh.meshExtents.addPoint(transformedVertex); + hfmModel.meshExtents.addPoint(transformedVertex); + } +#endif + } + + // Add epsilon to mesh extents to compensate for planar meshes + mesh.meshExtents.minimum -= glm::vec3(EPSILON, EPSILON, EPSILON); + mesh.meshExtents.maximum += glm::vec3(EPSILON, EPSILON, EPSILON); + hfmModel.meshExtents.minimum -= glm::vec3(EPSILON, EPSILON, EPSILON); + hfmModel.meshExtents.maximum += glm::vec3(EPSILON, EPSILON, EPSILON); + + } + + for (int nodeIndex = 0; nodeIndex < numNodes; ++nodeIndex) { + const auto& node = _file.nodes[nodeIndex]; + if (-1 == node.mesh) { + continue; + } + + const auto& mesh = _file.meshes[node.mesh]; + int primCount = (int)mesh.primitives.size(); + for (int primIndex = 0; primIndex < primCount; ++primIndex) { + const auto& primitive = mesh.primitives[primIndex]; + hfmModel.shapes.push_back({}); + auto& hfmShape = hfmModel.shapes.back(); + hfmShape.transform = nodeIndex; + hfmShape.mesh = node.mesh; + hfmShape.meshPart = primIndex; + hfmShape.material = primitive.material; } - ++nodecount; } return true; @@ -1637,9 +1673,8 @@ std::unique_ptr GLTFSerializer::getFactory() const { } HFMModel::Pointer GLTFSerializer::read(const hifi::ByteArray& data, const hifi::VariantHash& mapping, const hifi::URL& url) { - _url = url; - + // Normalize url for local files hifi::URL normalizeUrl = DependencyManager::get()->normalizeURL(_url); if (normalizeUrl.scheme().isEmpty() || (normalizeUrl.scheme() == "file")) { @@ -1649,6 +1684,9 @@ HFMModel::Pointer GLTFSerializer::read(const hifi::ByteArray& data, const hifi:: if (parseGLTF(data)) { //_file.dump(); + _file.sortNodes(); + _file.populateMaterialNames(); + _file.normalizeNodeTransforms(); auto hfmModelPtr = std::make_shared(); HFMModel& hfmModel = *hfmModelPtr; buildGeometry(hfmModel, mapping, _url); @@ -1672,7 +1710,7 @@ bool GLTFSerializer::readBinary(const QString& url, hifi::ByteArray& outdata) { hifi::URL binaryUrl = _url.resolved(url); std::tie(success, outdata) = requestData(binaryUrl); } - + return success; } @@ -1685,8 +1723,8 @@ bool GLTFSerializer::doesResourceExist(const QString& url) { } std::tuple GLTFSerializer::requestData(hifi::URL& url) { - auto request = DependencyManager::get()->createResourceRequest( - nullptr, url, true, -1, "GLTFSerializer::requestData"); + auto request = + DependencyManager::get()->createResourceRequest(nullptr, url, true, -1, "GLTFSerializer::requestData"); if (!request) { return std::make_tuple(false, hifi::ByteArray()); @@ -1705,19 +1743,16 @@ std::tuple GLTFSerializer::requestData(hifi::URL& url) { } hifi::ByteArray GLTFSerializer::requestEmbeddedData(const QString& url) { - QString binaryUrl = url.split(",")[1]; + QString binaryUrl = url.split(",")[1]; return binaryUrl.isEmpty() ? hifi::ByteArray() : QByteArray::fromBase64(binaryUrl.toUtf8()); } - QNetworkReply* GLTFSerializer::request(hifi::URL& url, bool isTest) { if (!qApp) { return nullptr; } bool aboutToQuit{ false }; - auto connection = QObject::connect(qApp, &QCoreApplication::aboutToQuit, [&] { - aboutToQuit = true; - }); + auto connection = QObject::connect(qApp, &QCoreApplication::aboutToQuit, [&] { aboutToQuit = true; }); QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest netRequest(url); netRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); @@ -1726,18 +1761,18 @@ QNetworkReply* GLTFSerializer::request(hifi::URL& url, bool isTest) { netReply->deleteLater(); return nullptr; } - QEventLoop loop; // Create an event loop that will quit when we get the finished signal + QEventLoop loop; // Create an event loop that will quit when we get the finished signal QObject::connect(netReply, SIGNAL(finished()), &loop, SLOT(quit())); - loop.exec(); // Nothing is going to happen on this whole run thread until we get this + loop.exec(); // Nothing is going to happen on this whole run thread until we get this QObject::disconnect(connection); - return netReply; // trying to sync later on. + return netReply; // trying to sync later on. } HFMTexture GLTFSerializer::getHFMTexture(const GLTFTexture& texture) { HFMTexture fbxtex = HFMTexture(); fbxtex.texcoordSet = 0; - + if (texture.defined["source"]) { QString url = _file.images[texture.source].uri; @@ -1745,10 +1780,10 @@ HFMTexture GLTFSerializer::getHFMTexture(const GLTFTexture& texture) { hifi::URL textureUrl = _url.resolved(url); fbxtex.name = fname; fbxtex.filename = textureUrl.toEncoded(); - + if (_url.toString().endsWith("glb") && !_glbBinary.isEmpty()) { int bufferView = _file.images[texture.source].bufferView; - + GLTFBufferView& imagesBufferview = _file.bufferviews[bufferView]; int offset = imagesBufferview.byteOffset; int length = imagesBufferview.byteLength; @@ -1758,19 +1793,15 @@ HFMTexture GLTFSerializer::getHFMTexture(const GLTFTexture& texture) { } if (url.contains("data:image/jpeg;base64,") || url.contains("data:image/png;base64,")) { - fbxtex.content = requestEmbeddedData(url); + fbxtex.content = requestEmbeddedData(url); } } return fbxtex; } void GLTFSerializer::setHFMMaterial(HFMMaterial& fbxmat, const GLTFMaterial& material) { - - if (material.defined["emissiveFactor"] && material.emissiveFactor.size() == 3) { - glm::vec3 emissive = glm::vec3(material.emissiveFactor[0], - material.emissiveFactor[1], - material.emissiveFactor[2]); + glm::vec3 emissive = glm::vec3(material.emissiveFactor[0], material.emissiveFactor[1], material.emissiveFactor[2]); fbxmat._material->setEmissive(emissive); } @@ -1778,12 +1809,12 @@ void GLTFSerializer::setHFMMaterial(HFMMaterial& fbxmat, const GLTFMaterial& mat fbxmat.emissiveTexture = getHFMTexture(_file.textures[material.emissiveTexture]); fbxmat.useEmissiveMap = true; } - + if (material.defined["normalTexture"]) { fbxmat.normalTexture = getHFMTexture(_file.textures[material.normalTexture]); fbxmat.useNormalMap = true; } - + if (material.defined["occlusionTexture"]) { fbxmat.occlusionTexture = getHFMTexture(_file.textures[material.occlusionTexture]); fbxmat.useOcclusionMap = true; @@ -1791,7 +1822,7 @@ void GLTFSerializer::setHFMMaterial(HFMMaterial& fbxmat, const GLTFMaterial& mat if (material.defined["pbrMetallicRoughness"]) { fbxmat.isPBSMaterial = true; - + if (material.pbrMetallicRoughness.defined["metallicFactor"]) { fbxmat.metallic = material.pbrMetallicRoughness.metallicFactor; } @@ -1811,23 +1842,20 @@ void GLTFSerializer::setHFMMaterial(HFMMaterial& fbxmat, const GLTFMaterial& mat if (material.pbrMetallicRoughness.defined["roughnessFactor"]) { fbxmat._material->setRoughness(material.pbrMetallicRoughness.roughnessFactor); } - if (material.pbrMetallicRoughness.defined["baseColorFactor"] && + if (material.pbrMetallicRoughness.defined["baseColorFactor"] && material.pbrMetallicRoughness.baseColorFactor.size() == 4) { - glm::vec3 dcolor = glm::vec3(material.pbrMetallicRoughness.baseColorFactor[0], - material.pbrMetallicRoughness.baseColorFactor[1], - material.pbrMetallicRoughness.baseColorFactor[2]); + glm::vec3 dcolor = + glm::vec3(material.pbrMetallicRoughness.baseColorFactor[0], material.pbrMetallicRoughness.baseColorFactor[1], + material.pbrMetallicRoughness.baseColorFactor[2]); fbxmat.diffuseColor = dcolor; fbxmat._material->setAlbedo(dcolor); fbxmat._material->setOpacity(material.pbrMetallicRoughness.baseColorFactor[3]); - } + } } - } -template -bool GLTFSerializer::readArray(const hifi::ByteArray& bin, int byteOffset, int count, - QVector& outarray, int accessorType) { - +template +bool GLTFSerializer::readArray(const hifi::ByteArray& bin, int byteOffset, int count, QVector& outarray, int accessorType) { QDataStream blobstream(bin); blobstream.setByteOrder(QDataStream::LittleEndian); blobstream.setVersion(QDataStream::Qt_5_9); @@ -1836,31 +1864,31 @@ bool GLTFSerializer::readArray(const hifi::ByteArray& bin, int byteOffset, int c int bufferCount = 0; switch (accessorType) { - case GLTFAccessorType::SCALAR: - bufferCount = 1; - break; - case GLTFAccessorType::VEC2: - bufferCount = 2; - break; - case GLTFAccessorType::VEC3: - bufferCount = 3; - break; - case GLTFAccessorType::VEC4: - bufferCount = 4; - break; - case GLTFAccessorType::MAT2: - bufferCount = 4; - break; - case GLTFAccessorType::MAT3: - bufferCount = 9; - break; - case GLTFAccessorType::MAT4: - bufferCount = 16; - break; - default: - qWarning(modelformat) << "Unknown accessorType: " << accessorType; - blobstream.unsetDevice(); - return false; + case GLTFAccessorType::SCALAR: + bufferCount = 1; + break; + case GLTFAccessorType::VEC2: + bufferCount = 2; + break; + case GLTFAccessorType::VEC3: + bufferCount = 3; + break; + case GLTFAccessorType::VEC4: + bufferCount = 4; + break; + case GLTFAccessorType::MAT2: + bufferCount = 4; + break; + case GLTFAccessorType::MAT3: + bufferCount = 9; + break; + case GLTFAccessorType::MAT4: + bufferCount = 16; + break; + default: + qWarning(modelformat) << "Unknown accessorType: " << accessorType; + blobstream.unsetDevice(); + return false; } for (int i = 0; i < count; ++i) { for (int j = 0; j < bufferCount; ++j) { @@ -1878,31 +1906,137 @@ bool GLTFSerializer::readArray(const hifi::ByteArray& bin, int byteOffset, int c blobstream.unsetDevice(); return true; } -template -bool GLTFSerializer::addArrayOfType(const hifi::ByteArray& bin, int byteOffset, int count, - QVector& outarray, int accessorType, int componentType) { - +template +bool GLTFSerializer::addArrayOfType(const hifi::ByteArray& bin, + int byteOffset, + int count, + QVector& outarray, + int accessorType, + int componentType) { switch (componentType) { - case GLTFAccessorComponentType::BYTE: {} - case GLTFAccessorComponentType::UNSIGNED_BYTE: { - return readArray(bin, byteOffset, count, outarray, accessorType); - } - case GLTFAccessorComponentType::SHORT: { - return readArray(bin, byteOffset, count, outarray, accessorType); - } - case GLTFAccessorComponentType::UNSIGNED_INT: { - return readArray(bin, byteOffset, count, outarray, accessorType); - } - case GLTFAccessorComponentType::UNSIGNED_SHORT: { - return readArray(bin, byteOffset, count, outarray, accessorType); - } - case GLTFAccessorComponentType::FLOAT: { - return readArray(bin, byteOffset, count, outarray, accessorType); - } + case GLTFAccessorComponentType::BYTE: { + } + case GLTFAccessorComponentType::UNSIGNED_BYTE: { + return readArray(bin, byteOffset, count, outarray, accessorType); + } + case GLTFAccessorComponentType::SHORT: { + return readArray(bin, byteOffset, count, outarray, accessorType); + } + case GLTFAccessorComponentType::UNSIGNED_INT: { + return readArray(bin, byteOffset, count, outarray, accessorType); + } + case GLTFAccessorComponentType::UNSIGNED_SHORT: { + return readArray(bin, byteOffset, count, outarray, accessorType); + } + case GLTFAccessorComponentType::FLOAT: { + return readArray(bin, byteOffset, count, outarray, accessorType); + } } return false; } + +template +bool GLTFSerializer::addArrayFromAttribute(GLTFVertexAttribute::Value vertexAttribute, GLTFAccessor& accessor, QVector& outarray) { + switch (vertexAttribute) { + case GLTFVertexAttribute::POSITION: + if (accessor.type != GLTFAccessorType::VEC3) { + qWarning(modelformat) << "Invalid accessor type on glTF POSITION data for model " << _url; + return false; + } + + if (!addArrayFromAccessor(accessor, outarray)) { + qWarning(modelformat) << "There was a problem reading glTF POSITION data for model " << _url; + return false; + } + break; + + case GLTFVertexAttribute::NORMAL: + if (accessor.type != GLTFAccessorType::VEC3) { + qWarning(modelformat) << "Invalid accessor type on glTF NORMAL data for model " << _url; + return false; + } + + if (!addArrayFromAccessor(accessor, outarray)) { + qWarning(modelformat) << "There was a problem reading glTF NORMAL data for model " << _url; + return false; + } + break; + + case GLTFVertexAttribute::TANGENT: + if (accessor.type != GLTFAccessorType::VEC4 && accessor.type != GLTFAccessorType::VEC3) { + qWarning(modelformat) << "Invalid accessor type on glTF TANGENT data for model " << _url; + return false; + } + break; + + if (!addArrayFromAccessor(accessor, outarray)) { + qWarning(modelformat) << "There was a problem reading glTF TANGENT data for model " << _url; + return false; + } + break; + + case GLTFVertexAttribute::TEXCOORD_0: + if (accessor.type != GLTFAccessorType::VEC2) { + qWarning(modelformat) << "Invalid accessor type on glTF TEXCOORD_0 data for model " << _url; + return false; + } + + if (!addArrayFromAccessor(accessor, outarray)) { + qWarning(modelformat) << "There was a problem reading glTF TEXCOORD_0 data for model " << _url; + return false; + } + break; + + case GLTFVertexAttribute::TEXCOORD_1: + if (accessor.type != GLTFAccessorType::VEC2) { + qWarning(modelformat) << "Invalid accessor type on glTF TEXCOORD_1 data for model " << _url; + return false; + } + if (!addArrayFromAccessor(accessor, outarray)) { + qWarning(modelformat) << "There was a problem reading glTF TEXCOORD_1 data for model " << _url; + return false; + } + break; + + case GLTFVertexAttribute::COLOR_0: + if (accessor.type != GLTFAccessorType::VEC4 && accessor.type != GLTFAccessorType::VEC3) { + qWarning(modelformat) << "Invalid accessor type on glTF COLOR_0 data for model " << _url; + return false; + } + + if (!addArrayFromAccessor(accessor, outarray)) { + qWarning(modelformat) << "There was a problem reading glTF COLOR_0 data for model " << _url; + return false; + } + break; + + case GLTFVertexAttribute::JOINTS_0: + if (accessor.type < GLTFAccessorType::SCALAR || accessor.type > GLTFAccessorType::VEC4) { + qWarning(modelformat) << "Invalid accessor type on glTF JOINTS_0 data for model " << _url; + return false; + } + + if (!addArrayFromAccessor(accessor, outarray)) { + qWarning(modelformat) << "There was a problem reading glTF JOINTS_0 data for model " << _url; + return false; + } + break; + + case GLTFVertexAttribute::WEIGHTS_0: + if (accessor.type < GLTFAccessorType::SCALAR || accessor.type > GLTFAccessorType::VEC4) { + qWarning(modelformat) << "Invalid accessor type on glTF WEIGHTS_0 data for model " << _url; + return false; + } + + if (!addArrayFromAccessor(accessor, outarray)) { + qWarning(modelformat) << "There was a problem reading glTF WEIGHTS_0 data for model " << _url; + } + } + + return true; +} + template bool GLTFSerializer::addArrayFromAccessor(GLTFAccessor& accessor, QVector& outarray) { bool success = true; @@ -1948,7 +2082,7 @@ bool GLTFSerializer::addArrayFromAccessor(GLTFAccessor& accessor, QVector& ou if (success) { for (int i = 0; i < accessor.sparse.count; ++i) { - if ((i * 3) + 2 < out_sparse_values_array.size()) { + if ((i * 3) + 2 < out_sparse_values_array.size()) { if ((out_sparse_indices_array[i] * 3) + 2 < outarray.length()) { for (int j = 0; j < 3; ++j) { outarray[(out_sparse_indices_array[i] * 3) + j] = out_sparse_values_array[(i * 3) + j]; @@ -1970,14 +2104,16 @@ bool GLTFSerializer::addArrayFromAccessor(GLTFAccessor& accessor, QVector& ou return success; } -void GLTFSerializer::retriangulate(const QVector& inIndices, const QVector& in_vertices, - const QVector& in_normals, QVector& outIndices, - QVector& out_vertices, QVector& out_normals) { +void GLTFSerializer::retriangulate(const QVector& inIndices, + const QVector& in_vertices, + const QVector& in_normals, + QVector& outIndices, + QVector& out_vertices, + QVector& out_normals) { for (int i = 0; i < inIndices.size(); i = i + 3) { - int idx1 = inIndices[i]; - int idx2 = inIndices[i+1]; - int idx3 = inIndices[i+2]; + int idx2 = inIndices[i + 1]; + int idx3 = inIndices[i + 2]; out_vertices.push_back(in_vertices[idx1]); out_vertices.push_back(in_vertices[idx2]); @@ -1988,8 +2124,8 @@ void GLTFSerializer::retriangulate(const QVector& inIndices, const QVector< out_normals.push_back(in_normals[idx3]); outIndices.push_back(i); - outIndices.push_back(i+1); - outIndices.push_back(i+2); + outIndices.push_back(i + 1); + outIndices.push_back(i + 2); } } @@ -1998,7 +2134,7 @@ void GLTFSerializer::glTFDebugDump() { for (GLTFNode node : _file.nodes) { if (node.defined["mesh"]) { qCDebug(modelformat) << "\n"; - qCDebug(modelformat) << " node_transforms" << node.transforms; + qCDebug(modelformat) << " node_transform" << node.transform; qCDebug(modelformat) << "\n"; } } @@ -2051,12 +2187,12 @@ void GLTFSerializer::hfmDebugDump(const HFMModel& hfmModel) { qCDebug(modelformat) << " colors.count() =" << mesh.colors.count(); qCDebug(modelformat) << " texCoords.count() =" << mesh.texCoords.count(); qCDebug(modelformat) << " texCoords1.count() =" << mesh.texCoords1.count(); - qCDebug(modelformat) << " clusterIndices.count() =" << mesh.clusterIndices.count(); - qCDebug(modelformat) << " clusterWeights.count() =" << mesh.clusterWeights.count(); - qCDebug(modelformat) << " modelTransform =" << mesh.modelTransform; + //qCDebug(modelformat) << " clusterIndices.count() =" << mesh.clusterIndices.count(); + //qCDebug(modelformat) << " clusterWeights.count() =" << mesh.clusterWeights.count(); + //qCDebug(modelformat) << " modelTransform =" << mesh.modelTransform; qCDebug(modelformat) << " parts.count() =" << mesh.parts.size(); qCDebug(modelformat) << "---------------- Meshes (blendshapes)--------"; - foreach(HFMBlendshape bshape, mesh.blendshapes) { + for (HFMBlendshape bshape : mesh.blendshapes) { qCDebug(modelformat) << "\n"; qCDebug(modelformat) << " bshape.indices.count() =" << bshape.indices.count(); qCDebug(modelformat) << " bshape.vertices.count() =" << bshape.vertices.count(); @@ -2064,37 +2200,37 @@ void GLTFSerializer::hfmDebugDump(const HFMModel& hfmModel) { qCDebug(modelformat) << "\n"; } qCDebug(modelformat) << "---------------- Meshes (meshparts)--------"; - foreach(HFMMeshPart meshPart, mesh.parts) { + for (HFMMeshPart meshPart : mesh.parts) { qCDebug(modelformat) << "\n"; qCDebug(modelformat) << " quadIndices.count() =" << meshPart.quadIndices.count(); qCDebug(modelformat) << " triangleIndices.count() =" << meshPart.triangleIndices.count(); - qCDebug(modelformat) << " materialID =" << meshPart.materialID; + //qCDebug(modelformat) << " materialID =" << meshPart.materialID; qCDebug(modelformat) << "\n"; - } qCDebug(modelformat) << "---------------- Meshes (clusters)--------"; - qCDebug(modelformat) << " clusters.count() =" << mesh.clusters.count(); - foreach(HFMCluster cluster, mesh.clusters) { - qCDebug(modelformat) << "\n"; - qCDebug(modelformat) << " jointIndex =" << cluster.jointIndex; - qCDebug(modelformat) << " inverseBindMatrix =" << cluster.inverseBindMatrix; - qCDebug(modelformat) << "\n"; - } - qCDebug(modelformat) << "\n"; + //qCDebug(modelformat) << " clusters.count() =" << mesh.clusters.count(); + //for(HFMCluster cluster : mesh.clusters) { + // qCDebug(modelformat) << "\n"; + // qCDebug(modelformat) << " jointIndex =" << cluster.jointIndex; + // qCDebug(modelformat) << " inverseBindMatrix =" << cluster.inverseBindMatrix; + // qCDebug(modelformat) << "\n"; + //} + //qCDebug(modelformat) << "\n"; } qCDebug(modelformat) << "---------------- AnimationFrames ----------------"; - foreach(HFMAnimationFrame anim, hfmModel.animationFrames) { + for (HFMAnimationFrame anim : hfmModel.animationFrames) { qCDebug(modelformat) << " anim.translations = " << anim.translations; qCDebug(modelformat) << " anim.rotations = " << anim.rotations; } QList mitomona_keys = hfmModel.meshIndicesToModelNames.keys(); - foreach(int key, mitomona_keys) { - qCDebug(modelformat) << " meshIndicesToModelNames key =" << key << " val =" << hfmModel.meshIndicesToModelNames[key]; + for (int key : mitomona_keys) { + qCDebug(modelformat) << " meshIndicesToModelNames key =" << key + << " val =" << hfmModel.meshIndicesToModelNames[key]; } qCDebug(modelformat) << "---------------- Materials ----------------"; - foreach(HFMMaterial mat, hfmModel.materials) { + for (HFMMaterial mat : hfmModel.materials) { qCDebug(modelformat) << "\n"; qCDebug(modelformat) << " mat.materialID =" << mat.materialID; qCDebug(modelformat) << " diffuseColor =" << mat.diffuseColor; diff --git a/libraries/fbx/src/GLTFSerializer.h b/libraries/fbx/src/GLTFSerializer.h index 4d72805863..78dc9b9a37 100755 --- a/libraries/fbx/src/GLTFSerializer.h +++ b/libraries/fbx/src/GLTFSerializer.h @@ -38,14 +38,14 @@ struct GLTFAsset { struct GLTFNode { QString name; - int camera; - int mesh; + int camera{ -1 }; + int mesh{ -1 }; QVector children; QVector translation; QVector rotation; QVector scale; QVector matrix; - QVector transforms; + glm::mat4 transform; int skin; QVector skeletons; QString jointName; @@ -85,6 +85,8 @@ struct GLTFNode { qCDebug(modelformat) << "skeletons: " << skeletons; } } + + void normalizeTransform(); }; // Meshes @@ -460,15 +462,56 @@ struct GLTFMaterial { // Accesors namespace GLTFAccessorType { - enum Values { - SCALAR = 0, - VEC2, - VEC3, - VEC4, - MAT2, - MAT3, - MAT4 + enum Value { + SCALAR = 1, + VEC2 = 2, + VEC3 = 3, + VEC4 = 4, + MAT2 = 5, + MAT3 = 9, + MAT4 = 16 }; + + inline int count(Value value) { + if (value == MAT2) { + return 4; + } + return (int)value; + } +} + +namespace GLTFVertexAttribute { + enum Value { + UNKNOWN = -1, + POSITION = 0, + NORMAL, + TANGENT, + TEXCOORD_0, + TEXCOORD_1, + COLOR_0, + JOINTS_0, + WEIGHTS_0, + }; + inline Value fromString(const QString& key) { + if (key == "POSITION") { + return POSITION; + } else if (key == "NORMAL") { + return NORMAL; + } else if (key == "TANGENT") { + return TANGENT; + } else if (key == "TEXCOORD_0") { + return TEXCOORD_0; + } else if (key == "TEXCOORD_1") { + return TEXCOORD_1; + } else if (key == "COLOR_0") { + return COLOR_0; + } else if (key == "JOINTS_0") { + return JOINTS_0; + } else if (key == "WEIGHTS_0") { + return WEIGHTS_0; + } + return UNKNOWN; + } } namespace GLTFAccessorComponentType { enum Values { @@ -760,6 +803,13 @@ struct GLTFFile { foreach(auto tex, textures) tex.dump(); } } + + + void populateMaterialNames(); + void sortNodes(); + void normalizeNodeTransforms(); +private: + void reorderNodes(const std::unordered_map& reorderMap); }; class GLTFSerializer : public QObject, public HFMSerializer { @@ -774,7 +824,7 @@ private: hifi::URL _url; hifi::ByteArray _glbBinary; - glm::mat4 getModelTransform(const GLTFNode& node); + const glm::mat4& getModelTransform(const GLTFNode& node); void getSkinInverseBindMatrices(std::vector>& inverseBindMatrixValues); void generateTargetData(int index, float weight, QVector& returnVector); @@ -843,6 +893,9 @@ private: template bool addArrayFromAccessor(GLTFAccessor& accessor, QVector& outarray); + template + bool addArrayFromAttribute(GLTFVertexAttribute::Value vertexAttribute, GLTFAccessor& accessor, QVector& outarray); + void retriangulate(const QVector& in_indices, const QVector& in_vertices, const QVector& in_normals, QVector& out_indices, QVector& out_vertices, QVector& out_normals); diff --git a/libraries/hfm/src/hfm/HFM.h b/libraries/hfm/src/hfm/HFM.h index 8e0944db43..497bb60568 100644 --- a/libraries/hfm/src/hfm/HFM.h +++ b/libraries/hfm/src/hfm/HFM.h @@ -122,8 +122,7 @@ public: /// A single binding to a joint. class Cluster { public: - - int jointIndex; + uint32_t jointIndex; glm::mat4 inverseBindMatrix; Transform inverseBindTransform; }; @@ -289,7 +288,8 @@ public: class TransformNode { public: - uint32_t parent { 0 }; + static const uint32_t INVALID_PARENT_INDEX{ (uint32_t)-1 }; + uint32_t parent { INVALID_PARENT_INDEX }; Transform transform; }; diff --git a/tests-manual/fbx/CMakeLists.txt b/tests-manual/fbx/CMakeLists.txt new file mode 100644 index 0000000000..7221f081fe --- /dev/null +++ b/tests-manual/fbx/CMakeLists.txt @@ -0,0 +1,11 @@ +set(TARGET_NAME fbx-test) +# This is not a testcase -- just set it up as a regular hifi project +setup_hifi_project(Quick Gui) +setup_memory_debugger() +set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") + +file(GLOB_RECURSE GLB_TEST_FILES "c:/Users/bdavi/git/glTF-Sample-Models/2.0/*.glb") +list(JOIN GLB_TEST_FILES "|" GLB_TEST_FILES) +target_compile_definitions(${TARGET_NAME} PRIVATE -DGLB_TEST_FILES="${GLB_TEST_FILES}") +link_hifi_libraries(shared graphics networking image gpu hfm fbx) +package_libraries_for_deployment() diff --git a/tests-manual/fbx/src/main.cpp b/tests-manual/fbx/src/main.cpp new file mode 100644 index 0000000000..66c3a4f30e --- /dev/null +++ b/tests-manual/fbx/src/main.cpp @@ -0,0 +1,77 @@ +// +// Created by Bradley Austin Davis on 2018/01/11 +// Copyright 2014 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 + +// Currently only used by testing code +inline std::list splitString(const std::string& source, const char delimiter = ' ') { + std::list result; + size_t start = 0, next; + + while (std::string::npos != (next = source.find(delimiter, start))) { + std::string sub = source.substr(start, next - start); + if (!sub.empty()) { + result.push_back(sub); + } + start = next + 1; + } + if (source.size() > start) { + result.push_back(source.substr(start)); + } + return result; +} + +std::list getGlbTestFiles() { + return splitString(GLB_TEST_FILES, '|'); +} + +QtMessageHandler originalHandler; + +void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { +#if defined(Q_OS_WIN) + OutputDebugStringA(message.toStdString().c_str()); + OutputDebugStringA("\n"); +#endif + originalHandler(type, context, message); +} + +QByteArray readFileBytes(const std::string& filename) { + QFile file(filename.c_str()); + file.open(QFile::ReadOnly); + QByteArray result = file.readAll(); + file.close(); + return result; +} + +void processFile(const std::string& filename) { + qDebug() << filename.c_str(); + GLTFSerializer().read(readFileBytes(filename), {}, QUrl::fromLocalFile(filename.c_str())); +} + +int main(int argc, char** argv) { + QCoreApplication app{ argc, argv }; + originalHandler = qInstallMessageHandler(messageHandler); + + DependencyManager::set(false); + + //processFile("c:/Users/bdavi/git/glTF-Sample-Models/2.0/Box/glTF-Binary/Box.glb"); + + for (const auto& testFile : getGlbTestFiles()) { + processFile(testFile); + } +}