From c2d16f080d4ea15e85ec5e30ea754117ce51c78b Mon Sep 17 00:00:00 2001 From: Henrik Halen Date: Wed, 26 Jun 2019 16:32:42 -0700 Subject: [PATCH] Allow arbitrary number of skinning weights. Controlled through command line parameter. Defaults to 4 skinning weights. --- src/FBX2glTF.cpp | 19 +++++-- src/FBX2glTF.h | 30 ++++++----- src/fbx/Fbx2Raw.cpp | 74 ++++++++++++++------------- src/fbx/FbxSkinningAccess.cpp | 32 ++++-------- src/fbx/FbxSkinningAccess.hpp | 30 +++++------ src/gltf/GltfModel.hpp | 25 +++++++++ src/gltf/Raw2Gltf.cpp | 45 +++++++++------- src/gltf/Raw2Gltf.hpp | 24 +++++++++ src/gltf/properties/PrimitiveData.hpp | 26 ++++++++++ src/raw/RawModel.cpp | 74 ++++++++++++++++++++++----- src/raw/RawModel.hpp | 46 +++++++++++++++-- 11 files changed, 298 insertions(+), 127 deletions(-) diff --git a/src/FBX2glTF.cpp b/src/FBX2glTF.cpp index 7c8948d..3cdfecf 100644 --- a/src/FBX2glTF.cpp +++ b/src/FBX2glTF.cpp @@ -152,11 +152,24 @@ int main(int argc, char* argv[]) { gltfOptions.useBlendShapeTangents, "Include blend shape tangents, if reported present by the FBX SDK."); + app.add_option( + "--normalize-weights", + gltfOptions.normalizeSkinningWeights, + "Normalize skinning weights.", + true); + + app.add_option( + "--skinning-weights", + gltfOptions.maxSkinningWeights, + "The number of joint influences per vertex.", + true) + ->check(CLI::Range(0, 512)); + app.add_option( "-k,--keep-attribute", [&](std::vector attributes) -> bool { gltfOptions.keepAttribs = - RAW_VERTEX_ATTRIBUTE_JOINT_INDICES | RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS; + RAW_VERTEX_ATTRIBUTE_JOINT_INDICES | RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS; for (std::string attribute : attributes) { if (attribute == "position") { gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_POSITION; @@ -216,7 +229,7 @@ int main(int argc, char* argv[]) { app.add_option( "--draco-bits-for-normals", gltfOptions.draco.quantBitsNormal, - "How many bits to quantize nornals to.", + "How many bits to quantize normals to.", true) ->check(CLI::Range(1, 32)) ->group("Draco"); @@ -334,7 +347,7 @@ int main(int argc, char* argv[]) { if (!texturesTransforms.empty()) { raw.TransformTextures(texturesTransforms); } - raw.Condense(); + raw.Condense(gltfOptions.maxSkinningWeights, gltfOptions.normalizeSkinningWeights); raw.TransformGeometry(gltfOptions.computeNormals); std::ofstream outStream; // note: auto-flushes in destructor diff --git a/src/FBX2glTF.h b/src/FBX2glTF.h index 56c7c2c..28c6640 100644 --- a/src/FBX2glTF.h +++ b/src/FBX2glTF.h @@ -112,17 +112,21 @@ struct GltfOptions { /** Whether to include lights through the KHR_punctual_lights extension. */ bool useKHRLightsPunctual{true}; - /** Whether to include blend shape normals, if present according to the SDK. */ - bool useBlendShapeNormals{false}; - /** Whether to include blend shape tangents, if present according to the SDK. */ - bool useBlendShapeTangents{false}; - /** When to compute vertex normals from geometry. */ - ComputeNormalsOption computeNormals = ComputeNormalsOption::BROKEN; - /** When to use 32-bit indices. */ - UseLongIndicesOptions useLongIndices = UseLongIndicesOptions::AUTO; - /** Select baked animation framerate. */ - AnimationFramerateOptions animationFramerate = AnimationFramerateOptions::BAKE24; - - /** Temporary directory used by FBX SDK. */ - std::string fbxTempDir; + /** Whether to include blend shape normals, if present according to the SDK. */ + bool useBlendShapeNormals { false }; + /** Whether to include blend shape tangents, if present according to the SDK. */ + bool useBlendShapeTangents { false }; + /** Whether to normalized skinning weights. */ + bool normalizeSkinningWeights { true }; + /** Maximum number of bone influences per vertex. */ + int maxSkinningWeights { 4 }; + /** When to compute vertex normals from geometry. */ + ComputeNormalsOption computeNormals = ComputeNormalsOption::BROKEN; + /** When to use 32-bit indices. */ + UseLongIndicesOptions useLongIndices = UseLongIndicesOptions::AUTO; + /** Select baked animation framerate. */ + AnimationFramerateOptions animationFramerate = AnimationFramerateOptions::BAKE24; + + /** Temporary directory used by FBX SDK. */ + std::string fbxTempDir; }; diff --git a/src/fbx/Fbx2Raw.cpp b/src/fbx/Fbx2Raw.cpp index 351bd85..b581fe8 100644 --- a/src/fbx/Fbx2Raw.cpp +++ b/src/fbx/Fbx2Raw.cpp @@ -88,6 +88,30 @@ static RawMaterialType GetMaterialType( return skinned ? RAW_MATERIAL_TYPE_SKINNED_OPAQUE : RAW_MATERIAL_TYPE_OPAQUE; } +static void calcMinMax( + RawSurface& rawSurface, + const FbxSkinningAccess& skinning, + const FbxVector4& globalPosition, + const std::vector& indicesAndWeights) { + for (int i = 0; i < indicesAndWeights.size(); i++) { + if (indicesAndWeights[i].jointWeight > 0.0f) { + const FbxVector4 localPosition = + skinning.GetJointInverseGlobalTransforms(indicesAndWeights[i].jointIndex) + .MultNormalize(globalPosition); + + Vec3f& mins = rawSurface.jointGeometryMins[indicesAndWeights[i].jointIndex]; + mins[0] = std::min(mins[0], (float)localPosition[0]); + mins[1] = std::min(mins[1], (float)localPosition[1]); + mins[2] = std::min(mins[2], (float)localPosition[2]); + + Vec3f& maxs = rawSurface.jointGeometryMaxs[indicesAndWeights[i].jointIndex]; + maxs[0] = std::max(maxs[0], (float)localPosition[0]); + maxs[1] = std::max(maxs[1], (float)localPosition[1]); + maxs[2] = std::max(maxs[2], (float)localPosition[2]); + } + } +} + static void ReadMesh( RawModel& raw, FbxScene* pScene, @@ -177,10 +201,6 @@ static void ReadMesh( if (uvLayer1.LayerPresent()) { raw.AddVertexAttribute(RAW_VERTEX_ATTRIBUTE_UV1); } - if (skinning.IsSkinned()) { - raw.AddVertexAttribute(RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS); - raw.AddVertexAttribute(RAW_VERTEX_ATTRIBUTE_JOINT_INDICES); - } RawSurface& rawSurface = raw.GetSurface(rawSurfaceIndex); @@ -361,8 +381,15 @@ static void ReadMesh( vertex.uv0[1] = (float)fbxUV0[1]; vertex.uv1[0] = (float)fbxUV1[0]; vertex.uv1[1] = (float)fbxUV1[1]; - vertex.jointIndices = skinning.GetVertexIndices(controlPointIndex); - vertex.jointWeights = skinning.GetVertexWeights(controlPointIndex); + if (skinning.IsSkinned()) { + const std::vector skinningInfo = + skinning.GetVertexSkinningInfo(controlPointIndex); + for (int skinningIndex = 0; skinningIndex < skinningInfo.size(); skinningIndex++) { + const FbxVertexSkinningInfo& sourceSkinningInfo = skinningInfo[skinningIndex]; + vertex.skinningInfo.push_back( + RawVertexSkinningInfo{sourceSkinningInfo.jointId, sourceSkinningInfo.weight}); + } + } vertex.polarityUv0 = false; // flag this triangle as transparent if any of its corner vertices substantially deviates from @@ -406,38 +433,13 @@ static void ReadMesh( } if (skinning.IsSkinned()) { - const int jointIndices[FbxSkinningAccess::MAX_WEIGHTS] = {vertex.jointIndices[0], - vertex.jointIndices[1], - vertex.jointIndices[2], - vertex.jointIndices[3]}; - const float jointWeights[FbxSkinningAccess::MAX_WEIGHTS] = {vertex.jointWeights[0], - vertex.jointWeights[1], - vertex.jointWeights[2], - vertex.jointWeights[3]}; - const FbxMatrix skinningMatrix = - skinning.GetJointSkinningTransform(jointIndices[0]) * jointWeights[0] + - skinning.GetJointSkinningTransform(jointIndices[1]) * jointWeights[1] + - skinning.GetJointSkinningTransform(jointIndices[2]) * jointWeights[2] + - skinning.GetJointSkinningTransform(jointIndices[3]) * jointWeights[3]; + FbxMatrix skinningMatrix = FbxMatrix() * 0.0; + for (int j = 0; j < vertex.skinningInfo.size(); j++) + skinningMatrix += skinning.GetJointSkinningTransform(vertex.skinningInfo[j].jointIndex) * + vertex.skinningInfo[j].jointWeight; const FbxVector4 globalPosition = skinningMatrix.MultNormalize(fbxPosition); - for (int i = 0; i < FbxSkinningAccess::MAX_WEIGHTS; i++) { - if (jointWeights[i] > 0.0f) { - const FbxVector4 localPosition = - skinning.GetJointInverseGlobalTransforms(jointIndices[i]) - .MultNormalize(globalPosition); - - Vec3f& mins = rawSurface.jointGeometryMins[jointIndices[i]]; - mins[0] = std::min(mins[0], (float)localPosition[0]); - mins[1] = std::min(mins[1], (float)localPosition[1]); - mins[2] = std::min(mins[2], (float)localPosition[2]); - - Vec3f& maxs = rawSurface.jointGeometryMaxs[jointIndices[i]]; - maxs[0] = std::max(maxs[0], (float)localPosition[0]); - maxs[1] = std::max(maxs[1], (float)localPosition[1]); - maxs[2] = std::max(maxs[2], (float)localPosition[2]); - } - } + calcMinMax(rawSurface, skinning, globalPosition, vertex.skinningInfo); } } diff --git a/src/fbx/FbxSkinningAccess.cpp b/src/fbx/FbxSkinningAccess.cpp index 8bdf6fe..6e70812 100644 --- a/src/fbx/FbxSkinningAccess.cpp +++ b/src/fbx/FbxSkinningAccess.cpp @@ -19,8 +19,7 @@ FbxSkinningAccess::FbxSkinningAccess(const FbxMesh* pMesh, FbxScene* pScene, Fbx continue; } int controlPointCount = pMesh->GetControlPointsCount(); - vertexJointIndices.resize(controlPointCount, Vec4i(0, 0, 0, 0)); - vertexJointWeights.resize(controlPointCount, Vec4f(0.0f, 0.0f, 0.0f, 0.0f)); + vertexSkinning.resize(controlPointCount); for (int clusterIndex = 0; clusterIndex < clusterCount; clusterIndex++) { FbxCluster* cluster = skin->GetCluster(clusterIndex); @@ -56,31 +55,18 @@ FbxSkinningAccess::FbxSkinningAccess(const FbxMesh* pMesh, FbxScene* pScene, Fbx if (clusterIndices[i] < 0 || clusterIndices[i] >= controlPointCount) { continue; } - if (clusterWeights[i] <= vertexJointWeights[clusterIndices[i]][MAX_WEIGHTS - 1]) { + if (clusterWeights[i] <= 0.0) { continue; } - vertexJointIndices[clusterIndices[i]][MAX_WEIGHTS - 1] = clusterIndex; - vertexJointWeights[clusterIndices[i]][MAX_WEIGHTS - 1] = (float)clusterWeights[i]; - for (int j = MAX_WEIGHTS - 1; j > 0; j--) { - if (vertexJointWeights[clusterIndices[i]][j - 1] >= - vertexJointWeights[clusterIndices[i]][j]) { - break; - } - std::swap( - vertexJointIndices[clusterIndices[i]][j - 1], - vertexJointIndices[clusterIndices[i]][j]); - std::swap( - vertexJointWeights[clusterIndices[i]][j - 1], - vertexJointWeights[clusterIndices[i]][j]); - } + + vertexSkinning[clusterIndices[i]].push_back(FbxVertexSkinningInfo{(int) clusterIndex, (float)clusterWeights[i]}); + } } - for (int i = 0; i < controlPointCount; i++) { - const float weightSumRcp = 1.0 / - (vertexJointWeights[i][0] + vertexJointWeights[i][1] + vertexJointWeights[i][2] + - vertexJointWeights[i][3]); - vertexJointWeights[i] *= weightSumRcp; - } + + for (int i = 0; i < vertexSkinning.size(); i++) + maxBoneInfluences = std::max((int) vertexSkinning[i].size(), maxBoneInfluences); + } } diff --git a/src/fbx/FbxSkinningAccess.hpp b/src/fbx/FbxSkinningAccess.hpp index e43ef86..4f0e446 100644 --- a/src/fbx/FbxSkinningAccess.hpp +++ b/src/fbx/FbxSkinningAccess.hpp @@ -18,16 +18,22 @@ #include "FBX2glTF.h" +struct FbxVertexSkinningInfo { + int jointId; + float weight; +}; + + class FbxSkinningAccess { public: - static const int MAX_WEIGHTS = 4; FbxSkinningAccess(const FbxMesh* pMesh, FbxScene* pScene, FbxNode* pNode); bool IsSkinned() const { - return (vertexJointWeights.size() > 0); + return (vertexSkinning.size() > 0); } + int GetNodeCount() const { return (int)jointNodes.size(); } @@ -56,24 +62,18 @@ class FbxSkinningAccess { const FbxAMatrix& GetInverseBindMatrix(const int jointIndex) const { return inverseBindMatrices[jointIndex]; } - - const Vec4i GetVertexIndices(const int controlPointIndex) const { - return (!vertexJointIndices.empty()) ? vertexJointIndices[controlPointIndex] - : Vec4i(0, 0, 0, 0); - } - - const Vec4f GetVertexWeights(const int controlPointIndex) const { - return (!vertexJointWeights.empty()) ? vertexJointWeights[controlPointIndex] - : Vec4f(0, 0, 0, 0); - } + + const std::vector GetVertexSkinningInfo(const int controlPointIndex) const { + return vertexSkinning[controlPointIndex]; + } private: int rootIndex; - std::vector jointIds; + int maxBoneInfluences; + std::vector jointIds; std::vector jointNodes; std::vector jointSkinningTransforms; std::vector jointInverseGlobalTransforms; std::vector inverseBindMatrices; - std::vector vertexJointIndices; - std::vector vertexJointWeights; + std::vector> vertexSkinning; }; diff --git a/src/gltf/GltfModel.hpp b/src/gltf/GltfModel.hpp index 9a15437..48939db 100644 --- a/src/gltf/GltfModel.hpp +++ b/src/gltf/GltfModel.hpp @@ -122,6 +122,31 @@ class GltfModel { return accessor; }; + template + std::shared_ptr AddAttributeArrayToPrimitive( + BufferData& buffer, + const RawModel& surfaceModel, + PrimitiveData& primitive, + const AttributeArrayDefinition& attrDef) { + // copy attribute data into vector + std::vector attribArr; + surfaceModel.GetArrayAttributeArray(attribArr, attrDef.rawAttributeIx, attrDef.arrayOffset); + + std::shared_ptr accessor; + if (attrDef.dracoComponentType != draco::DT_INVALID && primitive.dracoMesh != nullptr) { + primitive.AddDracoArrayAttrib(attrDef, attribArr); + + accessor = accessors.hold(new AccessorData(attrDef.glType)); + accessor->count = attribArr.size(); + } + else { + auto bufferView = GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_BUFFER); + accessor = AddAccessorWithView(*bufferView, attrDef.glType, attribArr, std::string("")); + } + primitive.AddAttrib(attrDef.gltfName, *accessor); + return accessor; + }; + template void serializeHolder(json& glTFJson, std::string key, const Holder holder) { if (!holder.ptrs.empty()) { diff --git a/src/gltf/Raw2Gltf.cpp b/src/gltf/Raw2Gltf.cpp index 0e2c592..99d228f 100644 --- a/src/gltf/Raw2Gltf.cpp +++ b/src/gltf/Raw2Gltf.cpp @@ -496,8 +496,8 @@ ModelData* Raw2Gltf( } if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_TANGENT) != 0) { const AttributeDefinition ATTR_TANGENT("TANGENT", &RawVertex::tangent, GLT_VEC4F); - const auto _ = gltf->AddAttributeToPrimitive( - buffer, surfaceModel, *primitive, ATTR_TANGENT); + const auto _ = + gltf->AddAttributeToPrimitive(buffer, surfaceModel, *primitive, ATTR_TANGENT); } if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_COLOR) != 0) { const AttributeDefinition ATTR_COLOR( @@ -530,24 +530,30 @@ ModelData* Raw2Gltf( buffer, surfaceModel, *primitive, ATTR_TEXCOORD_1); } if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_JOINT_INDICES) != 0) { - const AttributeDefinition ATTR_JOINTS( - "JOINTS_0", - &RawVertex::jointIndices, - GLT_VEC4I, - draco::GeometryAttribute::GENERIC, - draco::DT_UINT16); - const auto _ = - gltf->AddAttributeToPrimitive(buffer, surfaceModel, *primitive, ATTR_JOINTS); + for (int i = 0; i < surfaceModel.GetGlobalWeightCount(); i += 4) { + const AttributeArrayDefinition ATTR_JOINTS( + std::string("JOINTS_") + std::to_string(i / 4), + &RawVertex::jointIndices, + GLT_VEC4I, + draco::GeometryAttribute::GENERIC, + draco::DT_UINT16, + i / 4); + const auto _ = gltf->AddAttributeArrayToPrimitive( + buffer, surfaceModel, *primitive, ATTR_JOINTS); + } } if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS) != 0) { - const AttributeDefinition ATTR_WEIGHTS( - "WEIGHTS_0", - &RawVertex::jointWeights, - GLT_VEC4F, - draco::GeometryAttribute::GENERIC, - draco::DT_FLOAT32); - const auto _ = - gltf->AddAttributeToPrimitive(buffer, surfaceModel, *primitive, ATTR_WEIGHTS); + for (int i = 0; i < surfaceModel.GetGlobalWeightCount(); i += 4) { + const AttributeArrayDefinition ATTR_WEIGHTS( + std::string("WEIGHTS_") + std::to_string(i / 4), + &RawVertex::jointWeights, + GLT_VEC4F, + draco::GeometryAttribute::GENERIC, + draco::DT_FLOAT32, + i / 4); + const auto _ = gltf->AddAttributeArrayToPrimitive( + buffer, surfaceModel, *primitive, ATTR_WEIGHTS); + } } // each channel present in the mesh always ends up a target in the primitive @@ -632,7 +638,8 @@ ModelData* Raw2Gltf( draco::Status status = encoder.EncodeMeshToBuffer(*primitive->dracoMesh, &dracoBuffer); assert(status.code() == draco::Status::OK); - auto view = gltf->AddRawBufferView(buffer, dracoBuffer.data(), to_uint32(dracoBuffer.size())); + auto view = + gltf->AddRawBufferView(buffer, dracoBuffer.data(), to_uint32(dracoBuffer.size())); primitive->NoteDracoBuffer(*view); } mesh->AddPrimitive(primitive); diff --git a/src/gltf/Raw2Gltf.hpp b/src/gltf/Raw2Gltf.hpp index f30b545..9dd753f 100644 --- a/src/gltf/Raw2Gltf.hpp +++ b/src/gltf/Raw2Gltf.hpp @@ -155,6 +155,30 @@ struct AttributeDefinition { dracoComponentType(draco::DataType::DT_INVALID) {} }; +template +struct AttributeArrayDefinition { + const std::string gltfName; + const std::vector RawVertex::*rawAttributeIx; + const GLType glType; + const int arrayOffset; + const draco::GeometryAttribute::Type dracoAttribute; + const draco::DataType dracoComponentType; + + AttributeArrayDefinition( + const std::string gltfName, + const std::vector RawVertex::*rawAttributeIx, + const GLType& _glType, + const draco::GeometryAttribute::Type dracoAttribute, + const draco::DataType dracoComponentType, + const int arrayOffset) + : gltfName(gltfName), + rawAttributeIx(rawAttributeIx), + glType(_glType), + dracoAttribute(dracoAttribute), + dracoComponentType(dracoComponentType), + arrayOffset(arrayOffset) {} +}; + struct AccessorData; struct AnimationData; struct BufferData; diff --git a/src/gltf/properties/PrimitiveData.hpp b/src/gltf/properties/PrimitiveData.hpp index 1ff401b..a574f8b 100644 --- a/src/gltf/properties/PrimitiveData.hpp +++ b/src/gltf/properties/PrimitiveData.hpp @@ -61,6 +61,32 @@ struct PrimitiveData { dracoAttributes[attribute.gltfName] = dracoAttId; } + template + void AddDracoArrayAttrib(const AttributeArrayDefinition attribute, const std::vector& attribArr) { + draco::PointAttribute att; + int8_t componentCount = attribute.glType.count; + att.Init( + attribute.dracoAttribute, + nullptr, + componentCount, + attribute.dracoComponentType, + false, + componentCount * draco::DataTypeLength(attribute.dracoComponentType), + 0); + + const int dracoAttId = dracoMesh->AddAttribute(att, true, attribArr.size()); + draco::PointAttribute* attPtr = dracoMesh->attribute(dracoAttId); + + std::vector buf(sizeof(T)); + for (uint32_t ii = 0; ii < attribArr.size(); ii++) { + uint8_t* ptr = &buf[0]; + attribute.glType.write(ptr, attribArr[ii]); + attPtr->SetAttributeValue(attPtr->mapped_index(draco::PointIndex(ii)), ptr); + } + + dracoAttributes[attribute.gltfName] = dracoAttId; + } + void NoteDracoBuffer(const BufferViewData& data); const int indices; diff --git a/src/raw/RawModel.cpp b/src/raw/RawModel.cpp index 84b8ae9..c9f3773 100644 --- a/src/raw/RawModel.cpp +++ b/src/raw/RawModel.cpp @@ -34,8 +34,9 @@ size_t VertexHasher::operator()(const RawVertex& v) const { bool RawVertex::operator==(const RawVertex& other) const { return (position == other.position) && (normal == other.normal) && (tangent == other.tangent) && (binormal == other.binormal) && (color == other.color) && (uv0 == other.uv0) && - (uv1 == other.uv1) && (jointIndices == other.jointIndices) && - (jointWeights == other.jointWeights) && (polarityUv0 == other.polarityUv0) && + (uv1 == other.uv1) && + (jointWeights == other.jointWeights) && (jointIndices == other.jointIndices) && + (polarityUv0 == other.polarityUv0) && (blendSurfaceIx == other.blendSurfaceIx) && (blends == other.blends); } @@ -63,16 +64,13 @@ size_t RawVertex::Difference(const RawVertex& other) const { attributes |= RAW_VERTEX_ATTRIBUTE_UV1; } // Always need both or neither. - if (jointIndices != other.jointIndices) { - attributes |= RAW_VERTEX_ATTRIBUTE_JOINT_INDICES | RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS; - } - if (jointWeights != other.jointWeights) { - attributes |= RAW_VERTEX_ATTRIBUTE_JOINT_INDICES | RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS; + if (jointIndices != other.jointIndices || jointWeights != other.jointWeights) { + attributes |= RAW_VERTEX_ATTRIBUTE_JOINT_INDICES | RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS; } return attributes; } -RawModel::RawModel() : vertexAttributes(0) {} +RawModel::RawModel() : vertexAttributes(0){} void RawModel::AddVertexAttribute(const RawVertexAttribute attrib) { vertexAttributes |= attrib; @@ -335,7 +333,7 @@ int RawModel::AddNode(const long id, const char* name, const long parentId) { return (int)nodes.size() - 1; } -void RawModel::Condense() { +void RawModel::Condense(const int maxSkinningWeights, const bool normalizeWeights) { // Only keep surfaces that are referenced by one or more triangles. { std::vector oldSurfaces = surfaces; @@ -405,6 +403,53 @@ void RawModel::Condense() { } } } + + { + globalMaxWeights = 0; + for (auto& vertex: vertices) { + + // Sort from largest to smallest weight. + std::sort(vertex.skinningInfo.begin(), vertex.skinningInfo.end(), std::greater()); + + // Reduce to fit the requirements. + if (maxSkinningWeights < vertex.skinningInfo.size()) + vertex.skinningInfo.resize(maxSkinningWeights); + globalMaxWeights = std::max(globalMaxWeights, (int) vertex.skinningInfo.size()); + + // Normalize weights if requested. + if (normalizeWeights) { + float weightSum = 0; + for (auto& jointWeight : vertex.skinningInfo) + weightSum += jointWeight.jointWeight; + const float weightSumRcp = 1.0 / weightSum; + for (auto& jointWeight : vertex.skinningInfo) + jointWeight.jointWeight *= weightSumRcp; + } + } + + if (globalMaxWeights > 0) { + AddVertexAttribute(RAW_VERTEX_ATTRIBUTE_JOINT_INDICES); + AddVertexAttribute(RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS); + } + + + assert(globalMaxWeights >= 0); + // Copy to gltf friendly structure + for (auto& vertex : vertices) { + vertex.jointIndices.reserve(globalMaxWeights); + vertex.jointWeights.reserve(globalMaxWeights); + for (int i = 0; i < globalMaxWeights; i += 4) { // ensure every vertex has the same amount of weights + Vec4f weights{0.0}; + Vec4i jointIds{0,0,0,0}; + for (int j = i; j < i + 4 && j < vertex.skinningInfo.size(); j++) { + weights[j - i] = vertex.skinningInfo[j].jointWeight; + jointIds[j - i] = vertex.skinningInfo[j].jointIndex; + } + vertex.jointIndices.push_back(jointIds); + vertex.jointWeights.push_back(weights); + } + } + } } void RawModel::TransformGeometry(ComputeNormalsOption normals) { @@ -554,6 +599,7 @@ void RawModel::CreateMaterialModels( surfaces[sortedTriangles[i - 1].surfaceIndex].discrete))) { materialModels.resize(materialModels.size() + 1); model = &materialModels[materialModels.size() - 1]; + model->globalMaxWeights = globalMaxWeights; } // FIXME: will have to unlink from the nodes, transform both surfaces into a @@ -622,11 +668,11 @@ void RawModel::CreateMaterialModels( if ((keep & RAW_VERTEX_ATTRIBUTE_UV1) == 0) { vertex.uv1 = defaultVertex.uv1; } - if ((keep & RAW_VERTEX_ATTRIBUTE_JOINT_INDICES) == 0) { - vertex.jointIndices = defaultVertex.jointIndices; - } - if ((keep & RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS) == 0) { - vertex.jointWeights = defaultVertex.jointWeights; + if ((keep & RAW_VERTEX_ATTRIBUTE_JOINT_INDICES) == 0) { + vertex.jointIndices = defaultVertex.jointIndices; + } + if ((keep & RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS) == 0) { + vertex.jointWeights = defaultVertex.jointWeights; } } diff --git a/src/raw/RawModel.hpp b/src/raw/RawModel.hpp index 81452d6..8d1fd3f 100644 --- a/src/raw/RawModel.hpp +++ b/src/raw/RawModel.hpp @@ -22,7 +22,7 @@ enum RawVertexAttribute { RAW_VERTEX_ATTRIBUTE_COLOR = 1 << 4, RAW_VERTEX_ATTRIBUTE_UV0 = 1 << 5, RAW_VERTEX_ATTRIBUTE_UV1 = 1 << 6, - RAW_VERTEX_ATTRIBUTE_JOINT_INDICES = 1 << 7, + RAW_VERTEX_ATTRIBUTE_JOINT_INDICES = 1 << 7, RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS = 1 << 8, RAW_VERTEX_ATTRIBUTE_AUTO = 1 << 31 @@ -38,6 +38,16 @@ struct RawBlendVertex { } }; +struct RawVertexSkinningInfo +{ + int jointIndex; + float jointWeight; + + bool operator>(const RawVertexSkinningInfo& rjw) const { + return jointWeight > rjw.jointWeight; + } +}; + struct RawVertex { Vec3f position{0.0f}; Vec3f normal{0.0f}; @@ -46,8 +56,10 @@ struct RawVertex { Vec4f color{0.0f}; Vec2f uv0{0.0f}; Vec2f uv1{0.0f}; - Vec4i jointIndices{0, 0, 0, 0}; - Vec4f jointWeights{0.0f}; + std::vector jointIndices; + std::vector jointWeights; + + std::vector skinningInfo; // end of members that directly correspond to vertex attributes // if this vertex participates in a blend shape setup, the surfaceIx of its dedicated mesh; @@ -347,6 +359,7 @@ struct RawNode { class RawModel { public: + RawModel(); // Add geometry. @@ -407,7 +420,7 @@ class RawModel { // Remove unused vertices, textures or materials after removing vertex attributes, textures, // materials or surfaces. - void Condense(); + void Condense(const int maxSkinningWeights, const bool normalizeWeights); void TransformGeometry(ComputeNormalsOption); @@ -424,6 +437,11 @@ class RawModel { int GetVertexCount() const { return (int)vertices.size(); } + + int GetGlobalWeightCount() const{ + return globalMaxWeights; + } + const RawVertex& GetVertex(const int index) const { return vertices[index]; } @@ -506,6 +524,14 @@ class RawModel { void GetAttributeArray(std::vector<_attrib_type_>& out, const _attrib_type_ RawVertex::*ptr) const; + // Create individual attribute arrays, with the source as an array. + // Returns true if the vertices store the particular attribute. + template + void GetArrayAttributeArray(std::vector<_attrib_type_>& out, + const std::vector<_attrib_type_> RawVertex::*ptr, + const int arrayOffset) + const; + // Create an array with a raw model for each material. // Multiple surfaces with the same material will turn into a single model. // However, surfaces that are marked as 'discrete' will turn into separate models. @@ -520,6 +546,7 @@ class RawModel { long rootNodeId; int vertexAttributes; + int globalMaxWeights; std::unordered_map vertexHash; std::vector vertices; std::vector triangles; @@ -541,3 +568,14 @@ void RawModel::GetAttributeArray( out[i] = vertices[i].*ptr; } } + +template +void RawModel::GetArrayAttributeArray( + std::vector<_attrib_type_>& out, + const std::vector<_attrib_type_> RawVertex::*ptr, + const int arrayOffset) const { + out.resize(vertices.size()); + for (size_t i = 0; i < vertices.size(); i++) { + out[i] = (vertices[i].*ptr)[arrayOffset]; + } +}