diff --git a/libraries/model-baker/src/model-baker/Baker.cpp b/libraries/model-baker/src/model-baker/Baker.cpp index bf2b21993c..c15b2c9926 100644 --- a/libraries/model-baker/src/model-baker/Baker.cpp +++ b/libraries/model-baker/src/model-baker/Baker.cpp @@ -15,26 +15,64 @@ #include "BakerTypes.h" #include "BuildGraphicsMeshTask.h" +#include "CalculateMeshNormalsTask.h" +#include "CalculateMeshTangentsTask.h" +#include "CalculateBlendshapeNormalsTask.h" +#include "CalculateBlendshapeTangentsTask.h" namespace baker { class GetModelPartsTask { public: using Input = hfm::Model::Pointer; - using Output = VaryingSet3, hifi::URL, MeshIndicesToModelNames>; + using Output = VaryingSet4, hifi::URL, baker::MeshIndicesToModelNames, baker::BlendshapesPerMesh>; using JobModel = Job::ModelIO; void run(const BakeContextPointer& context, const Input& input, Output& output) { - auto& hfmModelIn = input; + const auto& hfmModelIn = input; output.edit0() = hfmModelIn->meshes.toStdVector(); output.edit1() = hfmModelIn->originalURL; output.edit2() = hfmModelIn->meshIndicesToModelNames; + auto& blendshapesPerMesh = output.edit3(); + blendshapesPerMesh.reserve(hfmModelIn->meshes.size()); + for (int i = 0; i < hfmModelIn->meshes.size(); i++) { + blendshapesPerMesh.push_back(hfmModelIn->meshes[i].blendshapes.toStdVector()); + } + } + }; + + class BuildBlendshapesTask { + public: + using Input = VaryingSet3, std::vector>; + using Output = BlendshapesPerMesh; + using JobModel = Job::ModelIO; + + void run(const BakeContextPointer& context, const Input& input, Output& output) { + const auto& blendshapesPerMeshIn = input.get0(); + const auto& normalsPerBlendshapePerMesh = input.get1(); + const auto& tangentsPerBlendshapePerMesh = input.get2(); + auto& blendshapesPerMeshOut = output; + + blendshapesPerMeshOut = blendshapesPerMeshIn; + + for (int i = 0; i < blendshapesPerMeshOut.size(); i++) { + const auto& normalsPerBlendshape = normalsPerBlendshapePerMesh[i]; + const auto& tangentsPerBlendshape = tangentsPerBlendshapePerMesh[i]; + auto& blendshapesOut = blendshapesPerMeshOut[i]; + for (int j = 0; j < blendshapesOut.size(); j++) { + const auto& normals = normalsPerBlendshape[j]; + const auto& tangents = tangentsPerBlendshape[j]; + auto& blendshape = blendshapesOut[j]; + blendshape.normals = QVector::fromStdVector(normals); + blendshape.tangents = QVector::fromStdVector(tangents); + } + } } }; class BuildMeshesTask { public: - using Input = VaryingSet4, std::vector, TangentsPerMesh, BlendshapesPerMesh>; + using Input = VaryingSet5, std::vector, NormalsPerMesh, TangentsPerMesh, BlendshapesPerMesh>; using Output = std::vector; using JobModel = Job::ModelIO; @@ -42,13 +80,15 @@ namespace baker { auto& meshesIn = input.get0(); int numMeshes = (int)meshesIn.size(); auto& graphicsMeshesIn = input.get1(); - auto& tangentsPerMeshIn = input.get2(); - auto& blendshapesPerMeshIn = input.get3(); + auto& normalsPerMeshIn = input.get2(); + auto& tangentsPerMeshIn = input.get3(); + auto& blendshapesPerMeshIn = input.get4(); auto meshesOut = meshesIn; for (int i = 0; i < numMeshes; i++) { auto& meshOut = meshesOut[i]; meshOut._mesh = graphicsMeshesIn[i]; + meshOut.normals = QVector::fromStdVector(normalsPerMeshIn[i]); meshOut.tangents = QVector::fromStdVector(tangentsPerMeshIn[i]); meshOut.blendshapes = QVector::fromStdVector(blendshapesPerMeshIn[i]); } @@ -80,17 +120,25 @@ namespace baker { const auto meshesIn = modelPartsIn.getN(0); const auto url = modelPartsIn.getN(1); const auto meshIndicesToModelNames = modelPartsIn.getN(2); + const auto blendshapesPerMeshIn = modelPartsIn.getN(3); + + // Calculate normals and tangents for meshes and blendshapes if they do not exist + const auto normalsPerMesh = model.addJob("CalculateMeshNormals", meshesIn); + const auto calculateMeshTangentsInputs = CalculateMeshTangentsTask::Input(normalsPerMesh, meshesIn, hfmModelIn).asVarying(); + const auto tangentsPerMesh = model.addJob("CalculateMeshTangents", calculateMeshTangentsInputs); + const auto calculateBlendshapeNormalsInputs = CalculateBlendshapeNormalsTask::Input(blendshapesPerMeshIn, meshesIn).asVarying(); + const auto normalsPerBlendshapePerMesh = model.addJob("CalculateBlendshapeNormals", calculateBlendshapeNormalsInputs); + const auto calculateBlendshapeTangentsInputs = CalculateBlendshapeTangentsTask::Input(normalsPerBlendshapePerMesh, blendshapesPerMeshIn, meshesIn, hfmModelIn).asVarying(); + const auto tangentsPerBlendshapePerMesh = model.addJob("CalculateBlendshapeTangents", calculateBlendshapeTangentsInputs); // Build the graphics::MeshPointer for each hfm::Mesh - const auto buildGraphicsMeshInputs = BuildGraphicsMeshTask::Input(meshesIn, url, meshIndicesToModelNames).asVarying(); - const auto buildGraphicsMeshOutputs = model.addJob("BuildGraphicsMesh", buildGraphicsMeshInputs); - const auto graphicsMeshes = buildGraphicsMeshOutputs.getN(0); - // TODO: Move tangent/blendshape validation/calculation to an earlier step - const auto tangentsPerMesh = buildGraphicsMeshOutputs.getN(1); - const auto blendshapesPerMesh = buildGraphicsMeshOutputs.getN(2); + const auto buildGraphicsMeshInputs = BuildGraphicsMeshTask::Input(meshesIn, url, meshIndicesToModelNames, normalsPerMesh, tangentsPerMesh).asVarying(); + const auto graphicsMeshes = model.addJob("BuildGraphicsMesh", buildGraphicsMeshInputs); // Combine the outputs into a new hfm::Model - const auto buildMeshesInputs = BuildMeshesTask::Input(meshesIn, graphicsMeshes, tangentsPerMesh, blendshapesPerMesh).asVarying(); + const auto buildBlendshapesInputs = BuildBlendshapesTask::Input(blendshapesPerMeshIn, normalsPerBlendshapePerMesh, tangentsPerBlendshapePerMesh).asVarying(); + const auto blendshapesPerMeshOut = model.addJob("BuildBlendshapes", buildBlendshapesInputs); + const auto buildMeshesInputs = BuildMeshesTask::Input(meshesIn, graphicsMeshes, normalsPerMesh, tangentsPerMesh, blendshapesPerMeshOut).asVarying(); const auto meshesOut = model.addJob("BuildMeshes", buildMeshesInputs); const auto buildModelInputs = BuildModelTask::Input(hfmModelIn, meshesOut).asVarying(); hfmModelOut = model.addJob("BuildModel", buildModelInputs); diff --git a/libraries/model-baker/src/model-baker/BakerTypes.h b/libraries/model-baker/src/model-baker/BakerTypes.h index 8615c1b17c..5d14ee5420 100644 --- a/libraries/model-baker/src/model-baker/BakerTypes.h +++ b/libraries/model-baker/src/model-baker/BakerTypes.h @@ -15,10 +15,25 @@ #include namespace baker { + using MeshIndices = std::vector; + using IndicesPerMesh = std::vector>; + using VerticesPerMesh = std::vector>; + using MeshNormals = std::vector; + using NormalsPerMesh = std::vector>; using MeshTangents = std::vector; using TangentsPerMesh = std::vector>; + using Blendshapes = std::vector; using BlendshapesPerMesh = std::vector>; + using BlendshapeVertices = std::vector; + using BlendshapeNormals = std::vector; + using BlendshapeIndices = std::vector; + using VerticesPerBlendshape = std::vector>; + using NormalsPerBlendshape = std::vector>; + using IndicesPerBlendshape = std::vector>; + using BlendshapeTangents = std::vector; + using TangentsPerBlendshape = std::vector>; + using MeshIndicesToModelNames = QHash; }; diff --git a/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.cpp b/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.cpp index 6d351a99e9..370add2c2e 100644 --- a/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.cpp +++ b/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.cpp @@ -26,9 +26,18 @@ glm::vec3 normalizeDirForPacking(const glm::vec3& dir) { return dir; } -void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphicsMeshPointer, baker::MeshTangents& meshTangents, baker::Blendshapes& blendshapes) { +void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphicsMeshPointer, const baker::MeshNormals& meshNormals, const baker::MeshTangents& meshTangentsIn) { auto graphicsMesh = std::make_shared(); + // Fill tangents with a dummy value to force tangents to be present if there are normals + baker::MeshTangents meshTangents; + if (!meshTangentsIn.empty()) { + meshTangents = meshTangentsIn; + } else { + meshTangents.reserve(meshNormals.size()); + std::fill_n(std::back_inserter(meshTangents), meshNormals.size(), Vectors::UNIT_X); + } + unsigned int totalSourceIndices = 0; foreach(const HFMMeshPart& part, hfmMesh.parts) { totalSourceIndices += (part.quadTrianglesIndices.size() + part.triangleIndices.size()); @@ -48,23 +57,6 @@ void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphics int numVerts = hfmMesh.vertices.size(); - if (!hfmMesh.normals.empty() && hfmMesh.tangents.empty()) { - // Fill with a dummy value to force tangents to be present if there are normals - meshTangents.reserve(hfmMesh.normals.size()); - std::fill_n(std::back_inserter(meshTangents), hfmMesh.normals.size(), Vectors::UNIT_X); - } else { - meshTangents = hfmMesh.tangents.toStdVector(); - } - // Same thing with blend shapes - blendshapes = hfmMesh.blendshapes.toStdVector(); - for (auto& blendShape : blendshapes) { - if (!blendShape.normals.empty() && blendShape.tangents.empty()) { - // Fill with a dummy value to force tangents to be present if there are normals - blendShape.tangents.reserve(blendShape.normals.size()); - std::fill_n(std::back_inserter(blendShape.tangents), blendShape.normals.size(), Vectors::UNIT_X); - } - } - // evaluate all attribute elements and data sizes // Position is a vec3 @@ -73,12 +65,12 @@ void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphics // Normal and tangent are always there together packed in normalized xyz32bits word (times 2) const auto normalElement = HFM_NORMAL_ELEMENT; - const int normalsSize = hfmMesh.normals.size() * normalElement.getSize(); + const int normalsSize = (int)meshNormals.size() * normalElement.getSize(); const int tangentsSize = (int)meshTangents.size() * normalElement.getSize(); // If there are normals then there should be tangents assert(normalsSize <= tangentsSize); if (tangentsSize > normalsSize) { - HIFI_FCDEBUG_ID(model_baker(), repeatMessageID, "BuildGraphicsMeshTask -- Unexpected tangents in file"); + HIFI_FCDEBUG_ID(model_baker(), repeatMessageID, "BuildGraphicsMeshTask -- Unexpected tangents in mesh"); } const auto normalsAndTangentsSize = normalsSize + tangentsSize; @@ -124,11 +116,11 @@ void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphics if (normalsSize > 0) { std::vector normalsAndTangents; - normalsAndTangents.reserve(hfmMesh.normals.size() + (int)meshTangents.size()); - auto normalIt = hfmMesh.normals.constBegin(); + normalsAndTangents.reserve(meshNormals.size() + (int)meshTangents.size()); + auto normalIt = meshNormals.cbegin(); auto tangentIt = meshTangents.cbegin(); for (; - normalIt != hfmMesh.normals.constEnd(); + normalIt != meshNormals.cend(); ++normalIt, ++tangentIt) { #if HFM_PACK_NORMALS const auto normal = normalizeDirForPacking(*normalIt); @@ -212,11 +204,6 @@ void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphics auto vertexFormat = std::make_shared(); auto vertexBufferStream = std::make_shared(); - // Decision time: - // if blendshapes then keep position and normals/tangents as separated channel buffers from interleaved attributes - // else everything is interleaved in one buffer - - // Default case is no blend shapes gpu::BufferPointer attribBuffer; int totalAttribBufferSize = totalVertsSize; gpu::uint8 posChannel = 0; @@ -244,7 +231,7 @@ void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphics } } - // Pack normal and Tangent with the rest of atributes if no blend shapes + // Pack normal and Tangent with the rest of atributes if (colorsSize) { vertexFormat->setAttribute(gpu::Stream::COLOR, attribChannel, colorElement, bufOffset); bufOffset += colorElement.getSize(); @@ -384,22 +371,21 @@ void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphics } void BuildGraphicsMeshTask::run(const baker::BakeContextPointer& context, const Input& input, Output& output) { - auto& meshes = input.get0(); - auto& url = input.get1(); - auto& meshIndicesToModelNames = input.get2(); + const auto& meshes = input.get0(); + const auto& url = input.get1(); + const auto& meshIndicesToModelNames = input.get2(); + const auto& normalsPerMesh = input.get3(); + const auto& tangentsPerMesh = input.get4(); + + auto& graphicsMeshes = output; - auto& graphicsMeshes = output.edit0(); - auto& tangentsPerMesh = output.edit1(); - auto& blendshapesPerMesh = output.edit2(); int n = (int)meshes.size(); for (int i = 0; i < n; i++) { graphicsMeshes.emplace_back(); auto& graphicsMesh = graphicsMeshes[i]; - tangentsPerMesh.emplace_back(); - blendshapesPerMesh.emplace_back(); // Try to create the graphics::Mesh - buildGraphicsMesh(meshes[i], graphicsMesh, tangentsPerMesh[i], blendshapesPerMesh[i]); + buildGraphicsMesh(meshes[i], graphicsMesh, normalsPerMesh[i], tangentsPerMesh[i]); // Choose a name for the mesh if (graphicsMesh) { diff --git a/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.h b/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.h index 3c8985ef9a..bb4136c086 100644 --- a/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.h +++ b/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.h @@ -20,8 +20,8 @@ class BuildGraphicsMeshTask { public: - using Input = baker::VaryingSet3, hifi::URL, baker::MeshIndicesToModelNames>; - using Output = baker::VaryingSet3, std::vector, std::vector>; + using Input = baker::VaryingSet5, hifi::URL, baker::MeshIndicesToModelNames, baker::NormalsPerMesh, baker::TangentsPerMesh>; + using Output = std::vector; using JobModel = baker::Job::ModelIO; void run(const baker::BakeContextPointer& context, const Input& input, Output& output); diff --git a/libraries/model-baker/src/model-baker/CalculateBlendshapeNormalsTask.cpp b/libraries/model-baker/src/model-baker/CalculateBlendshapeNormalsTask.cpp new file mode 100644 index 0000000000..b908fa9ced --- /dev/null +++ b/libraries/model-baker/src/model-baker/CalculateBlendshapeNormalsTask.cpp @@ -0,0 +1,70 @@ +// +// CalculateBlendshapeNormalsTask.h +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/01/07. +// Copyright 2019 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 "CalculateBlendshapeNormalsTask.h" + +#include "ModelMath.h" + +void CalculateBlendshapeNormalsTask::run(const baker::BakeContextPointer& context, const Input& input, Output& output) { + const auto& blendshapesPerMesh = input.get0(); + const auto& meshes = input.get1(); + auto& normalsPerBlendshapePerMeshOut = output; + + normalsPerBlendshapePerMeshOut.reserve(blendshapesPerMesh.size()); + for (size_t i = 0; i < blendshapesPerMesh.size(); i++) { + const auto& mesh = meshes[i]; + const auto& blendshapes = blendshapesPerMesh[i]; + normalsPerBlendshapePerMeshOut.emplace_back(); + auto& normalsPerBlendshapeOut = normalsPerBlendshapePerMeshOut[normalsPerBlendshapePerMeshOut.size()-1]; + + normalsPerBlendshapeOut.reserve(blendshapes.size()); + for (size_t j = 0; j < blendshapes.size(); j++) { + const auto& blendshape = blendshapes[j]; + const auto& normalsIn = blendshape.normals; + // Check if normals are already defined. Otherwise, calculate them from existing blendshape vertices. + if (!normalsIn.empty()) { + normalsPerBlendshapeOut.push_back(normalsIn.toStdVector()); + } else { + // Create lookup to get index in blendshape from vertex index in mesh + std::vector reverseIndices; + reverseIndices.resize(mesh.vertices.size()); + std::iota(reverseIndices.begin(), reverseIndices.end(), 0); + for (int indexInBlendShape = 0; indexInBlendShape < blendshape.indices.size(); ++indexInBlendShape) { + auto indexInMesh = blendshape.indices[indexInBlendShape]; + reverseIndices[indexInMesh] = indexInBlendShape; + } + + normalsPerBlendshapeOut.emplace_back(); + auto& normals = normalsPerBlendshapeOut[normalsPerBlendshapeOut.size()-1]; + normals.resize(mesh.vertices.size()); + baker::calculateNormals(mesh, + [&reverseIndices, &blendshape, &normals](int normalIndex) /* NormalAccessor */ { + const auto lookupIndex = reverseIndices[normalIndex]; + if (lookupIndex < blendshape.vertices.size()) { + return &normals[lookupIndex]; + } else { + // Index isn't in the blendshape. Request that the normal not be calculated. + return (glm::vec3*)nullptr; + } + }, + [&mesh, &reverseIndices, &blendshape](int vertexIndex, glm::vec3& outVertex) /* VertexSetter */ { + const auto lookupIndex = reverseIndices[vertexIndex]; + if (lookupIndex < blendshape.vertices.size()) { + outVertex = blendshape.vertices[lookupIndex]; + } else { + // Index isn't in the blendshape, so return vertex from mesh + outVertex = mesh.vertices[lookupIndex]; + } + }); + } + } + } +} diff --git a/libraries/model-baker/src/model-baker/CalculateBlendshapeNormalsTask.h b/libraries/model-baker/src/model-baker/CalculateBlendshapeNormalsTask.h new file mode 100644 index 0000000000..0eb7d3c4e7 --- /dev/null +++ b/libraries/model-baker/src/model-baker/CalculateBlendshapeNormalsTask.h @@ -0,0 +1,28 @@ +// +// CalculateBlendshapeNormalsTask.h +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/01/07. +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_CalculateBlendshapeNormalsTask_h +#define hifi_CalculateBlendshapeNormalsTask_h + +#include "Engine.h" +#include "BakerTypes.h" + +// Calculate blendshape normals if not already present in the blendshape +class CalculateBlendshapeNormalsTask { +public: + using Input = baker::VaryingSet2>; + using Output = std::vector; + using JobModel = baker::Job::ModelIO; + + void run(const baker::BakeContextPointer& context, const Input& input, Output& output); +}; + +#endif // hifi_CalculateBlendshapeNormalsTask_h diff --git a/libraries/model-baker/src/model-baker/CalculateBlendshapeTangentsTask.cpp b/libraries/model-baker/src/model-baker/CalculateBlendshapeTangentsTask.cpp new file mode 100644 index 0000000000..66e1dadc41 --- /dev/null +++ b/libraries/model-baker/src/model-baker/CalculateBlendshapeTangentsTask.cpp @@ -0,0 +1,95 @@ +// +// CalculateBlendshapeTangentsTask.cpp +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/01/08. +// Copyright 2019 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 "CalculateBlendshapeTangentsTask.h" + +#include + +#include "ModelMath.h" + +void CalculateBlendshapeTangentsTask::run(const baker::BakeContextPointer& context, const Input& input, Output& output) { + const auto& normalsPerBlendshapePerMesh = input.get0(); + const auto& blendshapesPerMesh = input.get1(); + const auto& meshes = input.get2(); + const hfm::Model& model = *input.get3(); + auto& tangentsPerBlendshapePerMeshOut = output; + + tangentsPerBlendshapePerMeshOut.reserve(normalsPerBlendshapePerMesh.size()); + for (size_t i = 0; i < blendshapesPerMesh.size(); i++) { + const auto& normalsPerBlendshape = normalsPerBlendshapePerMesh[i]; + const auto& blendshapes = blendshapesPerMesh[i]; + const auto& mesh = meshes[i]; + tangentsPerBlendshapePerMeshOut.emplace_back(); + auto& tangentsPerBlendshapeOut = tangentsPerBlendshapePerMeshOut[tangentsPerBlendshapePerMeshOut.size()-1]; + + // Check if we actually need to calculate the tangents, or just append empty arrays + bool needTangents = false; + for (const auto& meshPart : mesh.parts) { + auto materialIt = model.materials.find(meshPart.materialID); + if (materialIt != model.materials.end() && (*materialIt).needTangentSpace()) { + needTangents = true; + break; + } + } + + for (size_t j = 0; j < blendshapes.size(); j++) { + const auto& blendshape = blendshapes[j]; + const auto& tangentsIn = blendshape.tangents; + const auto& normals = normalsPerBlendshape[j]; + tangentsPerBlendshapeOut.emplace_back(); + auto& tangentsOut = tangentsPerBlendshapeOut[tangentsPerBlendshapeOut.size()-1]; + + // Check if we already have tangents + if (!tangentsIn.empty()) { + tangentsOut = tangentsIn.toStdVector(); + continue; + } + + // Check if we can and should calculate tangents (we need normals to calculate the tangents) + if (normals.empty() || !needTangents) { + continue; + } + tangentsOut.resize(normals.size()); + + // Create lookup to get index in blend shape from vertex index in mesh + std::vector reverseIndices; + reverseIndices.resize(mesh.vertices.size()); + std::iota(reverseIndices.begin(), reverseIndices.end(), 0); + for (int indexInBlendShape = 0; indexInBlendShape < blendshape.indices.size(); ++indexInBlendShape) { + auto indexInMesh = blendshape.indices[indexInBlendShape]; + reverseIndices[indexInMesh] = indexInBlendShape; + } + + baker::calculateTangents(mesh, + [&mesh, &blendshape, &normals, &tangentsOut, &reverseIndices](int firstIndex, int secondIndex, glm::vec3* outVertices, glm::vec2* outTexCoords, glm::vec3& outNormal) { + const auto index1 = reverseIndices[firstIndex]; + const auto index2 = reverseIndices[secondIndex]; + + if (index1 < blendshape.vertices.size()) { + outVertices[0] = blendshape.vertices[index1]; + outTexCoords[0] = mesh.texCoords[index1]; + outTexCoords[1] = mesh.texCoords[index2]; + if (index2 < blendshape.vertices.size()) { + outVertices[1] = blendshape.vertices[index2]; + } else { + // Index isn't in the blend shape so return vertex from mesh + outVertices[1] = mesh.vertices[secondIndex]; + } + outNormal = normals[index1]; + return &tangentsOut[index1]; + } else { + // Index isn't in blend shape so return nullptr + return (glm::vec3*)nullptr; + } + }); + } + } +} diff --git a/libraries/model-baker/src/model-baker/CalculateBlendshapeTangentsTask.h b/libraries/model-baker/src/model-baker/CalculateBlendshapeTangentsTask.h new file mode 100644 index 0000000000..9ff79d17a6 --- /dev/null +++ b/libraries/model-baker/src/model-baker/CalculateBlendshapeTangentsTask.h @@ -0,0 +1,28 @@ +// +// CalculateBlendshapeTangentsTask.h +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/01/07. +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_CalculateBlendshapeTangentsTask_h +#define hifi_CalculateBlendshapeTangentsTask_h + +#include "Engine.h" +#include "BakerTypes.h" + +// Calculate blendshape tangents if not already present in the blendshape +class CalculateBlendshapeTangentsTask { +public: + using Input = baker::VaryingSet4, baker::BlendshapesPerMesh, std::vector, hfm::Model::Pointer>; + using Output = std::vector; + using JobModel = baker::Job::ModelIO; + + void run(const baker::BakeContextPointer& context, const Input& input, Output& output); +}; + +#endif // hifi_CalculateBlendshapeTangentsTask_h diff --git a/libraries/model-baker/src/model-baker/CalculateMeshNormalsTask.cpp b/libraries/model-baker/src/model-baker/CalculateMeshNormalsTask.cpp new file mode 100644 index 0000000000..02986e160e --- /dev/null +++ b/libraries/model-baker/src/model-baker/CalculateMeshNormalsTask.cpp @@ -0,0 +1,40 @@ +// +// CalculateMeshNormalsTask.cpp +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/01/22. +// Copyright 2019 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 "CalculateMeshNormalsTask.h" + +#include "ModelMath.h" + +void CalculateMeshNormalsTask::run(const baker::BakeContextPointer& context, const Input& input, Output& output) { + const auto& meshes = input; + auto& normalsPerMeshOut = output; + + normalsPerMeshOut.reserve(meshes.size()); + for (int i = 0; i < meshes.size(); i++) { + const auto& mesh = meshes[i]; + normalsPerMeshOut.emplace_back(); + auto& normalsOut = normalsPerMeshOut[normalsPerMeshOut.size()-1]; + // Only calculate normals if this mesh doesn't already have them + if (!mesh.normals.empty()) { + normalsOut = mesh.normals.toStdVector(); + } else { + normalsOut.resize(mesh.vertices.size()); + baker::calculateNormals(mesh, + [&normalsOut](int normalIndex) /* NormalAccessor */ { + return &normalsOut[normalIndex]; + }, + [&mesh](int vertexIndex, glm::vec3& outVertex) /* VertexSetter */ { + outVertex = mesh.vertices[vertexIndex]; + } + ); + } + } +} diff --git a/libraries/model-baker/src/model-baker/CalculateMeshNormalsTask.h b/libraries/model-baker/src/model-baker/CalculateMeshNormalsTask.h new file mode 100644 index 0000000000..b32780c68d --- /dev/null +++ b/libraries/model-baker/src/model-baker/CalculateMeshNormalsTask.h @@ -0,0 +1,30 @@ +// +// CalculateMeshNormalsTask.h +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/01/07. +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_CalculateMeshNormalsTask_h +#define hifi_CalculateMeshNormalsTask_h + +#include + +#include "Engine.h" +#include "BakerTypes.h" + +// Calculate mesh normals if not already present in the mesh +class CalculateMeshNormalsTask { +public: + using Input = std::vector; + using Output = baker::NormalsPerMesh; + using JobModel = baker::Job::ModelIO; + + void run(const baker::BakeContextPointer& context, const Input& input, Output& output); +}; + +#endif // hifi_CalculateMeshNormalsTask_h diff --git a/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.cpp b/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.cpp new file mode 100644 index 0000000000..96cf4ddc5c --- /dev/null +++ b/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.cpp @@ -0,0 +1,66 @@ +// +// CalculateMeshTangentsTask.cpp +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/01/22. +// Copyright 2019 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 "CalculateMeshTangentsTask.h" + +#include "ModelMath.h" + +void CalculateMeshTangentsTask::run(const baker::BakeContextPointer& context, const Input& input, Output& output) { + const auto& normalsPerMesh = input.get0(); + const std::vector& meshes = input.get1(); + const hfm::Model& model = *input.get2(); + auto& tangentsPerMeshOut = output; + + tangentsPerMeshOut.reserve(meshes.size()); + for (int i = 0; i < meshes.size(); i++) { + const auto& mesh = meshes[i]; + const auto& tangentsIn = mesh.tangents; + const auto& normals = normalsPerMesh[i]; + const auto& vertices = mesh.vertices; + tangentsPerMeshOut.emplace_back(); + auto& tangentsOut = tangentsPerMeshOut[tangentsPerMeshOut.size()-1]; + + // Check if we already have tangents and therefore do not need to do any calculation + if (!tangentsIn.empty()) { + tangentsOut = tangentsIn.toStdVector(); + continue; + } + + // Check if we have normals, and if not then tangents can't be calculated + if (normals.empty()) { + continue; + } + + // Check if we actually need to calculate the tangents + bool needTangents = false; + for (const auto& meshPart : mesh.parts) { + auto materialIt = model.materials.find(meshPart.materialID); + if (materialIt != model.materials.end() && (*materialIt).needTangentSpace()) { + needTangents = true; + break; + } + } + if (needTangents) { + continue; + } + + tangentsOut.resize(normals.size()); + baker::calculateTangents(mesh, + [&mesh, &normals, &tangentsOut](int firstIndex, int secondIndex, glm::vec3* outVertices, glm::vec2* outTexCoords, glm::vec3& outNormal) { + outVertices[0] = mesh.vertices[firstIndex]; + outVertices[1] = mesh.vertices[secondIndex]; + outNormal = normals[firstIndex]; + outTexCoords[0] = mesh.texCoords[firstIndex]; + outTexCoords[1] = mesh.texCoords[secondIndex]; + return &(tangentsOut[firstIndex]); + }); + } +} diff --git a/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.h b/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.h new file mode 100644 index 0000000000..1d0d572b28 --- /dev/null +++ b/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.h @@ -0,0 +1,32 @@ +// +// CalculateMeshTangentsTask.h +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/01/07. +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_CalculateMeshTangentsTask_h +#define hifi_CalculateMeshTangentsTask_h + +#include + +#include "Engine.h" +#include "BakerTypes.h" + +// Calculate mesh tangents if not already present in the mesh +class CalculateMeshTangentsTask { +public: + using NormalsPerMesh = std::vector>; + + using Input = baker::VaryingSet3, hfm::Model::Pointer>; + using Output = baker::TangentsPerMesh; + using JobModel = baker::Job::ModelIO; + + void run(const baker::BakeContextPointer& context, const Input& input, Output& output); +}; + +#endif // hifi_CalculateMeshTangentsTask_h diff --git a/libraries/model-baker/src/model-baker/ModelMath.cpp b/libraries/model-baker/src/model-baker/ModelMath.cpp new file mode 100644 index 0000000000..50e7e89cf4 --- /dev/null +++ b/libraries/model-baker/src/model-baker/ModelMath.cpp @@ -0,0 +1,121 @@ +// +// ModelMath.cpp +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/01/18. +// Copyright 2019 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 "ModelMath.h" + +#include +#include "ModelBakerLogging.h" + +namespace baker { + template + const T& checkedAt(const QVector& vector, int i) { + if (i < 0 || i >= vector.size()) { + throw std::out_of_range("baker::checked_at (ModelMath.cpp): index " + std::to_string(i) + " is out of range"); + } + return vector[i]; + } + + template + const T& checkedAt(const std::vector& vector, int i) { + if (i < 0 || i >= vector.size()) { + throw std::out_of_range("baker::checked_at (ModelMath.cpp): index " + std::to_string(i) + " is out of range"); + } + return vector[i]; + } + + template + T& checkedAt(std::vector& vector, int i) { + if (i < 0 || i >= vector.size()) { + throw std::out_of_range("baker::checked_at (ModelMath.cpp): index " + std::to_string(i) + " is out of range"); + } + return vector[i]; + } + + void setTangent(const HFMMesh& mesh, const IndexAccessor& vertexAccessor, int firstIndex, int secondIndex) { + glm::vec3 vertex[2]; + glm::vec2 texCoords[2]; + glm::vec3 normal; + glm::vec3* tangent = vertexAccessor(firstIndex, secondIndex, vertex, texCoords, normal); + if (tangent) { + glm::vec3 bitangent = glm::cross(normal, vertex[1] - vertex[0]); + if (glm::length(bitangent) < EPSILON) { + return; + } + glm::vec2 texCoordDelta = texCoords[1] - texCoords[0]; + glm::vec3 normalizedNormal = glm::normalize(normal); + *tangent += glm::cross(glm::angleAxis(-atan2f(-texCoordDelta.t, texCoordDelta.s), normalizedNormal) * + glm::normalize(bitangent), normalizedNormal); + } + } + + void calculateNormals(const hfm::Mesh& mesh, NormalAccessor normalAccessor, VertexSetter vertexSetter) { + static int repeatMessageID = LogHandler::getInstance().newRepeatedMessageID(); + for (const HFMMeshPart& part : mesh.parts) { + for (int i = 0; i < part.quadIndices.size(); i += 4) { + glm::vec3* n0 = normalAccessor(part.quadIndices[i]); + glm::vec3* n1 = normalAccessor(part.quadIndices[i + 1]); + glm::vec3* n2 = normalAccessor(part.quadIndices[i + 2]); + glm::vec3* n3 = normalAccessor(part.quadIndices[i + 3]); + if (!n0 || !n1 || !n2 || !n3) { + // Quad is not in the mesh (can occur with blendshape meshes, which are a subset of the hfm Mesh vertices) + continue; + } + glm::vec3 vertices[3]; // Assume all vertices in this quad are in the same plane, so only the first three are needed to calculate the normal + vertexSetter(part.quadIndices[i], vertices[0]); + vertexSetter(part.quadIndices[i + 1], vertices[1]); + vertexSetter(part.quadIndices[i + 2], vertices[2]); + *n0 = *n1 = *n2 = *n3 = glm::cross(vertices[1] - vertices[0], vertices[2] - vertices[0]); + } + // <= size - 3 in order to prevent overflowing triangleIndices when (i % 3) != 0 + // This is most likely evidence of a further problem in extractMesh() + for (int i = 0; i <= part.triangleIndices.size() - 3; i += 3) { + glm::vec3* n0 = normalAccessor(part.triangleIndices[i]); + glm::vec3* n1 = normalAccessor(part.triangleIndices[i + 1]); + glm::vec3* n2 = normalAccessor(part.triangleIndices[i + 2]); + if (!n0 || !n1 || !n2) { + // Tri is not in the mesh (can occur with blendshape meshes, which are a subset of the hfm Mesh vertices) + continue; + } + glm::vec3 vertices[3]; + vertexSetter(part.triangleIndices[i], vertices[0]); + vertexSetter(part.triangleIndices[i + 1], vertices[1]); + vertexSetter(part.triangleIndices[i + 2], vertices[2]); + *n0 = *n1 = *n2 = glm::cross(vertices[1] - vertices[0], vertices[2] - vertices[0]); + } + if ((part.triangleIndices.size() % 3) != 0) { + HIFI_FCDEBUG_ID(model_baker(), repeatMessageID, "Error in baker::calculateNormals: part.triangleIndices.size() is not divisible by three"); + } + } + } + + void calculateTangents(const hfm::Mesh& mesh, IndexAccessor accessor) { + static int repeatMessageID = LogHandler::getInstance().newRepeatedMessageID(); + for (const HFMMeshPart& part : mesh.parts) { + for (int i = 0; i < part.quadIndices.size(); i += 4) { + setTangent(mesh, accessor, part.quadIndices.at(i), part.quadIndices.at(i + 1)); + setTangent(mesh, accessor, part.quadIndices.at(i + 1), part.quadIndices.at(i + 2)); + setTangent(mesh, accessor, part.quadIndices.at(i + 2), part.quadIndices.at(i + 3)); + setTangent(mesh, accessor, part.quadIndices.at(i + 3), part.quadIndices.at(i)); + } + // <= size - 3 in order to prevent overflowing triangleIndices when (i % 3) != 0 + // This is most likely evidence of a further problem in extractMesh() + for (int i = 0; i <= part.triangleIndices.size() - 3; i += 3) { + setTangent(mesh, accessor, part.triangleIndices.at(i), part.triangleIndices.at(i + 1)); + setTangent(mesh, accessor, part.triangleIndices.at(i + 1), part.triangleIndices.at(i + 2)); + setTangent(mesh, accessor, part.triangleIndices.at(i + 2), part.triangleIndices.at(i)); + } + if ((part.triangleIndices.size() % 3) != 0) { + HIFI_FCDEBUG_ID(model_baker(), repeatMessageID, "Error in baker::calculateTangents: part.triangleIndices.size() is not divisible by three"); + } + } + } +} + diff --git a/libraries/model-baker/src/model-baker/ModelMath.h b/libraries/model-baker/src/model-baker/ModelMath.h new file mode 100644 index 0000000000..2a909e6eed --- /dev/null +++ b/libraries/model-baker/src/model-baker/ModelMath.h @@ -0,0 +1,34 @@ +// +// ModelMath.h +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/01/07. +// Copyright 2019 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 "BakerTypes.h" + +namespace baker { + // Returns a reference to the normal at the specified index, or nullptr if it cannot be accessed + using NormalAccessor = std::function; + + // Assigns a vertex to outVertex given the lookup index + using VertexSetter = std::function; + + void calculateNormals(const hfm::Mesh& mesh, NormalAccessor normalAccessor, VertexSetter vertexAccessor); + + // firstIndex, secondIndex: the vertex indices to be used for calculation + // outVertices: should be assigned a 2 element array containing the vertices at firstIndex and secondIndex + // outTexCoords: same as outVertices but for texture coordinates + // outNormal: reference to the normal of this triangle + // + // Return value: pointer to the tangent you want to be calculated + using IndexAccessor = std::function; + + void calculateTangents(const hfm::Mesh& mesh, IndexAccessor accessor); +};