Blendshapes fix (still missing tests) 85/292885/7
authorAdam Bialogonski <adam.b@samsung.com>
Tue, 16 May 2023 15:53:24 +0000 (16:53 +0100)
committerRichard <r.huang@samsung.com>
Wed, 17 May 2023 14:05:59 +0000 (15:05 +0100)
When using sparse accessors, the gltf loader loads data correctly
but the blendshapes generator (texture and data) always assumes that
morph targets contain all the vertices which isn't a case.

Fix makes sure that for sparse access only indexed vertices are
affected by the blendshape. It still generate full vertex data
(for whole mesh) so it's compatible with current shader.

Change-Id: I1f36c1d9d24dec0eb194d0aaf1b93488b61b1d58
Signed-off-by: Adam Bialogonski <adam.b@samsung.com>
dali-scene3d/public-api/loader/mesh-definition.cpp
dali-scene3d/public-api/loader/mesh-definition.h

index 444eb37..5ccf70c 100644 (file)
@@ -113,7 +113,7 @@ void ReadValues(const std::vector<uint8_t>& valuesBuffer, const std::vector<uint
   }
 }
 
-bool ReadAccessor(const MeshDefinition::Accessor& accessor, std::istream& source, uint8_t* target)
+bool ReadAccessor(const MeshDefinition::Accessor& accessor, std::istream& source, uint8_t* target, std::vector<uint32_t>* sparseIndices)
 {
   bool success = false;
 
@@ -152,31 +152,63 @@ bool ReadAccessor(const MeshDefinition::Accessor& accessor, std::istream& source
       return false;
     }
 
+    // If non-null sparse indices vector, prepare it for output
+    if(sparseIndices)
+    {
+      sparseIndices->resize(accessor.mSparse->mCount);
+    }
+
     switch(indices.mElementSizeHint)
     {
       case 1u:
       {
         ReadValues<uint8_t>(valuesBuffer, indicesBuffer, target, accessor.mSparse->mCount, values.mElementSizeHint);
+        if(sparseIndices)
+        {
+          // convert 8-bit indices into 32-bit
+          std::transform(indicesBuffer.begin(), indicesBuffer.end(), sparseIndices->begin(), [](const uint8_t& value) { return uint32_t(value); });
+        }
         break;
       }
       case 2u:
       {
         ReadValues<uint16_t>(valuesBuffer, indicesBuffer, target, accessor.mSparse->mCount, values.mElementSizeHint);
+        if(sparseIndices)
+        {
+          // convert 16-bit indices into 32-bit
+          std::transform(reinterpret_cast<uint16_t*>(indicesBuffer.data()),
+                         reinterpret_cast<uint16_t*>(indicesBuffer.data()) + accessor.mSparse->mCount,
+                         sparseIndices->begin(),
+                         [](const uint16_t& value) {
+                           return uint32_t(value);
+                         });
+        }
         break;
       }
       case 4u:
       {
         ReadValues<uint32_t>(valuesBuffer, indicesBuffer, target, accessor.mSparse->mCount, values.mElementSizeHint);
+        if(sparseIndices)
+        {
+          std::copy(indicesBuffer.begin(), indicesBuffer.end(), reinterpret_cast<uint8_t*>(sparseIndices->data()));
+        }
         break;
       }
       default:
+      {
         DALI_ASSERT_DEBUG(!"Unsupported type for an index");
+      }
     }
   }
 
   return success;
 }
 
+bool ReadAccessor(const MeshDefinition::Accessor& accessor, std::istream& source, uint8_t* target)
+{
+  return ReadAccessor(accessor, source, target, nullptr);
+}
+
 template<typename T>
 void ReadJointAccessor(MeshDefinition::RawData& raw, const MeshDefinition::Accessor& accessor, std::istream& source, const std::string& meshPath)
 {
@@ -427,21 +459,41 @@ void CalculateGltf2BlendShapes(uint8_t* geometryBuffer, const std::vector<MeshDe
                           blendShape.deltas.mBlob.mStride >= sizeof(Vector3)) &&
                          "Blend Shape position buffer length not a multiple of element size");
 
