|
@@ -87,7 +87,6 @@ JPH_IMPLEMENT_SERIALIZABLE_NON_VIRTUAL(SoftBodySharedSettings)
|
|
JPH_ADD_ATTRIBUTE(SoftBodySharedSettings, mVertices)
|
|
JPH_ADD_ATTRIBUTE(SoftBodySharedSettings, mVertices)
|
|
JPH_ADD_ATTRIBUTE(SoftBodySharedSettings, mFaces)
|
|
JPH_ADD_ATTRIBUTE(SoftBodySharedSettings, mFaces)
|
|
JPH_ADD_ATTRIBUTE(SoftBodySharedSettings, mEdgeConstraints)
|
|
JPH_ADD_ATTRIBUTE(SoftBodySharedSettings, mEdgeConstraints)
|
|
- JPH_ADD_ATTRIBUTE(SoftBodySharedSettings, mEdgeGroupEndIndices)
|
|
|
|
JPH_ADD_ATTRIBUTE(SoftBodySharedSettings, mDihedralBendConstraints)
|
|
JPH_ADD_ATTRIBUTE(SoftBodySharedSettings, mDihedralBendConstraints)
|
|
JPH_ADD_ATTRIBUTE(SoftBodySharedSettings, mVolumeConstraints)
|
|
JPH_ADD_ATTRIBUTE(SoftBodySharedSettings, mVolumeConstraints)
|
|
JPH_ADD_ATTRIBUTE(SoftBodySharedSettings, mSkinnedConstraints)
|
|
JPH_ADD_ATTRIBUTE(SoftBodySharedSettings, mSkinnedConstraints)
|
|
@@ -464,39 +463,245 @@ void SoftBodySharedSettings::CalculateSkinnedConstraintNormals()
|
|
|
|
|
|
void SoftBodySharedSettings::Optimize(OptimizationResults &outResults)
|
|
void SoftBodySharedSettings::Optimize(OptimizationResults &outResults)
|
|
{
|
|
{
|
|
- const uint cMaxNumGroups = 32;
|
|
|
|
- const uint cNonParallelGroupIdx = cMaxNumGroups - 1;
|
|
|
|
- const uint cMinimumSize = 2 * SoftBodyUpdateContext::cEdgeConstraintBatch; // There should be at least 2 batches, otherwise there's no point in parallelizing
|
|
|
|
-
|
|
|
|
- // Assign edges to non-overlapping groups
|
|
|
|
- Array<uint32> masks;
|
|
|
|
- masks.resize(mVertices.size(), 0);
|
|
|
|
- Array<uint> edge_groups[cMaxNumGroups];
|
|
|
|
- for (const Edge &e : mEdgeConstraints)
|
|
|
|
|
|
+ // Create a list of connected vertices
|
|
|
|
+ struct Connection
|
|
|
|
+ {
|
|
|
|
+ uint32 mVertex;
|
|
|
|
+ uint32 mCount;
|
|
|
|
+ };
|
|
|
|
+ Array<Array<Connection>> connectivity;
|
|
|
|
+ connectivity.resize(mVertices.size());
|
|
|
|
+ auto add_connection = [&connectivity](uint inV1, uint inV2) {
|
|
|
|
+ for (int i = 0; i < 2; ++i)
|
|
|
|
+ {
|
|
|
|
+ bool found = false;
|
|
|
|
+ for (Connection &c : connectivity[inV1])
|
|
|
|
+ if (c.mVertex == inV2)
|
|
|
|
+ {
|
|
|
|
+ c.mCount++;
|
|
|
|
+ found = true;
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ if (!found)
|
|
|
|
+ connectivity[inV1].push_back({ inV2, 1 });
|
|
|
|
+
|
|
|
|
+ swap(inV1, inV2);
|
|
|
|
+ }
|
|
|
|
+ };
|
|
|
|
+ for (const Edge &c : mEdgeConstraints)
|
|
|
|
+ add_connection(c.mVertex[0], c.mVertex[1]);
|
|
|
|
+ for (const LRA &c : mLRAConstraints)
|
|
|
|
+ add_connection(c.mVertex[0], c.mVertex[1]);
|
|
|
|
+ for (const DihedralBend &c : mDihedralBendConstraints)
|
|
|
|
+ {
|
|
|
|
+ add_connection(c.mVertex[0], c.mVertex[1]);
|
|
|
|
+ add_connection(c.mVertex[0], c.mVertex[2]);
|
|
|
|
+ add_connection(c.mVertex[0], c.mVertex[3]);
|
|
|
|
+ add_connection(c.mVertex[1], c.mVertex[2]);
|
|
|
|
+ add_connection(c.mVertex[1], c.mVertex[3]);
|
|
|
|
+ add_connection(c.mVertex[2], c.mVertex[3]);
|
|
|
|
+ }
|
|
|
|
+ for (const Volume &c : mVolumeConstraints)
|
|
{
|
|
{
|
|
- uint32 &mask1 = masks[e.mVertex[0]];
|
|
|
|
- uint32 &mask2 = masks[e.mVertex[1]];
|
|
|
|
- uint group = min(CountTrailingZeros((~mask1) & (~mask2)), cNonParallelGroupIdx);
|
|
|
|
- uint32 mask = uint32(1U << group);
|
|
|
|
- mask1 |= mask;
|
|
|
|
- mask2 |= mask;
|
|
|
|
- edge_groups[group].push_back(uint(&e - mEdgeConstraints.data()));
|
|
|
|
|
|
+ add_connection(c.mVertex[0], c.mVertex[1]);
|
|
|
|
+ add_connection(c.mVertex[0], c.mVertex[2]);
|
|
|
|
+ add_connection(c.mVertex[0], c.mVertex[3]);
|
|
|
|
+ add_connection(c.mVertex[1], c.mVertex[2]);
|
|
|
|
+ add_connection(c.mVertex[1], c.mVertex[3]);
|
|
|
|
+ add_connection(c.mVertex[2], c.mVertex[3]);
|
|
}
|
|
}
|
|
|
|
|
|
- // Merge groups that are too small into the non-parallel group
|
|
|
|
- for (uint i = 0; i < cNonParallelGroupIdx; ++i)
|
|
|
|
- if (edge_groups[i].size() < cMinimumSize)
|
|
|
|
|
|
+ // Maps each of the vertices to a group index
|
|
|
|
+ Array<int> group_idx;
|
|
|
|
+ group_idx.resize(mVertices.size(), -1);
|
|
|
|
+
|
|
|
|
+ // Which group we are currently filling and its vertices
|
|
|
|
+ int current_group_idx = 0;
|
|
|
|
+ Array<uint> current_group;
|
|
|
|
+
|
|
|
|
+ // Start greedy algorithm to group vertices
|
|
|
|
+ for (;;)
|
|
|
|
+ {
|
|
|
|
+ // Find the bounding box of the ungrouped vertices
|
|
|
|
+ AABox bounds;
|
|
|
|
+ for (uint i = 0; i < (uint)mVertices.size(); ++i)
|
|
|
|
+ if (group_idx[i] == -1)
|
|
|
|
+ bounds.Encapsulate(Vec3(mVertices[i].mPosition));
|
|
|
|
+
|
|
|
|
+ // Determine longest and shortest axis
|
|
|
|
+ Vec3 bounds_size = bounds.GetSize();
|
|
|
|
+ uint max_axis = bounds_size.GetHighestComponentIndex();
|
|
|
|
+ uint min_axis = bounds_size.GetLowestComponentIndex();
|
|
|
|
+ if (min_axis == max_axis)
|
|
|
|
+ min_axis = (min_axis + 1) % 3;
|
|
|
|
+ uint mid_axis = 3 - min_axis - max_axis;
|
|
|
|
+
|
|
|
|
+ // Find the vertex that has the lowest value on the axis with the largest extent
|
|
|
|
+ uint current_vertex = UINT_MAX;
|
|
|
|
+ Float3 current_vertex_position { FLT_MAX, FLT_MAX, FLT_MAX };
|
|
|
|
+ for (uint i = 0; i < (uint)mVertices.size(); ++i)
|
|
|
|
+ if (group_idx[i] == -1)
|
|
|
|
+ {
|
|
|
|
+ const Float3 &vertex_position = mVertices[i].mPosition;
|
|
|
|
+ float max_axis_value = vertex_position[max_axis];
|
|
|
|
+ float mid_axis_value = vertex_position[mid_axis];
|
|
|
|
+ float min_axis_value = vertex_position[min_axis];
|
|
|
|
+
|
|
|
|
+ if (max_axis_value < current_vertex_position[max_axis]
|
|
|
|
+ || (max_axis_value == current_vertex_position[max_axis]
|
|
|
|
+ && (mid_axis_value < current_vertex_position[mid_axis]
|
|
|
|
+ || (mid_axis_value == current_vertex_position[mid_axis]
|
|
|
|
+ && min_axis_value < current_vertex_position[min_axis]))))
|
|
|
|
+ {
|
|
|
|
+ current_vertex_position = mVertices[i].mPosition;
|
|
|
|
+ current_vertex = i;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ if (current_vertex == UINT_MAX)
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ // Initialize the current group with 1 vertex
|
|
|
|
+ current_group.push_back(current_vertex);
|
|
|
|
+ group_idx[current_vertex] = current_group_idx;
|
|
|
|
+
|
|
|
|
+ // Fill up the group
|
|
|
|
+ for (;;)
|
|
{
|
|
{
|
|
- edge_groups[cNonParallelGroupIdx].insert(edge_groups[cNonParallelGroupIdx].end(), edge_groups[i].begin(), edge_groups[i].end());
|
|
|
|
- edge_groups[i].clear();
|
|
|
|
|
|
+ // Find the vertex that is most connected to the current group
|
|
|
|
+ uint best_vertex = UINT_MAX;
|
|
|
|
+ uint best_num_connections = 0;
|
|
|
|
+ float best_dist_sq = FLT_MAX;
|
|
|
|
+ for (uint i = 0; i < (uint)current_group.size(); ++i) // For all vertices in the current group
|
|
|
|
+ for (const Connection &c : connectivity[current_group[i]]) // For all connections to other vertices
|
|
|
|
+ {
|
|
|
|
+ uint v = c.mVertex;
|
|
|
|
+ if (group_idx[v] == -1) // Ungrouped vertices only
|
|
|
|
+ {
|
|
|
|
+ // Count the number of connections to this group
|
|
|
|
+ uint num_connections = 0;
|
|
|
|
+ for (const Connection &v2 : connectivity[v])
|
|
|
|
+ if (group_idx[v2.mVertex] == current_group_idx)
|
|
|
|
+ num_connections += v2.mCount;
|
|
|
|
+
|
|
|
|
+ // Calculate distance to group centroid
|
|
|
|
+ float dist_sq = (Vec3(mVertices[v].mPosition) - Vec3(mVertices[current_group.front()].mPosition)).LengthSq();
|
|
|
|
+
|
|
|
|
+ if (best_vertex == UINT_MAX
|
|
|
|
+ || num_connections > best_num_connections
|
|
|
|
+ || (num_connections == best_num_connections && dist_sq < best_dist_sq))
|
|
|
|
+ {
|
|
|
|
+ best_vertex = v;
|
|
|
|
+ best_num_connections = num_connections;
|
|
|
|
+ best_dist_sq = dist_sq;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Add the best vertex to the current group
|
|
|
|
+ if (best_vertex != UINT_MAX)
|
|
|
|
+ {
|
|
|
|
+ current_group.push_back(best_vertex);
|
|
|
|
+ group_idx[best_vertex] = current_group_idx;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Create a new group?
|
|
|
|
+ if (current_group.size() >= SoftBodyUpdateContext::cVertexConstraintBatch // If full, yes
|
|
|
|
+ || (current_group.size() > SoftBodyUpdateContext::cVertexConstraintBatch / 2 && best_vertex == UINT_MAX)) // If half full and we found no connected vertex, yes
|
|
|
|
+ {
|
|
|
|
+ current_group.clear();
|
|
|
|
+ current_group_idx++;
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // If we didn't find a connected vertex, we need to find a new starting vertex
|
|
|
|
+ if (best_vertex == UINT_MAX)
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // If the last group is more than half full, we'll keep it as a separate group, otherwise we merge it with the 'non parallel' group
|
|
|
|
+ if (current_group.size() > SoftBodyUpdateContext::cVertexConstraintBatch / 2)
|
|
|
|
+ ++current_group_idx;
|
|
|
|
+
|
|
|
|
+ // We no longer need the current group array, free the memory
|
|
|
|
+ current_group.clear();
|
|
|
|
+ current_group.shrink_to_fit();
|
|
|
|
+
|
|
|
|
+ // We're done with the connectivity list, free the memory
|
|
|
|
+ connectivity.clear();
|
|
|
|
+ connectivity.shrink_to_fit();
|
|
|
|
+
|
|
|
|
+ // Assign the constraints to their groups
|
|
|
|
+ struct Group
|
|
|
|
+ {
|
|
|
|
+ uint GetSize() const
|
|
|
|
+ {
|
|
|
|
+ return (uint)mEdgeConstraints.size() + (uint)mLRAConstraints.size() + (uint)mDihedralBendConstraints.size() + (uint)mVolumeConstraints.size();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ Array<uint> mEdgeConstraints;
|
|
|
|
+ Array<uint> mLRAConstraints;
|
|
|
|
+ Array<uint> mDihedralBendConstraints;
|
|
|
|
+ Array<uint> mVolumeConstraints;
|
|
|
|
+ };
|
|
|
|
+ Array<Group> groups;
|
|
|
|
+ groups.resize(current_group_idx + 1); // + non parallel group
|
|
|
|
+ for (const Edge &e : mEdgeConstraints)
|
|
|
|
+ {
|
|
|
|
+ int g1 = group_idx[e.mVertex[0]];
|
|
|
|
+ int g2 = group_idx[e.mVertex[1]];
|
|
|
|
+ JPH_ASSERT(g1 >= 0 && g2 >= 0);
|
|
|
|
+ if (g1 == g2) // In the same group
|
|
|
|
+ groups[g1].mEdgeConstraints.push_back(uint(&e - mEdgeConstraints.data()));
|
|
|
|
+ else // In different groups -> parallel group
|
|
|
|
+ groups.back().mEdgeConstraints.push_back(uint(&e - mEdgeConstraints.data()));
|
|
|
|
+ }
|
|
|
|
+ for (const LRA &l : mLRAConstraints)
|
|
|
|
+ {
|
|
|
|
+ int g1 = group_idx[l.mVertex[0]];
|
|
|
|
+ int g2 = group_idx[l.mVertex[1]];
|
|
|
|
+ JPH_ASSERT(g1 >= 0 && g2 >= 0);
|
|
|
|
+ if (g1 == g2) // In the same group
|
|
|
|
+ groups[g1].mLRAConstraints.push_back(uint(&l - mLRAConstraints.data()));
|
|
|
|
+ else // In different groups -> parallel group
|
|
|
|
+ groups.back().mLRAConstraints.push_back(uint(&l - mLRAConstraints.data()));
|
|
|
|
+ }
|
|
|
|
+ for (const DihedralBend &d : mDihedralBendConstraints)
|
|
|
|
+ {
|
|
|
|
+ int g1 = group_idx[d.mVertex[0]];
|
|
|
|
+ int g2 = group_idx[d.mVertex[1]];
|
|
|
|
+ int g3 = group_idx[d.mVertex[2]];
|
|
|
|
+ int g4 = group_idx[d.mVertex[3]];
|
|
|
|
+ JPH_ASSERT(g1 >= 0 && g2 >= 0 && g3 >= 0 && g4 >= 0);
|
|
|
|
+ if (g1 == g2 && g1 == g3 && g1 == g4) // In the same group
|
|
|
|
+ groups[g1].mDihedralBendConstraints.push_back(uint(&d - mDihedralBendConstraints.data()));
|
|
|
|
+ else // In different groups -> parallel group
|
|
|
|
+ groups.back().mDihedralBendConstraints.push_back(uint(&d - mDihedralBendConstraints.data()));
|
|
|
|
+ }
|
|
|
|
+ for (const Volume &v : mVolumeConstraints)
|
|
|
|
+ {
|
|
|
|
+ int g1 = group_idx[v.mVertex[0]];
|
|
|
|
+ int g2 = group_idx[v.mVertex[1]];
|
|
|
|
+ int g3 = group_idx[v.mVertex[2]];
|
|
|
|
+ int g4 = group_idx[v.mVertex[3]];
|
|
|
|
+ JPH_ASSERT(g1 >= 0 && g2 >= 0 && g3 >= 0 && g4 >= 0);
|
|
|
|
+ if (g1 == g2 && g1 == g3 && g1 == g4) // In the same group
|
|
|
|
+ groups[g1].mVolumeConstraints.push_back(uint(&v - mVolumeConstraints.data()));
|
|
|
|
+ else // In different groups -> parallel group
|
|
|
|
+ groups.back().mVolumeConstraints.push_back(uint(&v - mVolumeConstraints.data()));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Sort the parallel groups from big to small (this means the big groups will be scheduled first and have more time to complete)
|
|
|
|
+ QuickSort(groups.begin(), groups.end() - 1, [](const Group &inLHS, const Group &inRHS) { return inLHS.GetSize() > inRHS.GetSize(); });
|
|
|
|
+
|
|
// Make sure we know the closest kinematic vertex so we can sort
|
|
// Make sure we know the closest kinematic vertex so we can sort
|
|
CalculateClosestKinematic();
|
|
CalculateClosestKinematic();
|
|
|
|
|
|
- // Sort the edge constraints
|
|
|
|
- for (Array<uint> &group : edge_groups)
|
|
|
|
- QuickSort(group.begin(), group.end(), [this](uint inLHS, uint inRHS)
|
|
|
|
|
|
+ // Sort within each group
|
|
|
|
+ for (Group &group : groups)
|
|
|
|
+ {
|
|
|
|
+ // Sort the edge constraints
|
|
|
|
+ QuickSort(group.mEdgeConstraints.begin(), group.mEdgeConstraints.end(), [this](uint inLHS, uint inRHS)
|
|
{
|
|
{
|
|
const Edge &e1 = mEdgeConstraints[inLHS];
|
|
const Edge &e1 = mEdgeConstraints[inLHS];
|
|
const Edge &e2 = mEdgeConstraints[inRHS];
|
|
const Edge &e2 = mEdgeConstraints[inRHS];
|
|
@@ -509,33 +714,37 @@ void SoftBodySharedSettings::Optimize(OptimizationResults &outResults)
|
|
|
|
|
|
// Order the edges so that the ones with the smallest index go first (hoping to get better cache locality when we process the edges).
|
|
// Order the edges so that the ones with the smallest index go first (hoping to get better cache locality when we process the edges).
|
|
// Note we could also re-order the vertices but that would be much more of a burden to the end user
|
|
// Note we could also re-order the vertices but that would be much more of a burden to the end user
|
|
- return e1.GetMinVertexIndex() < e2.GetMinVertexIndex();
|
|
|
|
|
|
+ uint32 m1 = e1.GetMinVertexIndex();
|
|
|
|
+ uint32 m2 = e2.GetMinVertexIndex();
|
|
|
|
+ if (m1 != m2)
|
|
|
|
+ return m1 < m2;
|
|
|
|
+
|
|
|
|
+ return inLHS < inRHS;
|
|
});
|
|
});
|
|
|
|
|
|
- // Assign the edges to groups and reorder them
|
|
|
|
- Array<Edge> temp_edges;
|
|
|
|
- temp_edges.swap(mEdgeConstraints);
|
|
|
|
- mEdgeConstraints.reserve(temp_edges.size());
|
|
|
|
- for (const Array<uint> &group : edge_groups)
|
|
|
|
- if (!group.empty())
|
|
|
|
- {
|
|
|
|
- for (uint idx : group)
|
|
|
|
|
|
+ // Sort the LRA constraints
|
|
|
|
+ QuickSort(group.mLRAConstraints.begin(), group.mLRAConstraints.end(), [this](uint inLHS, uint inRHS)
|
|
{
|
|
{
|
|
- mEdgeConstraints.push_back(temp_edges[idx]);
|
|
|
|
- outResults.mEdgeRemap.push_back(idx);
|
|
|
|
- }
|
|
|
|
- mEdgeGroupEndIndices.push_back((uint)mEdgeConstraints.size());
|
|
|
|
- }
|
|
|
|
|
|
+ const LRA &l1 = mLRAConstraints[inLHS];
|
|
|
|
+ const LRA &l2 = mLRAConstraints[inRHS];
|
|
|
|
|
|
- // If there is no non-parallel group then add an empty group at the end
|
|
|
|
- if (edge_groups[cNonParallelGroupIdx].empty())
|
|
|
|
- mEdgeGroupEndIndices.push_back((uint)mEdgeConstraints.size());
|
|
|
|
|
|
+ // First sort so that the edge with the smallest distance to a kinematic vertex comes first
|
|
|
|
+ float d1 = min(mClosestKinematic[l1.mVertex[0]].mDistance, mClosestKinematic[l1.mVertex[1]].mDistance);
|
|
|
|
+ float d2 = min(mClosestKinematic[l2.mVertex[0]].mDistance, mClosestKinematic[l2.mVertex[1]].mDistance);
|
|
|
|
+ if (d1 != d2)
|
|
|
|
+ return d1 < d2;
|
|
|
|
+
|
|
|
|
+ // Order constraints so that the ones with the smallest index go first
|
|
|
|
+ uint32 m1 = l1.GetMinVertexIndex();
|
|
|
|
+ uint32 m2 = l2.GetMinVertexIndex();
|
|
|
|
+ if (m1 != m2)
|
|
|
|
+ return m1 < m2;
|
|
|
|
+
|
|
|
|
+ return inLHS < inRHS;
|
|
|
|
+ });
|
|
|
|
|
|
- // Sort the bend constraints
|
|
|
|
- outResults.mDihedralBendRemap.resize(mDihedralBendConstraints.size());
|
|
|
|
- for (int i = 0; i < (int)mDihedralBendConstraints.size(); ++i)
|
|
|
|
- outResults.mDihedralBendRemap[i] = i;
|
|
|
|
- QuickSort(outResults.mDihedralBendRemap.begin(), outResults.mDihedralBendRemap.end(), [this](uint inLHS, uint inRHS)
|
|
|
|
|
|
+ // Sort the dihedral bend constraints
|
|
|
|
+ QuickSort(group.mDihedralBendConstraints.begin(), group.mDihedralBendConstraints.end(), [this](uint inLHS, uint inRHS)
|
|
{
|
|
{
|
|
const DihedralBend &b1 = mDihedralBendConstraints[inLHS];
|
|
const DihedralBend &b1 = mDihedralBendConstraints[inLHS];
|
|
const DihedralBend &b2 = mDihedralBendConstraints[inRHS];
|
|
const DihedralBend &b2 = mDihedralBendConstraints[inRHS];
|
|
@@ -551,15 +760,95 @@ void SoftBodySharedSettings::Optimize(OptimizationResults &outResults)
|
|
return d1 < d2;
|
|
return d1 < d2;
|
|
|
|
|
|
// Order constraints so that the ones with the smallest index go first
|
|
// Order constraints so that the ones with the smallest index go first
|
|
- return b1.GetMinVertexIndex() < b2.GetMinVertexIndex();
|
|
|
|
|
|
+ uint32 m1 = b1.GetMinVertexIndex();
|
|
|
|
+ uint32 m2 = b2.GetMinVertexIndex();
|
|
|
|
+ if (m1 != m2)
|
|
|
|
+ return m1 < m2;
|
|
|
|
+
|
|
|
|
+ return inLHS < inRHS;
|
|
});
|
|
});
|
|
|
|
|
|
- // Reorder the bend constraints
|
|
|
|
- Array<DihedralBend> temp_bends;
|
|
|
|
- temp_bends.swap(mDihedralBendConstraints);
|
|
|
|
- mDihedralBendConstraints.reserve(temp_bends.size());
|
|
|
|
- for (uint idx : outResults.mDihedralBendRemap)
|
|
|
|
- mDihedralBendConstraints.push_back(temp_bends[idx]);
|
|
|
|
|
|
+ // Sort the volume constraints
|
|
|
|
+ QuickSort(group.mVolumeConstraints.begin(), group.mVolumeConstraints.end(), [this](uint inLHS, uint inRHS)
|
|
|
|
+ {
|
|
|
|
+ const Volume &v1 = mVolumeConstraints[inLHS];
|
|
|
|
+ const Volume &v2 = mVolumeConstraints[inRHS];
|
|
|
|
+
|
|
|
|
+ // First sort so that the constraint with the smallest distance to a kinematic vertex comes first
|
|
|
|
+ float d1 = min(
|
|
|
|
+ min(mClosestKinematic[v1.mVertex[0]].mDistance, mClosestKinematic[v1.mVertex[1]].mDistance),
|
|
|
|
+ min(mClosestKinematic[v1.mVertex[2]].mDistance, mClosestKinematic[v1.mVertex[3]].mDistance));
|
|
|
|
+ float d2 = min(
|
|
|
|
+ min(mClosestKinematic[v2.mVertex[0]].mDistance, mClosestKinematic[v2.mVertex[1]].mDistance),
|
|
|
|
+ min(mClosestKinematic[v2.mVertex[2]].mDistance, mClosestKinematic[v2.mVertex[3]].mDistance));
|
|
|
|
+ if (d1 != d2)
|
|
|
|
+ return d1 < d2;
|
|
|
|
+
|
|
|
|
+ // Order constraints so that the ones with the smallest index go first
|
|
|
|
+ uint32 m1 = v1.GetMinVertexIndex();
|
|
|
|
+ uint32 m2 = v2.GetMinVertexIndex();
|
|
|
|
+ if (m1 != m2)
|
|
|
|
+ return m1 < m2;
|
|
|
|
+
|
|
|
|
+ return inLHS < inRHS;
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Temporary store constraints as we reorder them
|
|
|
|
+ Array<Edge> temp_edges;
|
|
|
|
+ temp_edges.swap(mEdgeConstraints);
|
|
|
|
+ mEdgeConstraints.reserve(temp_edges.size());
|
|
|
|
+ outResults.mEdgeRemap.reserve(temp_edges.size());
|
|
|
|
+
|
|
|
|
+ Array<LRA> temp_lra;
|
|
|
|
+ temp_lra.swap(mLRAConstraints);
|
|
|
|
+ mLRAConstraints.reserve(temp_lra.size());
|
|
|
|
+ outResults.mLRARemap.reserve(temp_lra.size());
|
|
|
|
+
|
|
|
|
+ Array<DihedralBend> temp_dihedral_bend;
|
|
|
|
+ temp_dihedral_bend.swap(mDihedralBendConstraints);
|
|
|
|
+ mDihedralBendConstraints.reserve(temp_dihedral_bend.size());
|
|
|
|
+ outResults.mDihedralBendRemap.reserve(temp_dihedral_bend.size());
|
|
|
|
+
|
|
|
|
+ Array<Volume> temp_volume;
|
|
|
|
+ temp_volume.swap(mVolumeConstraints);
|
|
|
|
+ mVolumeConstraints.reserve(temp_volume.size());
|
|
|
|
+ outResults.mVolumeRemap.reserve(temp_volume.size());
|
|
|
|
+
|
|
|
|
+ // Finalize update groups
|
|
|
|
+ for (const Group &group : groups)
|
|
|
|
+ {
|
|
|
|
+ // Reorder edge constraints for this group
|
|
|
|
+ for (uint idx : group.mEdgeConstraints)
|
|
|
|
+ {
|
|
|
|
+ mEdgeConstraints.push_back(temp_edges[idx]);
|
|
|
|
+ outResults.mEdgeRemap.push_back(idx);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Reorder LRA constraints for this group
|
|
|
|
+ for (uint idx : group.mLRAConstraints)
|
|
|
|
+ {
|
|
|
|
+ mLRAConstraints.push_back(temp_lra[idx]);
|
|
|
|
+ outResults.mLRARemap.push_back(idx);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Reorder dihedral bend constraints for this group
|
|
|
|
+ for (uint idx : group.mDihedralBendConstraints)
|
|
|
|
+ {
|
|
|
|
+ mDihedralBendConstraints.push_back(temp_dihedral_bend[idx]);
|
|
|
|
+ outResults.mDihedralBendRemap.push_back(idx);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Reorder volume constraints for this group
|
|
|
|
+ for (uint idx : group.mVolumeConstraints)
|
|
|
|
+ {
|
|
|
|
+ mVolumeConstraints.push_back(temp_volume[idx]);
|
|
|
|
+ outResults.mVolumeRemap.push_back(idx);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Store end indices
|
|
|
|
+ mUpdateGroups.push_back({ (uint)mEdgeConstraints.size(), (uint)mLRAConstraints.size(), (uint)mDihedralBendConstraints.size(), (uint)mVolumeConstraints.size() });
|
|
|
|
+ }
|
|
|
|
|
|
// Free closest kinematic buffer
|
|
// Free closest kinematic buffer
|
|
mClosestKinematic.clear();
|
|
mClosestKinematic.clear();
|
|
@@ -572,7 +861,6 @@ Ref<SoftBodySharedSettings> SoftBodySharedSettings::Clone() const
|
|
clone->mVertices = mVertices;
|
|
clone->mVertices = mVertices;
|
|
clone->mFaces = mFaces;
|
|
clone->mFaces = mFaces;
|
|
clone->mEdgeConstraints = mEdgeConstraints;
|
|
clone->mEdgeConstraints = mEdgeConstraints;
|
|
- clone->mEdgeGroupEndIndices = mEdgeGroupEndIndices;
|
|
|
|
clone->mDihedralBendConstraints = mDihedralBendConstraints;
|
|
clone->mDihedralBendConstraints = mDihedralBendConstraints;
|
|
clone->mVolumeConstraints = mVolumeConstraints;
|
|
clone->mVolumeConstraints = mVolumeConstraints;
|
|
clone->mSkinnedConstraints = mSkinnedConstraints;
|
|
clone->mSkinnedConstraints = mSkinnedConstraints;
|
|
@@ -581,6 +869,7 @@ Ref<SoftBodySharedSettings> SoftBodySharedSettings::Clone() const
|
|
clone->mLRAConstraints = mLRAConstraints;
|
|
clone->mLRAConstraints = mLRAConstraints;
|
|
clone->mMaterials = mMaterials;
|
|
clone->mMaterials = mMaterials;
|
|
clone->mVertexRadius = mVertexRadius;
|
|
clone->mVertexRadius = mVertexRadius;
|
|
|
|
+ clone->mUpdateGroups = mUpdateGroups;
|
|
return clone;
|
|
return clone;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -589,13 +878,13 @@ void SoftBodySharedSettings::SaveBinaryState(StreamOut &inStream) const
|
|
inStream.Write(mVertices);
|
|
inStream.Write(mVertices);
|
|
inStream.Write(mFaces);
|
|
inStream.Write(mFaces);
|
|
inStream.Write(mEdgeConstraints);
|
|
inStream.Write(mEdgeConstraints);
|
|
- inStream.Write(mEdgeGroupEndIndices);
|
|
|
|
inStream.Write(mDihedralBendConstraints);
|
|
inStream.Write(mDihedralBendConstraints);
|
|
inStream.Write(mVolumeConstraints);
|
|
inStream.Write(mVolumeConstraints);
|
|
inStream.Write(mSkinnedConstraints);
|
|
inStream.Write(mSkinnedConstraints);
|
|
inStream.Write(mSkinnedConstraintNormals);
|
|
inStream.Write(mSkinnedConstraintNormals);
|
|
inStream.Write(mLRAConstraints);
|
|
inStream.Write(mLRAConstraints);
|
|
inStream.Write(mVertexRadius);
|
|
inStream.Write(mVertexRadius);
|
|
|
|
+ inStream.Write(mUpdateGroups);
|
|
|
|
|
|
// Can't write mInvBindMatrices directly because the class contains padding
|
|
// Can't write mInvBindMatrices directly because the class contains padding
|
|
inStream.Write(mInvBindMatrices, [](const InvBind &inElement, StreamOut &inS) {
|
|
inStream.Write(mInvBindMatrices, [](const InvBind &inElement, StreamOut &inS) {
|
|
@@ -609,13 +898,13 @@ void SoftBodySharedSettings::RestoreBinaryState(StreamIn &inStream)
|
|
inStream.Read(mVertices);
|
|
inStream.Read(mVertices);
|
|
inStream.Read(mFaces);
|
|
inStream.Read(mFaces);
|
|
inStream.Read(mEdgeConstraints);
|
|
inStream.Read(mEdgeConstraints);
|
|
- inStream.Read(mEdgeGroupEndIndices);
|
|
|
|
inStream.Read(mDihedralBendConstraints);
|
|
inStream.Read(mDihedralBendConstraints);
|
|
inStream.Read(mVolumeConstraints);
|
|
inStream.Read(mVolumeConstraints);
|
|
inStream.Read(mSkinnedConstraints);
|
|
inStream.Read(mSkinnedConstraints);
|
|
inStream.Read(mSkinnedConstraintNormals);
|
|
inStream.Read(mSkinnedConstraintNormals);
|
|
inStream.Read(mLRAConstraints);
|
|
inStream.Read(mLRAConstraints);
|
|
inStream.Read(mVertexRadius);
|
|
inStream.Read(mVertexRadius);
|
|
|
|
+ inStream.Read(mUpdateGroups);
|
|
|
|
|
|
inStream.Read(mInvBindMatrices, [](StreamIn &inS, InvBind &outElement) {
|
|
inStream.Read(mInvBindMatrices, [](StreamIn &inS, InvBind &outElement) {
|
|
inS.Read(outElement.mJointIndex);
|
|
inS.Read(outElement.mJointIndex);
|