Merge pull request #14770 from sabrina-shanman/hfm_prep_tangents

(case 20822) Add normal/tangent generation to HFM preparation step
This commit is contained in:
Jeff Clinton 2019-01-28 12:41:55 -08:00 committed by GitHub
commit 9b09c09aeb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 648 additions and 163 deletions

View file

@ -1480,9 +1480,6 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
}
}
extracted.mesh.createMeshTangents(generateTangents);
extracted.mesh.createBlendShapeTangents(generateTangents);
// find the clusters with which the mesh is associated
QVector<QString> clusterIDs;
foreach (const QString& childID, _connectionChildMap.values(it.key())) {

View file

@ -67,108 +67,6 @@ bool HFMMaterial::needTangentSpace() const {
return !normalTexture.isNull();
}
static void _createBlendShapeTangents(HFMMesh& mesh, bool generateFromTexCoords, HFMBlendshape& blendShape);
void HFMMesh::createBlendShapeTangents(bool generateTangents) {
for (auto& blendShape : blendshapes) {
_createBlendShapeTangents(*this, generateTangents, blendShape);
}
}
using IndexAccessor = std::function<glm::vec3*(const HFMMesh&, int, int, glm::vec3*, glm::vec3&)>;
static void setTangents(const HFMMesh& mesh, const IndexAccessor& vertexAccessor, int firstIndex, int secondIndex,
const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& normals, QVector<glm::vec3>& tangents) {
glm::vec3 vertex[2];
glm::vec3 normal;
glm::vec3* tangent = vertexAccessor(mesh, firstIndex, secondIndex, vertex, normal);
if (tangent) {
glm::vec3 bitangent = glm::cross(normal, vertex[1] - vertex[0]);
if (glm::length(bitangent) < EPSILON) {
return;
}
glm::vec2 texCoordDelta = mesh.texCoords.at(secondIndex) - mesh.texCoords.at(firstIndex);
glm::vec3 normalizedNormal = glm::normalize(normal);
*tangent += glm::cross(glm::angleAxis(-atan2f(-texCoordDelta.t, texCoordDelta.s), normalizedNormal) *
glm::normalize(bitangent), normalizedNormal);
}
}
static void createTangents(const HFMMesh& mesh, bool generateFromTexCoords,
const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& normals, QVector<glm::vec3>& tangents,
IndexAccessor accessor) {
// if we have a normal map (and texture coordinates), we must compute tangents
if (generateFromTexCoords && !mesh.texCoords.isEmpty()) {
tangents.resize(vertices.size());
foreach(const HFMMeshPart& part, mesh.parts) {
for (int i = 0; i < part.quadIndices.size(); i += 4) {
setTangents(mesh, accessor, part.quadIndices.at(i), part.quadIndices.at(i + 1), vertices, normals, tangents);
setTangents(mesh, accessor, part.quadIndices.at(i + 1), part.quadIndices.at(i + 2), vertices, normals, tangents);
setTangents(mesh, accessor, part.quadIndices.at(i + 2), part.quadIndices.at(i + 3), vertices, normals, tangents);
setTangents(mesh, accessor, part.quadIndices.at(i + 3), part.quadIndices.at(i), vertices, normals, tangents);
}
// <= 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) {
setTangents(mesh, accessor, part.triangleIndices.at(i), part.triangleIndices.at(i + 1), vertices, normals, tangents);
setTangents(mesh, accessor, part.triangleIndices.at(i + 1), part.triangleIndices.at(i + 2), vertices, normals, tangents);
setTangents(mesh, accessor, part.triangleIndices.at(i + 2), part.triangleIndices.at(i), vertices, normals, tangents);
}
if ((part.triangleIndices.size() % 3) != 0) {
qCDebug(modelformat) << "Error in extractHFMModel part.triangleIndices.size() is not divisible by three ";
}
}
}
}
void HFMMesh::createMeshTangents(bool generateFromTexCoords) {
HFMMesh& mesh = *this;
// This is the only workaround I've found to trick the compiler into understanding that mesh.tangents isn't
// const in the lambda function.
auto& tangents = mesh.tangents;
createTangents(mesh, generateFromTexCoords, mesh.vertices, mesh.normals, mesh.tangents,
[&](const HFMMesh& mesh, int firstIndex, int secondIndex, glm::vec3* outVertices, glm::vec3& outNormal) {
outVertices[0] = mesh.vertices[firstIndex];
outVertices[1] = mesh.vertices[secondIndex];
outNormal = mesh.normals[firstIndex];
return &(tangents[firstIndex]);
});
}
static void _createBlendShapeTangents(HFMMesh& mesh, bool generateFromTexCoords, HFMBlendshape& blendShape) {
// Create lookup to get index in blend shape from vertex index in mesh
std::vector<int> 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;
}
createTangents(mesh, generateFromTexCoords, blendShape.vertices, blendShape.normals, blendShape.tangents,
[&](const HFMMesh& mesh, int firstIndex, int secondIndex, glm::vec3* outVertices, glm::vec3& outNormal) {
const auto index1 = reverseIndices[firstIndex];
const auto index2 = reverseIndices[secondIndex];
if (index1 < blendShape.vertices.size()) {
outVertices[0] = blendShape.vertices[index1];
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 = blendShape.normals[index1];
return &blendShape.tangents[index1];
} else {
// Index isn't in blend shape so return nullptr
return (glm::vec3*)nullptr;
}
});
}
QStringList HFMModel::getJointNames() const {
QStringList names;
foreach (const HFMJoint& joint, joints) {

View file

@ -239,9 +239,6 @@ public:
graphics::MeshPointer _mesh;
bool wasCompressed { false };
void createMeshTangents(bool generateFromTexCoords);
void createBlendShapeTangents(bool generateTangents);
};
/**jsdoc

View file

@ -1,6 +1,4 @@
set(TARGET_NAME model-baker)
setup_hifi_library()
link_hifi_libraries(shared task gpu graphics)
include_hifi_library_headers(hfm)
link_hifi_libraries(shared task gpu graphics hfm)

View file

@ -15,26 +15,65 @@
#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<std::vector<hfm::Mesh>, hifi::URL, MeshIndicesToModelNames>;
using Output = VaryingSet5<std::vector<hfm::Mesh>, hifi::URL, baker::MeshIndicesToModelNames, baker::BlendshapesPerMesh, QHash<QString, hfm::Material>>;
using JobModel = Job::ModelIO<GetModelPartsTask, Input, Output>;
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());
}
output.edit4() = hfmModelIn->materials;
}
};
class BuildBlendshapesTask {
public:
using Input = VaryingSet3<BlendshapesPerMesh, std::vector<NormalsPerBlendshape>, std::vector<TangentsPerBlendshape>>;
using Output = BlendshapesPerMesh;
using JobModel = Job::ModelIO<BuildBlendshapesTask, Input, Output>;
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 < (int)blendshapesPerMeshOut.size(); i++) {
const auto& normalsPerBlendshape = normalsPerBlendshapePerMesh[i];
const auto& tangentsPerBlendshape = tangentsPerBlendshapePerMesh[i];
auto& blendshapesOut = blendshapesPerMeshOut[i];
for (int j = 0; j < (int)blendshapesOut.size(); j++) {
const auto& normals = normalsPerBlendshape[j];
const auto& tangents = tangentsPerBlendshape[j];
auto& blendshape = blendshapesOut[j];
blendshape.normals = QVector<glm::vec3>::fromStdVector(normals);
blendshape.tangents = QVector<glm::vec3>::fromStdVector(tangents);
}
}
}
};
class BuildMeshesTask {
public:
using Input = VaryingSet4<std::vector<hfm::Mesh>, std::vector<graphics::MeshPointer>, TangentsPerMesh, BlendshapesPerMesh>;
using Input = VaryingSet5<std::vector<hfm::Mesh>, std::vector<graphics::MeshPointer>, NormalsPerMesh, TangentsPerMesh, BlendshapesPerMesh>;
using Output = std::vector<hfm::Mesh>;
using JobModel = Job::ModelIO<BuildMeshesTask, Input, Output>;
@ -42,13 +81,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<glm::vec3>::fromStdVector(normalsPerMeshIn[i]);
meshOut.tangents = QVector<glm::vec3>::fromStdVector(tangentsPerMeshIn[i]);
meshOut.blendshapes = QVector<hfm::Blendshape>::fromStdVector(blendshapesPerMeshIn[i]);
}
@ -80,17 +121,27 @@ namespace baker {
const auto meshesIn = modelPartsIn.getN<GetModelPartsTask::Output>(0);
const auto url = modelPartsIn.getN<GetModelPartsTask::Output>(1);
const auto meshIndicesToModelNames = modelPartsIn.getN<GetModelPartsTask::Output>(2);
const auto blendshapesPerMeshIn = modelPartsIn.getN<GetModelPartsTask::Output>(3);
const auto materials = modelPartsIn.getN<GetModelPartsTask::Output>(4);
// Calculate normals and tangents for meshes and blendshapes if they do not exist
// Note: Normals are never calculated here for OBJ models. OBJ files optionally define normals on a per-face basis, so for consistency normals are calculated beforehand in OBJSerializer.
const auto normalsPerMesh = model.addJob<CalculateMeshNormalsTask>("CalculateMeshNormals", meshesIn);
const auto calculateMeshTangentsInputs = CalculateMeshTangentsTask::Input(normalsPerMesh, meshesIn, materials).asVarying();
const auto tangentsPerMesh = model.addJob<CalculateMeshTangentsTask>("CalculateMeshTangents", calculateMeshTangentsInputs);
const auto calculateBlendshapeNormalsInputs = CalculateBlendshapeNormalsTask::Input(blendshapesPerMeshIn, meshesIn).asVarying();
const auto normalsPerBlendshapePerMesh = model.addJob<CalculateBlendshapeNormalsTask>("CalculateBlendshapeNormals", calculateBlendshapeNormalsInputs);
const auto calculateBlendshapeTangentsInputs = CalculateBlendshapeTangentsTask::Input(normalsPerBlendshapePerMesh, blendshapesPerMeshIn, meshesIn, materials).asVarying();
const auto tangentsPerBlendshapePerMesh = model.addJob<CalculateBlendshapeTangentsTask>("CalculateBlendshapeTangents", calculateBlendshapeTangentsInputs);
// Build the graphics::MeshPointer for each hfm::Mesh
const auto buildGraphicsMeshInputs = BuildGraphicsMeshTask::Input(meshesIn, url, meshIndicesToModelNames).asVarying();
const auto buildGraphicsMeshOutputs = model.addJob<BuildGraphicsMeshTask>("BuildGraphicsMesh", buildGraphicsMeshInputs);
const auto graphicsMeshes = buildGraphicsMeshOutputs.getN<BuildGraphicsMeshTask::Output>(0);
// TODO: Move tangent/blendshape validation/calculation to an earlier step
const auto tangentsPerMesh = buildGraphicsMeshOutputs.getN<BuildGraphicsMeshTask::Output>(1);
const auto blendshapesPerMesh = buildGraphicsMeshOutputs.getN<BuildGraphicsMeshTask::Output>(2);
const auto buildGraphicsMeshInputs = BuildGraphicsMeshTask::Input(meshesIn, url, meshIndicesToModelNames, normalsPerMesh, tangentsPerMesh).asVarying();
const auto graphicsMeshes = model.addJob<BuildGraphicsMeshTask>("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<BuildBlendshapesTask>("BuildBlendshapes", buildBlendshapesInputs);
const auto buildMeshesInputs = BuildMeshesTask::Input(meshesIn, graphicsMeshes, normalsPerMesh, tangentsPerMesh, blendshapesPerMeshOut).asVarying();
const auto meshesOut = model.addJob<BuildMeshesTask>("BuildMeshes", buildMeshesInputs);
const auto buildModelInputs = BuildModelTask::Input(hfmModelIn, meshesOut).asVarying();
hfmModelOut = model.addJob<BuildModelTask>("BuildModel", buildModelInputs);

View file

@ -15,10 +15,25 @@
#include <hfm/HFM.h>
namespace baker {
using MeshIndices = std::vector<int>;
using IndicesPerMesh = std::vector<std::vector<int>>;
using VerticesPerMesh = std::vector<std::vector<glm::vec3>>;
using MeshNormals = std::vector<glm::vec3>;
using NormalsPerMesh = std::vector<std::vector<glm::vec3>>;
using MeshTangents = std::vector<glm::vec3>;
using TangentsPerMesh = std::vector<std::vector<glm::vec3>>;
using Blendshapes = std::vector<hfm::Blendshape>;
using BlendshapesPerMesh = std::vector<std::vector<hfm::Blendshape>>;
using BlendshapeVertices = std::vector<glm::vec3>;
using BlendshapeNormals = std::vector<glm::vec3>;
using BlendshapeIndices = std::vector<int>;
using VerticesPerBlendshape = std::vector<std::vector<glm::vec3>>;
using NormalsPerBlendshape = std::vector<std::vector<glm::vec3>>;
using IndicesPerBlendshape = std::vector<std::vector<int>>;
using BlendshapeTangents = std::vector<glm::vec3>;
using TangentsPerBlendshape = std::vector<std::vector<glm::vec3>>;
using MeshIndicesToModelNames = QHash<int, QString>;
};

View file

@ -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<graphics::Mesh>();
// 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<NormalType> 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<gpu::Stream::Format>();
auto vertexBufferStream = std::make_shared<gpu::BufferStream>();
// 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) {

View file

@ -20,8 +20,8 @@
class BuildGraphicsMeshTask {
public:
using Input = baker::VaryingSet3<std::vector<hfm::Mesh>, hifi::URL, baker::MeshIndicesToModelNames>;
using Output = baker::VaryingSet3<std::vector<graphics::MeshPointer>, std::vector<baker::MeshTangents>, std::vector<baker::Blendshapes>>;
using Input = baker::VaryingSet5<std::vector<hfm::Mesh>, hifi::URL, baker::MeshIndicesToModelNames, baker::NormalsPerMesh, baker::TangentsPerMesh>;
using Output = std::vector<graphics::MeshPointer>;
using JobModel = baker::Job::ModelIO<BuildGraphicsMeshTask, Input, Output>;
void run(const baker::BakeContextPointer& context, const Input& input, Output& output);

View file

@ -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<int> 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];
}
});
}
}
}
}

View file

@ -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<baker::BlendshapesPerMesh, std::vector<hfm::Mesh>>;
using Output = std::vector<baker::NormalsPerBlendshape>;
using JobModel = baker::Job::ModelIO<CalculateBlendshapeNormalsTask, Input, Output>;
void run(const baker::BakeContextPointer& context, const Input& input, Output& output);
};
#endif // hifi_CalculateBlendshapeNormalsTask_h

View file

@ -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 <set>
#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 auto& materials = 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 = materials.find(meshPart.materialID);
if (materialIt != 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<int> 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;
}
});
}
}
}

View file

@ -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<std::vector<baker::NormalsPerBlendshape>, baker::BlendshapesPerMesh, std::vector<hfm::Mesh>, QHash<QString, hfm::Material>>;
using Output = std::vector<baker::TangentsPerBlendshape>;
using JobModel = baker::Job::ModelIO<CalculateBlendshapeTangentsTask, Input, Output>;
void run(const baker::BakeContextPointer& context, const Input& input, Output& output);
};
#endif // hifi_CalculateBlendshapeTangentsTask_h

View file

@ -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 < (int)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];
}
);
}
}
}

View file

@ -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 <hfm/HFM.h>
#include "Engine.h"
#include "BakerTypes.h"
// Calculate mesh normals if not already present in the mesh
class CalculateMeshNormalsTask {
public:
using Input = std::vector<hfm::Mesh>;
using Output = baker::NormalsPerMesh;
using JobModel = baker::Job::ModelIO<CalculateMeshNormalsTask, Input, Output>;
void run(const baker::BakeContextPointer& context, const Input& input, Output& output);
};
#endif // hifi_CalculateMeshNormalsTask_h

View file

@ -0,0 +1,65 @@
//
// 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<hfm::Mesh>& meshes = input.get1();
const auto& materials = input.get2();
auto& tangentsPerMeshOut = output;
tangentsPerMeshOut.reserve(meshes.size());
for (int i = 0; i < (int)meshes.size(); i++) {
const auto& mesh = meshes[i];
const auto& tangentsIn = mesh.tangents;
const auto& normals = normalsPerMesh[i];
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 = materials.find(meshPart.materialID);
if (materialIt != 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]);
});
}
}

View file

@ -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 <hfm/HFM.h>
#include "Engine.h"
#include "BakerTypes.h"
// Calculate mesh tangents if not already present in the mesh
class CalculateMeshTangentsTask {
public:
using NormalsPerMesh = std::vector<std::vector<glm::vec3>>;
using Input = baker::VaryingSet3<baker::NormalsPerMesh, std::vector<hfm::Mesh>, QHash<QString, hfm::Material>>;
using Output = baker::TangentsPerMesh;
using JobModel = baker::Job::ModelIO<CalculateMeshTangentsTask, Input, Output>;
void run(const baker::BakeContextPointer& context, const Input& input, Output& output);
};
#endif // hifi_CalculateMeshTangentsTask_h

View file

@ -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 <LogHandler.h>
#include "ModelBakerLogging.h"
namespace baker {
template<class T>
const T& checkedAt(const QVector<T>& 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<class T>
const T& checkedAt(const std::vector<T>& 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<class T>
T& checkedAt(std::vector<T>& 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");
}
}
}
}

View file

@ -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 <hfm/HFM.h>
#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<glm::vec3*(int index)>;
// Assigns a vertex to outVertex given the lookup index
using VertexSetter = std::function<void(int index, glm::vec3& outVertex)>;
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<glm::vec3*(int firstIndex, int secondIndex, glm::vec3* outVertices, glm::vec2* outTexCoords, glm::vec3& outNormal)>;
void calculateTangents(const hfm::Mesh& mesh, IndexAccessor accessor);
};