-      const auto           bufferSize = blendShape.deltas.mBlob.GetBufferSize();
-      std::vector<uint8_t> buffer(bufferSize);
-      if(ReadAccessor(blendShape.deltas, buffers[blendShape.deltas.mBufferIdx].GetBufferStream(), buffer.data()))
+      const auto            bufferSize = blendShape.deltas.mBlob.GetBufferSize();
+      std::vector<uint8_t>  buffer(bufferSize);
+      std::vector<uint32_t> sparseIndices{};
+
+      if(ReadAccessor(blendShape.deltas, buffers[blendShape.deltas.mBufferIdx].GetBufferStream(), buffer.data(), &sparseIndices))
       {
-        blendShape.deltas.mBlob.ApplyMinMax(static_cast<uint32_t>(bufferSize / sizeof(Vector3)), reinterpret_cast<float*>(buffer.data()));
+        blendShape.deltas.mBlob.ApplyMinMax(static_cast<uint32_t>(bufferSize / sizeof(Vector3)), reinterpret_cast<float*>(buffer.data()), &sparseIndices);
+
         // Calculate the difference with the original mesh.
         // Find the max distance to normalize the deltas.
-        const Vector3* const deltasBuffer = reinterpret_cast<const Vector3* const>(buffer.data());
+        const auto* const deltasBuffer = reinterpret_cast<const Vector3* const>(buffer.data());
 
-        for(uint32_t index = 0u; index < numberOfVertices; ++index)
-        {
-          Vector3& delta = geometryBufferV3[geometryBufferIndex++];
-          delta          = deltasBuffer[index];
+        auto ProcessVertex = [&geometryBufferV3, &deltasBuffer, &maxDistanceSquared](uint32_t geometryBufferIndex, uint32_t deltaIndex) {
+          Vector3& delta = geometryBufferV3[geometryBufferIndex] = deltasBuffer[deltaIndex];
+          delta                                                  = deltasBuffer[deltaIndex];
+          return std::max(maxDistanceSquared, delta.LengthSquared());
+        };
 
-          maxDistanceSquared = std::max(maxDistanceSquared, delta.LengthSquared());
+        if(sparseIndices.empty())
+        {
+          for(uint32_t index = 0u; index < numberOfVertices; ++index)
+          {
+            maxDistanceSquared = ProcessVertex(geometryBufferIndex++, index);
+          }
+        }
+        else
+        {
+          // initialize blendshape texture
+          // TODO: there may be a case when sparse accessor uses a base buffer view for initial values.
+          std::fill(geometryBufferV3 + geometryBufferIndex, geometryBufferV3 + geometryBufferIndex + numberOfVertices, Vector3::ZERO);
+          for(auto index : sparseIndices)
+          {
+            maxDistanceSquared = ProcessVertex(geometryBufferIndex + index, index);
+          }
+          geometryBufferIndex += numberOfVertices;
         }
       }
     }
@@ -452,20 +504,18 @@ void CalculateGltf2BlendShapes(uint8_t* geometryBuffer, const std::vector<MeshDe
                           blendShape.normals.mBlob.mStride >= sizeof(Vector3)) &&
                          "Blend Shape normals buffer length not a multiple of element size");
 
-      const auto           bufferSize = blendShape.normals.mBlob.GetBufferSize();
-      std::vector<uint8_t> buffer(bufferSize);
-      if(ReadAccessor(blendShape.normals, buffers[blendShape.normals.mBufferIdx].GetBufferStream(), buffer.data()))
+      const auto            bufferSize = blendShape.normals.mBlob.GetBufferSize();
+      std::vector<uint8_t>  buffer(bufferSize);
+      std::vector<uint32_t> sparseIndices;
+
+      if(ReadAccessor(blendShape.normals, buffers[blendShape.normals.mBufferIdx].GetBufferStream(), buffer.data(), &sparseIndices))
       {
-        blendShape.normals.mBlob.ApplyMinMax(static_cast<uint32_t>(bufferSize / sizeof(Vector3)), reinterpret_cast<float*>(buffer.data()));
+        blendShape.normals.mBlob.ApplyMinMax(static_cast<uint32_t>(bufferSize / sizeof(Vector3)), reinterpret_cast<float*>(buffer.data()), &sparseIndices);
 
         // Calculate the difference with the original mesh, and translate to make all values positive.
-        const Vector3* const deltasBuffer = reinterpret_cast<const Vector3* const>(buffer.data());
-
-        for(uint32_t index = 0u; index < numberOfVertices; ++index)
-        {
-          Vector3& delta = geometryBufferV3[geometryBufferIndex++];
-          delta          = deltasBuffer[index];
-
+        const Vector3* const deltasBuffer  = reinterpret_cast<const Vector3* const>(buffer.data());
+        auto                 ProcessVertex = [&geometryBufferV3, &deltasBuffer, &maxDistanceSquared](uint32_t geometryBufferIndex, uint32_t deltaIndex) {
+          Vector3& delta = geometryBufferV3[geometryBufferIndex] = deltasBuffer[deltaIndex];
           delta.x *= 0.5f;
           delta.y *= 0.5f;
           delta.z *= 0.5f;
@@ -473,6 +523,23 @@ void CalculateGltf2BlendShapes(uint8_t* geometryBuffer, const std::vector<MeshDe
           delta.x += 0.5f;
           delta.y += 0.5f;
           delta.z += 0.5f;
+        };
+
+        if(sparseIndices.empty())
+        {
+          for(uint32_t index = 0u; index < numberOfVertices; ++index)
+          {
+            ProcessVertex(geometryBufferIndex++, index);
+          }
+        }
+        else
+        {
+          std::fill(geometryBufferV3 + geometryBufferIndex, geometryBufferV3 + geometryBufferIndex + numberOfVertices, Vector3(0.5, 0.5, 0.5));
+          for(auto index : sparseIndices)
+          {
+            ProcessVertex(geometryBufferIndex + index, index);
+          }
+          geometryBufferIndex += numberOfVertices;
         }
       }
     }
@@ -483,20 +550,18 @@ void CalculateGltf2BlendShapes(uint8_t* geometryBuffer, const std::vector<MeshDe
                           blendShape.tangents.mBlob.mStride >= sizeof(Vector3)) &&
                          "Blend Shape tangents buffer length not a multiple of element size");
 
-      const auto           bufferSize = blendShape.tangents.mBlob.GetBufferSize();
-      std::vector<uint8_t> buffer(bufferSize);
-      if(ReadAccessor(blendShape.tangents, buffers[blendShape.tangents.mBufferIdx].GetBufferStream(), buffer.data()))
+      const auto            bufferSize = blendShape.tangents.mBlob.GetBufferSize();
+      std::vector<uint8_t>  buffer(bufferSize);
+      std::vector<uint32_t> sparseIndices;
+
+      if(ReadAccessor(blendShape.tangents, buffers[blendShape.tangents.mBufferIdx].GetBufferStream(), buffer.data()), &sparseIndices)
       {
-        blendShape.tangents.mBlob.ApplyMinMax(static_cast<uint32_t>(bufferSize / sizeof(Vector3)), reinterpret_cast<float*>(buffer.data()));
+        blendShape.tangents.mBlob.ApplyMinMax(static_cast<uint32_t>(bufferSize / sizeof(Vector3)), reinterpret_cast<float*>(buffer.data()), &sparseIndices);
 
         // Calculate the difference with the original mesh, and translate to make all values positive.
-        const Vector3* const deltasBuffer = reinterpret_cast<const Vector3* const>(buffer.data());
-
-        for(uint32_t index = 0u; index < numberOfVertices; ++index)
-        {
-          Vector3& delta = geometryBufferV3[geometryBufferIndex++];
-          delta          = deltasBuffer[index];
-
+        const Vector3* const deltasBuffer  = reinterpret_cast<const Vector3* const>(buffer.data());
+        auto                 ProcessVertex = [&geometryBufferV3, &deltasBuffer, &maxDistanceSquared](uint32_t geometryBufferIndex, uint32_t deltaIndex) {
+          Vector3& delta = geometryBufferV3[geometryBufferIndex] = deltasBuffer[deltaIndex];
           delta.x *= 0.5f;
           delta.y *= 0.5f;
           delta.z *= 0.5f;
@@ -504,6 +569,23 @@ void CalculateGltf2BlendShapes(uint8_t* geometryBuffer, const std::vector<MeshDe
           delta.x += 0.5f;
           delta.y += 0.5f;
           delta.z += 0.5f;
+        };
+
+        if(sparseIndices.empty())
+        {
+          for(uint32_t index = 0u; index < numberOfVertices; ++index)
+          {
+            ProcessVertex(geometryBufferIndex++, index);
+          }
+        }
+        else
+        {
+          std::fill(geometryBufferV3 + geometryBufferIndex, geometryBufferV3 + geometryBufferIndex + numberOfVertices, Vector3(0.5, 0.5, 0.5));
+          for(auto index : sparseIndices)
+          {
+            ProcessVertex(geometryBufferIndex + index, index);
+          }
+          geometryBufferIndex += numberOfVertices;
         }
       }
     }
@@ -602,7 +684,7 @@ void MeshDefinition::Blob::ComputeMinMax(std::vector<float>& min, std::vector<fl
   }
 }
 
-void MeshDefinition::Blob::ApplyMinMax(const std::vector<float>& min, const std::vector<float>& max, uint32_t count, float* values)
+void MeshDefinition::Blob::ApplyMinMax(const std::vector<float>& min, const std::vector<float>& max, uint32_t count, float* values, std::vector<uint32_t>* sparseIndices)
 {
   DALI_ASSERT_DEBUG(max.size() == min.size() || max.size() * min.size() == 0);
   const auto numComponents = std::max(min.size(), max.size());
@@ -617,16 +699,31 @@ void MeshDefinition::Blob::ApplyMinMax(const std::vector<float>& min, const std:
     return;
   }
 
-  auto end = values + count * numComponents;
-  while(values != end)
+  // If there are sparse indices then process only relevant data
+  if(sparseIndices && !sparseIndices->empty())
   {
-    auto     nextElement = values + numComponents;
-    uint32_t i           = 0;
-    while(values != nextElement)
+    for(auto elementIndex : *sparseIndices)
     {
-      clampFn(min.data(), max.data(), i, *values);
-      ++values;
-      ++i;
+      auto value = values + (elementIndex * numComponents);
+      for(auto i = 0u; i < numComponents; ++i)
+      {
+        clampFn(min.data(), max.data(), i, *value);
+      }
+    }
+  }
+  else // if there's no sparse indices process all vertices
+  {
+    auto end = values + count * numComponents;
+    while(values != end)
+    {
+      auto     nextElement = values + numComponents;
+      uint32_t i           = 0;
+      while(values != nextElement)
+      {
+        clampFn(min.data(), max.data(), i, *values);
+        ++values;
+        ++i;
+      }
     }
   }
 }
@@ -651,9 +748,9 @@ void MeshDefinition::Blob::ComputeMinMax(uint32_t numComponents, uint32_t count,
   ComputeMinMax(mMin, mMax, numComponents, count, values);
 }
 
-void MeshDefinition::Blob::ApplyMinMax(uint32_t count, float* values) const
+void MeshDefinition::Blob::ApplyMinMax(uint32_t count, float* values, std::vector<uint32_t>* sparseIndices) const
 {
-  ApplyMinMax(mMin, mMax, count, values);
+  ApplyMinMax(mMin, mMax, count, values, sparseIndices);
 }
 
 void MeshDefinition::RawData::Attrib::AttachBuffer(Geometry& g) const
index 570537e..7bf1f7c 100644 (file)
@@ -83,7 +83,7 @@ struct DALI_SCENE3D_API MeshDefinition
 
     static void ComputeMinMax(std::vector<float>& min, std::vector<float>& max, uint32_t numComponents, uint32_t count, const float* values);
 
-    static void ApplyMinMax(const std::vector<float>& min, const std::vector<float>& max, uint32_t count, float* values);
+    static void ApplyMinMax(const std::vector<float>& min, const std::vector<float>& max, uint32_t count, float* values, std::vector<uint32_t>* sparseIndices = nullptr);
 
     Blob() = default;
 
@@ -132,8 +132,10 @@ struct DALI_SCENE3D_API MeshDefinition
      *
      * @param[in] count The number of data.
      * @param[in] values Data for the mesh that min / max values will be applied.
+     * @param[in] sparseIndices Pointer to array of sparse indices (or nullptr if not provided)
+     *
      */
-    void ApplyMinMax(uint32_t count, float* values) const;
+    void ApplyMinMax(uint32_t count, float* values, std::vector<uint32_t>* sparseIndices = nullptr) const;
   };
 
   /**