Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/SoftBody/SoftBodySharedSettings.cpp
9912 views
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)1// SPDX-FileCopyrightText: 2023 Jorrit Rouwe2// SPDX-License-Identifier: MIT34#include <Jolt/Jolt.h>56#include <Jolt/Physics/SoftBody/SoftBodySharedSettings.h>7#include <Jolt/Physics/SoftBody/SoftBodyUpdateContext.h>8#include <Jolt/ObjectStream/TypeDeclarations.h>9#include <Jolt/Core/StreamIn.h>10#include <Jolt/Core/StreamOut.h>11#include <Jolt/Core/QuickSort.h>12#include <Jolt/Core/UnorderedMap.h>13#include <Jolt/Core/UnorderedSet.h>14#include <Jolt/Core/BinaryHeap.h>1516JPH_NAMESPACE_BEGIN1718JPH_IMPLEMENT_SERIALIZABLE_NON_VIRTUAL(SoftBodySharedSettings::Vertex)19{20JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::Vertex, mPosition)21JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::Vertex, mVelocity)22JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::Vertex, mInvMass)23}2425JPH_IMPLEMENT_SERIALIZABLE_NON_VIRTUAL(SoftBodySharedSettings::Face)26{27JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::Face, mVertex)28JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::Face, mMaterialIndex)29}3031JPH_IMPLEMENT_SERIALIZABLE_NON_VIRTUAL(SoftBodySharedSettings::Edge)32{33JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::Edge, mVertex)34JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::Edge, mRestLength)35JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::Edge, mCompliance)36}3738JPH_IMPLEMENT_SERIALIZABLE_NON_VIRTUAL(SoftBodySharedSettings::DihedralBend)39{40JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::DihedralBend, mVertex)41JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::DihedralBend, mCompliance)42JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::DihedralBend, mInitialAngle)43}4445JPH_IMPLEMENT_SERIALIZABLE_NON_VIRTUAL(SoftBodySharedSettings::Volume)46{47JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::Volume, mVertex)48JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::Volume, mSixRestVolume)49JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::Volume, mCompliance)50}5152JPH_IMPLEMENT_SERIALIZABLE_NON_VIRTUAL(SoftBodySharedSettings::InvBind)53{54JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::InvBind, mJointIndex)55JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::InvBind, mInvBind)56}5758JPH_IMPLEMENT_SERIALIZABLE_NON_VIRTUAL(SoftBodySharedSettings::SkinWeight)59{60JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::SkinWeight, mInvBindIndex)61JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::SkinWeight, mWeight)62}6364JPH_IMPLEMENT_SERIALIZABLE_NON_VIRTUAL(SoftBodySharedSettings::Skinned)65{66JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::Skinned, mVertex)67JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::Skinned, mWeights)68JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::Skinned, mMaxDistance)69JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::Skinned, mBackStopDistance)70JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::Skinned, mBackStopRadius)71}7273JPH_IMPLEMENT_SERIALIZABLE_NON_VIRTUAL(SoftBodySharedSettings::LRA)74{75JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::LRA, mVertex)76JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::LRA, mMaxDistance)77}7879JPH_IMPLEMENT_SERIALIZABLE_NON_VIRTUAL(SoftBodySharedSettings)80{81JPH_ADD_ATTRIBUTE(SoftBodySharedSettings, mVertices)82JPH_ADD_ATTRIBUTE(SoftBodySharedSettings, mFaces)83JPH_ADD_ATTRIBUTE(SoftBodySharedSettings, mEdgeConstraints)84JPH_ADD_ATTRIBUTE(SoftBodySharedSettings, mDihedralBendConstraints)85JPH_ADD_ATTRIBUTE(SoftBodySharedSettings, mVolumeConstraints)86JPH_ADD_ATTRIBUTE(SoftBodySharedSettings, mSkinnedConstraints)87JPH_ADD_ATTRIBUTE(SoftBodySharedSettings, mInvBindMatrices)88JPH_ADD_ATTRIBUTE(SoftBodySharedSettings, mLRAConstraints)89JPH_ADD_ATTRIBUTE(SoftBodySharedSettings, mMaterials)90JPH_ADD_ATTRIBUTE(SoftBodySharedSettings, mVertexRadius)91}9293void SoftBodySharedSettings::CalculateClosestKinematic()94{95// Check if we already calculated this96if (!mClosestKinematic.empty())97return;9899// Reserve output size100mClosestKinematic.resize(mVertices.size());101102// Create a list of connected vertices103Array<Array<uint32>> connectivity;104connectivity.resize(mVertices.size());105for (const Edge &e : mEdgeConstraints)106{107connectivity[e.mVertex[0]].push_back(e.mVertex[1]);108connectivity[e.mVertex[1]].push_back(e.mVertex[0]);109}110111// Use Dijkstra's algorithm to find the closest kinematic vertex for each vertex112// See: https://en.wikipedia.org/wiki/Dijkstra's_algorithm113//114// An element in the open list115struct Open116{117// Order so that we get the shortest distance first118bool operator < (const Open &inRHS) const119{120return mDistance > inRHS.mDistance;121}122123uint32 mVertex;124float mDistance;125};126127// Start with all kinematic elements128Array<Open> to_visit;129for (uint32 v = 0; v < mVertices.size(); ++v)130if (mVertices[v].mInvMass == 0.0f)131{132mClosestKinematic[v].mVertex = v;133mClosestKinematic[v].mDistance = 0.0f;134to_visit.push_back({ v, 0.0f });135BinaryHeapPush(to_visit.begin(), to_visit.end(), std::less<Open> { });136}137138// Visit all vertices remembering the closest kinematic vertex and its distance139JPH_IF_ENABLE_ASSERTS(float last_closest = 0.0f;)140while (!to_visit.empty())141{142// Pop element from the open list143BinaryHeapPop(to_visit.begin(), to_visit.end(), std::less<Open> { });144Open current = to_visit.back();145to_visit.pop_back();146JPH_ASSERT(current.mDistance >= last_closest);147JPH_IF_ENABLE_ASSERTS(last_closest = current.mDistance;)148149// Loop through all of its connected vertices150for (uint32 v : connectivity[current.mVertex])151{152// Calculate distance from the current vertex to this target vertex and check if it is smaller153float new_distance = current.mDistance + (Vec3(mVertices[v].mPosition) - Vec3(mVertices[current.mVertex].mPosition)).Length();154if (new_distance < mClosestKinematic[v].mDistance)155{156// Remember new closest vertex157mClosestKinematic[v].mVertex = mClosestKinematic[current.mVertex].mVertex;158mClosestKinematic[v].mDistance = new_distance;159to_visit.push_back({ v, new_distance });160BinaryHeapPush(to_visit.begin(), to_visit.end(), std::less<Open> { });161}162}163}164}165166void SoftBodySharedSettings::CreateConstraints(const VertexAttributes *inVertexAttributes, uint inVertexAttributesLength, EBendType inBendType, float inAngleTolerance)167{168struct EdgeHelper169{170uint32 mVertex[2];171uint32 mEdgeIdx;172};173174// Create list of all edges175Array<EdgeHelper> edges;176edges.reserve(mFaces.size() * 3);177for (const Face &f : mFaces)178for (int i = 0; i < 3; ++i)179{180uint32 v0 = f.mVertex[i];181uint32 v1 = f.mVertex[(i + 1) % 3];182183EdgeHelper e;184e.mVertex[0] = min(v0, v1);185e.mVertex[1] = max(v0, v1);186e.mEdgeIdx = uint32(&f - mFaces.data()) * 3 + i;187edges.push_back(e);188}189190// Sort the edges191QuickSort(edges.begin(), edges.end(), [](const EdgeHelper &inLHS, const EdgeHelper &inRHS) { return inLHS.mVertex[0] < inRHS.mVertex[0] || (inLHS.mVertex[0] == inRHS.mVertex[0] && inLHS.mVertex[1] < inRHS.mVertex[1]); });192193// Only add edges if one of the vertices is movable194auto add_edge = [this](uint32 inVtx1, uint32 inVtx2, float inCompliance1, float inCompliance2) {195if ((mVertices[inVtx1].mInvMass > 0.0f || mVertices[inVtx2].mInvMass > 0.0f)196&& inCompliance1 < FLT_MAX && inCompliance2 < FLT_MAX)197{198Edge temp_edge;199temp_edge.mVertex[0] = inVtx1;200temp_edge.mVertex[1] = inVtx2;201temp_edge.mCompliance = 0.5f * (inCompliance1 + inCompliance2);202temp_edge.mRestLength = (Vec3(mVertices[inVtx2].mPosition) - Vec3(mVertices[inVtx1].mPosition)).Length();203JPH_ASSERT(temp_edge.mRestLength > 0.0f);204mEdgeConstraints.push_back(temp_edge);205}206};207208// Helper function to get the attributes of a vertex209auto attr = [inVertexAttributes, inVertexAttributesLength](uint32 inVertex) {210return inVertexAttributes[min(inVertex, inVertexAttributesLength - 1)];211};212213// Create the constraints214float sq_sin_tolerance = Square(Sin(inAngleTolerance));215float sq_cos_tolerance = Square(Cos(inAngleTolerance));216mEdgeConstraints.clear();217mEdgeConstraints.reserve(edges.size());218for (Array<EdgeHelper>::size_type i = 0; i < edges.size(); ++i)219{220const EdgeHelper &e0 = edges[i];221222// Get attributes for the vertices of the edge223const VertexAttributes &a0 = attr(e0.mVertex[0]);224const VertexAttributes &a1 = attr(e0.mVertex[1]);225226// Flag that indicates if this edge is a shear edge (if 2 triangles form a quad-like shape and this edge is on the diagonal)227bool is_shear = false;228229// Test if there are any shared edges230for (Array<EdgeHelper>::size_type j = i + 1; j < edges.size(); ++j)231{232const EdgeHelper &e1 = edges[j];233if (e0.mVertex[0] == e1.mVertex[0] && e0.mVertex[1] == e1.mVertex[1])234{235// Get opposing vertices236const Face &f0 = mFaces[e0.mEdgeIdx / 3];237const Face &f1 = mFaces[e1.mEdgeIdx / 3];238uint32 vopposite0 = f0.mVertex[(e0.mEdgeIdx + 2) % 3];239uint32 vopposite1 = f1.mVertex[(e1.mEdgeIdx + 2) % 3];240const VertexAttributes &a_opposite0 = attr(vopposite0);241const VertexAttributes &a_opposite1 = attr(vopposite1);242243// Faces should be roughly in a plane244Vec3 n0 = (Vec3(mVertices[f0.mVertex[2]].mPosition) - Vec3(mVertices[f0.mVertex[0]].mPosition)).Cross(Vec3(mVertices[f0.mVertex[1]].mPosition) - Vec3(mVertices[f0.mVertex[0]].mPosition));245Vec3 n1 = (Vec3(mVertices[f1.mVertex[2]].mPosition) - Vec3(mVertices[f1.mVertex[0]].mPosition)).Cross(Vec3(mVertices[f1.mVertex[1]].mPosition) - Vec3(mVertices[f1.mVertex[0]].mPosition));246if (Square(n0.Dot(n1)) > sq_cos_tolerance * n0.LengthSq() * n1.LengthSq())247{248// Faces should approximately form a quad249Vec3 e0_dir = Vec3(mVertices[vopposite0].mPosition) - Vec3(mVertices[e0.mVertex[0]].mPosition);250Vec3 e1_dir = Vec3(mVertices[vopposite1].mPosition) - Vec3(mVertices[e0.mVertex[0]].mPosition);251if (Square(e0_dir.Dot(e1_dir)) < sq_sin_tolerance * e0_dir.LengthSq() * e1_dir.LengthSq())252{253// Shear constraint254add_edge(vopposite0, vopposite1, a_opposite0.mShearCompliance, a_opposite1.mShearCompliance);255is_shear = true;256}257}258259// Bend constraint260switch (inBendType)261{262case EBendType::None:263// Do nothing264break;265266case EBendType::Distance:267// Create an edge constraint to represent the bend constraint268// Use the bend compliance of the shared edge269if (!is_shear)270add_edge(vopposite0, vopposite1, a0.mBendCompliance, a1.mBendCompliance);271break;272273case EBendType::Dihedral:274// Test if both opposite vertices are free to move275if ((mVertices[vopposite0].mInvMass > 0.0f || mVertices[vopposite1].mInvMass > 0.0f)276&& a0.mBendCompliance < FLT_MAX && a1.mBendCompliance < FLT_MAX)277{278// Create a bend constraint279// Use the bend compliance of the shared edge280mDihedralBendConstraints.emplace_back(e0.mVertex[0], e0.mVertex[1], vopposite0, vopposite1, 0.5f * (a0.mBendCompliance + a1.mBendCompliance));281}282break;283}284}285else286{287// Start iterating from the first non-shared edge288i = j - 1;289break;290}291}292293// Create a edge constraint for the current edge294add_edge(e0.mVertex[0], e0.mVertex[1], is_shear? a0.mShearCompliance : a0.mCompliance, is_shear? a1.mShearCompliance : a1.mCompliance);295}296mEdgeConstraints.shrink_to_fit();297298// Calculate the initial angle for all bend constraints299CalculateBendConstraintConstants();300301// Check if any vertices have LRA constraints302bool has_lra_constraints = false;303for (const VertexAttributes *va = inVertexAttributes; va < inVertexAttributes + inVertexAttributesLength; ++va)304if (va->mLRAType != ELRAType::None)305{306has_lra_constraints = true;307break;308}309if (has_lra_constraints)310{311// Ensure we have calculated the closest kinematic vertex for each vertex312CalculateClosestKinematic();313314// Find non-kinematic vertices315for (uint32 v = 0; v < (uint32)mVertices.size(); ++v)316if (mVertices[v].mInvMass > 0.0f)317{318// Check if a closest vertex was found319uint32 closest = mClosestKinematic[v].mVertex;320if (closest != 0xffffffff)321{322// Check which LRA constraint to create323const VertexAttributes &va = attr(v);324switch (va.mLRAType)325{326case ELRAType::None:327break;328329case ELRAType::EuclideanDistance:330mLRAConstraints.emplace_back(closest, v, va.mLRAMaxDistanceMultiplier * (Vec3(mVertices[closest].mPosition) - Vec3(mVertices[v].mPosition)).Length());331break;332333case ELRAType::GeodesicDistance:334mLRAConstraints.emplace_back(closest, v, va.mLRAMaxDistanceMultiplier * mClosestKinematic[v].mDistance);335break;336}337}338}339}340}341342void SoftBodySharedSettings::CalculateEdgeLengths()343{344for (Edge &e : mEdgeConstraints)345{346e.mRestLength = (Vec3(mVertices[e.mVertex[1]].mPosition) - Vec3(mVertices[e.mVertex[0]].mPosition)).Length();347JPH_ASSERT(e.mRestLength > 0.0f);348}349}350351void SoftBodySharedSettings::CalculateLRALengths(float inMaxDistanceMultiplier)352{353for (LRA &l : mLRAConstraints)354{355l.mMaxDistance = inMaxDistanceMultiplier * (Vec3(mVertices[l.mVertex[1]].mPosition) - Vec3(mVertices[l.mVertex[0]].mPosition)).Length();356JPH_ASSERT(l.mMaxDistance > 0.0f);357}358}359360void SoftBodySharedSettings::CalculateBendConstraintConstants()361{362for (DihedralBend &b : mDihedralBendConstraints)363{364// Get positions365Vec3 x0 = Vec3(mVertices[b.mVertex[0]].mPosition);366Vec3 x1 = Vec3(mVertices[b.mVertex[1]].mPosition);367Vec3 x2 = Vec3(mVertices[b.mVertex[2]].mPosition);368Vec3 x3 = Vec3(mVertices[b.mVertex[3]].mPosition);369370/*371x2372e1/ \e3373/ \374x0----x1375\ e0 /376e2\ /e4377x3378*/379380// Calculate edges381Vec3 e0 = x1 - x0;382Vec3 e1 = x2 - x0;383Vec3 e2 = x3 - x0;384385// Normals of both triangles386Vec3 n1 = e0.Cross(e1);387Vec3 n2 = e2.Cross(e0);388float denom = sqrt(n1.LengthSq() * n2.LengthSq());389if (denom < 1.0e-12f)390b.mInitialAngle = 0.0f;391else392{393float sign = Sign(n2.Cross(n1).Dot(e0));394b.mInitialAngle = sign * ACosApproximate(n1.Dot(n2) / denom); // Runtime uses the approximation too395}396}397}398399void SoftBodySharedSettings::CalculateVolumeConstraintVolumes()400{401for (Volume &v : mVolumeConstraints)402{403Vec3 x1(mVertices[v.mVertex[0]].mPosition);404Vec3 x2(mVertices[v.mVertex[1]].mPosition);405Vec3 x3(mVertices[v.mVertex[2]].mPosition);406Vec3 x4(mVertices[v.mVertex[3]].mPosition);407408Vec3 x1x2 = x2 - x1;409Vec3 x1x3 = x3 - x1;410Vec3 x1x4 = x4 - x1;411412v.mSixRestVolume = abs(x1x2.Cross(x1x3).Dot(x1x4));413}414}415416void SoftBodySharedSettings::CalculateSkinnedConstraintNormals()417{418// Clear any previous results419mSkinnedConstraintNormals.clear();420421// If there are no skinned constraints, we're done422if (mSkinnedConstraints.empty())423return;424425// First collect all vertices that are skinned426using VertexIndexSet = UnorderedSet<uint32>;427VertexIndexSet skinned_vertices;428skinned_vertices.reserve(VertexIndexSet::size_type(mSkinnedConstraints.size()));429for (const Skinned &s : mSkinnedConstraints)430skinned_vertices.insert(s.mVertex);431432// Now collect all faces that connect only to skinned vertices433using ConnectedFacesMap = UnorderedMap<uint32, VertexIndexSet>;434ConnectedFacesMap connected_faces;435connected_faces.reserve(ConnectedFacesMap::size_type(mVertices.size()));436for (const Face &f : mFaces)437{438// Must connect to only skinned vertices439bool valid = true;440for (uint32 v : f.mVertex)441valid &= skinned_vertices.find(v) != skinned_vertices.end();442if (!valid)443continue;444445// Store faces that connect to vertices446for (uint32 v : f.mVertex)447connected_faces[v].insert(uint32(&f - mFaces.data()));448}449450// Populate the list of connecting faces per skinned vertex451mSkinnedConstraintNormals.reserve(mFaces.size());452for (Skinned &s : mSkinnedConstraints)453{454uint32 start = uint32(mSkinnedConstraintNormals.size());455JPH_ASSERT((start >> 24) == 0);456ConnectedFacesMap::const_iterator connected_faces_it = connected_faces.find(s.mVertex);457if (connected_faces_it != connected_faces.cend())458{459const VertexIndexSet &faces = connected_faces_it->second;460uint32 num = uint32(faces.size());461JPH_ASSERT(num < 256);462mSkinnedConstraintNormals.insert(mSkinnedConstraintNormals.end(), faces.begin(), faces.end());463QuickSort(mSkinnedConstraintNormals.begin() + start, mSkinnedConstraintNormals.begin() + start + num);464s.mNormalInfo = start + (num << 24);465}466else467s.mNormalInfo = 0;468}469mSkinnedConstraintNormals.shrink_to_fit();470}471472void SoftBodySharedSettings::Optimize(OptimizationResults &outResults)473{474// Clear any previous results475mUpdateGroups.clear();476477// Create a list of connected vertices478struct Connection479{480uint32 mVertex;481uint32 mCount;482};483Array<Array<Connection>> connectivity;484connectivity.resize(mVertices.size());485auto add_connection = [&connectivity](uint inV1, uint inV2) {486for (int i = 0; i < 2; ++i)487{488bool found = false;489for (Connection &c : connectivity[inV1])490if (c.mVertex == inV2)491{492c.mCount++;493found = true;494break;495}496if (!found)497connectivity[inV1].push_back({ inV2, 1 });498499std::swap(inV1, inV2);500}501};502for (const Edge &c : mEdgeConstraints)503add_connection(c.mVertex[0], c.mVertex[1]);504for (const LRA &c : mLRAConstraints)505add_connection(c.mVertex[0], c.mVertex[1]);506for (const DihedralBend &c : mDihedralBendConstraints)507{508add_connection(c.mVertex[0], c.mVertex[1]);509add_connection(c.mVertex[0], c.mVertex[2]);510add_connection(c.mVertex[0], c.mVertex[3]);511add_connection(c.mVertex[1], c.mVertex[2]);512add_connection(c.mVertex[1], c.mVertex[3]);513add_connection(c.mVertex[2], c.mVertex[3]);514}515for (const Volume &c : mVolumeConstraints)516{517add_connection(c.mVertex[0], c.mVertex[1]);518add_connection(c.mVertex[0], c.mVertex[2]);519add_connection(c.mVertex[0], c.mVertex[3]);520add_connection(c.mVertex[1], c.mVertex[2]);521add_connection(c.mVertex[1], c.mVertex[3]);522add_connection(c.mVertex[2], c.mVertex[3]);523}524// Skinned constraints only update 1 vertex, so we don't need special logic here525526// Maps each of the vertices to a group index527Array<int> group_idx;528group_idx.resize(mVertices.size(), -1);529530// Which group we are currently filling and its vertices531int current_group_idx = 0;532Array<uint> current_group;533534// Start greedy algorithm to group vertices535for (;;)536{537// Find the bounding box of the ungrouped vertices538AABox bounds;539for (uint i = 0; i < (uint)mVertices.size(); ++i)540if (group_idx[i] == -1)541bounds.Encapsulate(Vec3(mVertices[i].mPosition));542543// If the bounds are invalid, it means that there were no ungrouped vertices544if (!bounds.IsValid())545break;546547// Determine longest and shortest axis548Vec3 bounds_size = bounds.GetSize();549uint max_axis = bounds_size.GetHighestComponentIndex();550uint min_axis = bounds_size.GetLowestComponentIndex();551if (min_axis == max_axis)552min_axis = (min_axis + 1) % 3;553uint mid_axis = 3 - min_axis - max_axis;554555// Find the vertex that has the lowest value on the axis with the largest extent556uint current_vertex = UINT_MAX;557Float3 current_vertex_position { FLT_MAX, FLT_MAX, FLT_MAX };558for (uint i = 0; i < (uint)mVertices.size(); ++i)559if (group_idx[i] == -1)560{561const Float3 &vertex_position = mVertices[i].mPosition;562float max_axis_value = vertex_position[max_axis];563float mid_axis_value = vertex_position[mid_axis];564float min_axis_value = vertex_position[min_axis];565566if (max_axis_value < current_vertex_position[max_axis]567|| (max_axis_value == current_vertex_position[max_axis]568&& (mid_axis_value < current_vertex_position[mid_axis]569|| (mid_axis_value == current_vertex_position[mid_axis]570&& min_axis_value < current_vertex_position[min_axis]))))571{572current_vertex_position = mVertices[i].mPosition;573current_vertex = i;574}575}576if (current_vertex == UINT_MAX)577break;578579// Initialize the current group with 1 vertex580current_group.push_back(current_vertex);581group_idx[current_vertex] = current_group_idx;582583// Fill up the group584for (;;)585{586// Find the vertex that is most connected to the current group587uint best_vertex = UINT_MAX;588uint best_num_connections = 0;589float best_dist_sq = FLT_MAX;590for (uint i = 0; i < (uint)current_group.size(); ++i) // For all vertices in the current group591for (const Connection &c : connectivity[current_group[i]]) // For all connections to other vertices592{593uint v = c.mVertex;594if (group_idx[v] == -1) // Ungrouped vertices only595{596// Count the number of connections to this group597uint num_connections = 0;598for (const Connection &v2 : connectivity[v])599if (group_idx[v2.mVertex] == current_group_idx)600num_connections += v2.mCount;601602// Calculate distance to group centroid603float dist_sq = (Vec3(mVertices[v].mPosition) - Vec3(mVertices[current_group.front()].mPosition)).LengthSq();604605if (best_vertex == UINT_MAX606|| num_connections > best_num_connections607|| (num_connections == best_num_connections && dist_sq < best_dist_sq))608{609best_vertex = v;610best_num_connections = num_connections;611best_dist_sq = dist_sq;612}613}614}615616// Add the best vertex to the current group617if (best_vertex != UINT_MAX)618{619current_group.push_back(best_vertex);620group_idx[best_vertex] = current_group_idx;621}622623// Create a new group?624if (current_group.size() >= SoftBodyUpdateContext::cVertexConstraintBatch // If full, yes625|| (current_group.size() > SoftBodyUpdateContext::cVertexConstraintBatch / 2 && best_vertex == UINT_MAX)) // If half full and we found no connected vertex, yes626{627current_group.clear();628current_group_idx++;629break;630}631632// If we didn't find a connected vertex, we need to find a new starting vertex633if (best_vertex == UINT_MAX)634break;635}636}637638// 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' group639if (current_group.size() > SoftBodyUpdateContext::cVertexConstraintBatch / 2)640++current_group_idx;641642// We no longer need the current group array, free the memory643current_group.clear();644current_group.shrink_to_fit();645646// We're done with the connectivity list, free the memory647connectivity.clear();648connectivity.shrink_to_fit();649650// Assign the constraints to their groups651struct Group652{653uint GetSize() const654{655return (uint)mEdgeConstraints.size() + (uint)mLRAConstraints.size() + (uint)mDihedralBendConstraints.size() + (uint)mVolumeConstraints.size() + (uint)mSkinnedConstraints.size();656}657658Array<uint> mEdgeConstraints;659Array<uint> mLRAConstraints;660Array<uint> mDihedralBendConstraints;661Array<uint> mVolumeConstraints;662Array<uint> mSkinnedConstraints;663};664Array<Group> groups;665groups.resize(current_group_idx + 1); // + non parallel group666for (const Edge &e : mEdgeConstraints)667{668int g1 = group_idx[e.mVertex[0]];669int g2 = group_idx[e.mVertex[1]];670JPH_ASSERT(g1 >= 0 && g2 >= 0);671if (g1 == g2) // In the same group672groups[g1].mEdgeConstraints.push_back(uint(&e - mEdgeConstraints.data()));673else // In different groups -> parallel group674groups.back().mEdgeConstraints.push_back(uint(&e - mEdgeConstraints.data()));675}676for (const LRA &l : mLRAConstraints)677{678int g1 = group_idx[l.mVertex[0]];679int g2 = group_idx[l.mVertex[1]];680JPH_ASSERT(g1 >= 0 && g2 >= 0);681if (g1 == g2) // In the same group682groups[g1].mLRAConstraints.push_back(uint(&l - mLRAConstraints.data()));683else // In different groups -> parallel group684groups.back().mLRAConstraints.push_back(uint(&l - mLRAConstraints.data()));685}686for (const DihedralBend &d : mDihedralBendConstraints)687{688int g1 = group_idx[d.mVertex[0]];689int g2 = group_idx[d.mVertex[1]];690int g3 = group_idx[d.mVertex[2]];691int g4 = group_idx[d.mVertex[3]];692JPH_ASSERT(g1 >= 0 && g2 >= 0 && g3 >= 0 && g4 >= 0);693if (g1 == g2 && g1 == g3 && g1 == g4) // In the same group694groups[g1].mDihedralBendConstraints.push_back(uint(&d - mDihedralBendConstraints.data()));695else // In different groups -> parallel group696groups.back().mDihedralBendConstraints.push_back(uint(&d - mDihedralBendConstraints.data()));697}698for (const Volume &v : mVolumeConstraints)699{700int g1 = group_idx[v.mVertex[0]];701int g2 = group_idx[v.mVertex[1]];702int g3 = group_idx[v.mVertex[2]];703int g4 = group_idx[v.mVertex[3]];704JPH_ASSERT(g1 >= 0 && g2 >= 0 && g3 >= 0 && g4 >= 0);705if (g1 == g2 && g1 == g3 && g1 == g4) // In the same group706groups[g1].mVolumeConstraints.push_back(uint(&v - mVolumeConstraints.data()));707else // In different groups -> parallel group708groups.back().mVolumeConstraints.push_back(uint(&v - mVolumeConstraints.data()));709}710for (const Skinned &s : mSkinnedConstraints)711{712int g1 = group_idx[s.mVertex];713JPH_ASSERT(g1 >= 0);714groups[g1].mSkinnedConstraints.push_back(uint(&s - mSkinnedConstraints.data()));715}716717// Sort the parallel groups from big to small (this means the big groups will be scheduled first and have more time to complete)718QuickSort(groups.begin(), groups.end() - 1, [](const Group &inLHS, const Group &inRHS) { return inLHS.GetSize() > inRHS.GetSize(); });719720// Make sure we know the closest kinematic vertex so we can sort721CalculateClosestKinematic();722723// Sort within each group724for (Group &group : groups)725{726// Sort the edge constraints727QuickSort(group.mEdgeConstraints.begin(), group.mEdgeConstraints.end(), [this](uint inLHS, uint inRHS)728{729const Edge &e1 = mEdgeConstraints[inLHS];730const Edge &e2 = mEdgeConstraints[inRHS];731732// First sort so that the edge with the smallest distance to a kinematic vertex comes first733float d1 = min(mClosestKinematic[e1.mVertex[0]].mDistance, mClosestKinematic[e1.mVertex[1]].mDistance);734float d2 = min(mClosestKinematic[e2.mVertex[0]].mDistance, mClosestKinematic[e2.mVertex[1]].mDistance);735if (d1 != d2)736return d1 < d2;737738// Order the edges so that the ones with the smallest index go first (hoping to get better cache locality when we process the edges).739// Note we could also re-order the vertices but that would be much more of a burden to the end user740uint32 m1 = e1.GetMinVertexIndex();741uint32 m2 = e2.GetMinVertexIndex();742if (m1 != m2)743return m1 < m2;744745return inLHS < inRHS;746});747748// Sort the LRA constraints749QuickSort(group.mLRAConstraints.begin(), group.mLRAConstraints.end(), [this](uint inLHS, uint inRHS)750{751const LRA &l1 = mLRAConstraints[inLHS];752const LRA &l2 = mLRAConstraints[inRHS];753754// First sort so that the longest constraint comes first (meaning the shortest constraint has the most influence on the end result)755// Most of the time there will be a single LRA constraint per vertex and since the LRA constraint only modifies a single vertex,756// updating one constraint will not violate another constraint.757if (l1.mMaxDistance != l2.mMaxDistance)758return l1.mMaxDistance > l2.mMaxDistance;759760// Order constraints so that the ones with the smallest index go first761uint32 m1 = l1.GetMinVertexIndex();762uint32 m2 = l2.GetMinVertexIndex();763if (m1 != m2)764return m1 < m2;765766return inLHS < inRHS;767});768769// Sort the dihedral bend constraints770QuickSort(group.mDihedralBendConstraints.begin(), group.mDihedralBendConstraints.end(), [this](uint inLHS, uint inRHS)771{772const DihedralBend &b1 = mDihedralBendConstraints[inLHS];773const DihedralBend &b2 = mDihedralBendConstraints[inRHS];774775// First sort so that the constraint with the smallest distance to a kinematic vertex comes first776float d1 = min(777min(mClosestKinematic[b1.mVertex[0]].mDistance, mClosestKinematic[b1.mVertex[1]].mDistance),778min(mClosestKinematic[b1.mVertex[2]].mDistance, mClosestKinematic[b1.mVertex[3]].mDistance));779float d2 = min(780min(mClosestKinematic[b2.mVertex[0]].mDistance, mClosestKinematic[b2.mVertex[1]].mDistance),781min(mClosestKinematic[b2.mVertex[2]].mDistance, mClosestKinematic[b2.mVertex[3]].mDistance));782if (d1 != d2)783return d1 < d2;784785// Order constraints so that the ones with the smallest index go first786uint32 m1 = b1.GetMinVertexIndex();787uint32 m2 = b2.GetMinVertexIndex();788if (m1 != m2)789return m1 < m2;790791return inLHS < inRHS;792});793794// Sort the volume constraints795QuickSort(group.mVolumeConstraints.begin(), group.mVolumeConstraints.end(), [this](uint inLHS, uint inRHS)796{797const Volume &v1 = mVolumeConstraints[inLHS];798const Volume &v2 = mVolumeConstraints[inRHS];799800// First sort so that the constraint with the smallest distance to a kinematic vertex comes first801float d1 = min(802min(mClosestKinematic[v1.mVertex[0]].mDistance, mClosestKinematic[v1.mVertex[1]].mDistance),803min(mClosestKinematic[v1.mVertex[2]].mDistance, mClosestKinematic[v1.mVertex[3]].mDistance));804float d2 = min(805min(mClosestKinematic[v2.mVertex[0]].mDistance, mClosestKinematic[v2.mVertex[1]].mDistance),806min(mClosestKinematic[v2.mVertex[2]].mDistance, mClosestKinematic[v2.mVertex[3]].mDistance));807if (d1 != d2)808return d1 < d2;809810// Order constraints so that the ones with the smallest index go first811uint32 m1 = v1.GetMinVertexIndex();812uint32 m2 = v2.GetMinVertexIndex();813if (m1 != m2)814return m1 < m2;815816return inLHS < inRHS;817});818819// Sort the skinned constraints820QuickSort(group.mSkinnedConstraints.begin(), group.mSkinnedConstraints.end(), [this](uint inLHS, uint inRHS)821{822const Skinned &s1 = mSkinnedConstraints[inLHS];823const Skinned &s2 = mSkinnedConstraints[inRHS];824825// Order the skinned constraints so that the ones with the smallest index go first (hoping to get better cache locality when we process the edges).826if (s1.mVertex != s2.mVertex)827return s1.mVertex < s2.mVertex;828829return inLHS < inRHS;830});831}832833// Temporary store constraints as we reorder them834Array<Edge> temp_edges;835temp_edges.swap(mEdgeConstraints);836mEdgeConstraints.reserve(temp_edges.size());837outResults.mEdgeRemap.reserve(temp_edges.size());838839Array<LRA> temp_lra;840temp_lra.swap(mLRAConstraints);841mLRAConstraints.reserve(temp_lra.size());842outResults.mLRARemap.reserve(temp_lra.size());843844Array<DihedralBend> temp_dihedral_bend;845temp_dihedral_bend.swap(mDihedralBendConstraints);846mDihedralBendConstraints.reserve(temp_dihedral_bend.size());847outResults.mDihedralBendRemap.reserve(temp_dihedral_bend.size());848849Array<Volume> temp_volume;850temp_volume.swap(mVolumeConstraints);851mVolumeConstraints.reserve(temp_volume.size());852outResults.mVolumeRemap.reserve(temp_volume.size());853854Array<Skinned> temp_skinned;855temp_skinned.swap(mSkinnedConstraints);856mSkinnedConstraints.reserve(temp_skinned.size());857outResults.mSkinnedRemap.reserve(temp_skinned.size());858859// Finalize update groups860for (const Group &group : groups)861{862// Reorder edge constraints for this group863for (uint idx : group.mEdgeConstraints)864{865mEdgeConstraints.push_back(temp_edges[idx]);866outResults.mEdgeRemap.push_back(idx);867}868869// Reorder LRA constraints for this group870for (uint idx : group.mLRAConstraints)871{872mLRAConstraints.push_back(temp_lra[idx]);873outResults.mLRARemap.push_back(idx);874}875876// Reorder dihedral bend constraints for this group877for (uint idx : group.mDihedralBendConstraints)878{879mDihedralBendConstraints.push_back(temp_dihedral_bend[idx]);880outResults.mDihedralBendRemap.push_back(idx);881}882883// Reorder volume constraints for this group884for (uint idx : group.mVolumeConstraints)885{886mVolumeConstraints.push_back(temp_volume[idx]);887outResults.mVolumeRemap.push_back(idx);888}889890// Reorder skinned constraints for this group891for (uint idx : group.mSkinnedConstraints)892{893mSkinnedConstraints.push_back(temp_skinned[idx]);894outResults.mSkinnedRemap.push_back(idx);895}896897// Store end indices898mUpdateGroups.push_back({ (uint)mEdgeConstraints.size(), (uint)mLRAConstraints.size(), (uint)mDihedralBendConstraints.size(), (uint)mVolumeConstraints.size(), (uint)mSkinnedConstraints.size() });899}900901// Free closest kinematic buffer902mClosestKinematic.clear();903mClosestKinematic.shrink_to_fit();904}905906Ref<SoftBodySharedSettings> SoftBodySharedSettings::Clone() const907{908Ref<SoftBodySharedSettings> clone = new SoftBodySharedSettings;909clone->mVertices = mVertices;910clone->mFaces = mFaces;911clone->mEdgeConstraints = mEdgeConstraints;912clone->mDihedralBendConstraints = mDihedralBendConstraints;913clone->mVolumeConstraints = mVolumeConstraints;914clone->mSkinnedConstraints = mSkinnedConstraints;915clone->mSkinnedConstraintNormals = mSkinnedConstraintNormals;916clone->mInvBindMatrices = mInvBindMatrices;917clone->mLRAConstraints = mLRAConstraints;918clone->mMaterials = mMaterials;919clone->mVertexRadius = mVertexRadius;920clone->mUpdateGroups = mUpdateGroups;921return clone;922}923924void SoftBodySharedSettings::SaveBinaryState(StreamOut &inStream) const925{926inStream.Write(mVertices);927inStream.Write(mFaces);928inStream.Write(mEdgeConstraints);929inStream.Write(mDihedralBendConstraints);930inStream.Write(mVolumeConstraints);931inStream.Write(mSkinnedConstraints);932inStream.Write(mSkinnedConstraintNormals);933inStream.Write(mLRAConstraints);934inStream.Write(mVertexRadius);935inStream.Write(mUpdateGroups);936937// Can't write mInvBindMatrices directly because the class contains padding938inStream.Write(mInvBindMatrices, [](const InvBind &inElement, StreamOut &inS) {939inS.Write(inElement.mJointIndex);940inS.Write(inElement.mInvBind);941});942}943944void SoftBodySharedSettings::RestoreBinaryState(StreamIn &inStream)945{946inStream.Read(mVertices);947inStream.Read(mFaces);948inStream.Read(mEdgeConstraints);949inStream.Read(mDihedralBendConstraints);950inStream.Read(mVolumeConstraints);951inStream.Read(mSkinnedConstraints);952inStream.Read(mSkinnedConstraintNormals);953inStream.Read(mLRAConstraints);954inStream.Read(mVertexRadius);955inStream.Read(mUpdateGroups);956957inStream.Read(mInvBindMatrices, [](StreamIn &inS, InvBind &outElement) {958inS.Read(outElement.mJointIndex);959inS.Read(outElement.mInvBind);960});961}962963void SoftBodySharedSettings::SaveWithMaterials(StreamOut &inStream, SharedSettingsToIDMap &ioSettingsMap, MaterialToIDMap &ioMaterialMap) const964{965SharedSettingsToIDMap::const_iterator settings_iter = ioSettingsMap.find(this);966if (settings_iter == ioSettingsMap.end())967{968// Write settings ID969uint32 settings_id = ioSettingsMap.size();970ioSettingsMap[this] = settings_id;971inStream.Write(settings_id);972973// Write the settings974SaveBinaryState(inStream);975976// Write materials977StreamUtils::SaveObjectArray(inStream, mMaterials, &ioMaterialMap);978}979else980{981// Known settings, just write the ID982inStream.Write(settings_iter->second);983}984}985986SoftBodySharedSettings::SettingsResult SoftBodySharedSettings::sRestoreWithMaterials(StreamIn &inStream, IDToSharedSettingsMap &ioSettingsMap, IDToMaterialMap &ioMaterialMap)987{988SettingsResult result;989990// Read settings id991uint32 settings_id;992inStream.Read(settings_id);993if (inStream.IsEOF() || inStream.IsFailed())994{995result.SetError("Failed to read settings id");996return result;997}998999// Check nullptr settings1000if (settings_id == ~uint32(0))1001{1002result.Set(nullptr);1003return result;1004}10051006// Check if we already read this settings1007if (settings_id < ioSettingsMap.size())1008{1009result.Set(ioSettingsMap[settings_id]);1010return result;1011}10121013// Create new object1014Ref<SoftBodySharedSettings> settings = new SoftBodySharedSettings;10151016// Read state1017settings->RestoreBinaryState(inStream);10181019// Read materials1020Result mlresult = StreamUtils::RestoreObjectArray<PhysicsMaterialList>(inStream, ioMaterialMap);1021if (mlresult.HasError())1022{1023result.SetError(mlresult.GetError());1024return result;1025}1026settings->mMaterials = mlresult.Get();10271028// Add the settings to the map1029ioSettingsMap.push_back(settings);10301031result.Set(settings);1032return result;1033}10341035Ref<SoftBodySharedSettings> SoftBodySharedSettings::sCreateCube(uint inGridSize, float inGridSpacing)1036{1037const Vec3 cOffset = Vec3::sReplicate(-0.5f * inGridSpacing * (inGridSize - 1));10381039// Create settings1040SoftBodySharedSettings *settings = new SoftBodySharedSettings;1041for (uint z = 0; z < inGridSize; ++z)1042for (uint y = 0; y < inGridSize; ++y)1043for (uint x = 0; x < inGridSize; ++x)1044{1045SoftBodySharedSettings::Vertex v;1046(cOffset + Vec3::sReplicate(inGridSpacing) * Vec3(float(x), float(y), float(z))).StoreFloat3(&v.mPosition);1047settings->mVertices.push_back(v);1048}10491050// Function to get the vertex index of a point on the cube1051auto vertex_index = [inGridSize](uint inX, uint inY, uint inZ)1052{1053return inX + inY * inGridSize + inZ * inGridSize * inGridSize;1054};10551056// Create edges1057for (uint z = 0; z < inGridSize; ++z)1058for (uint y = 0; y < inGridSize; ++y)1059for (uint x = 0; x < inGridSize; ++x)1060{1061SoftBodySharedSettings::Edge e;1062e.mVertex[0] = vertex_index(x, y, z);1063if (x < inGridSize - 1)1064{1065e.mVertex[1] = vertex_index(x + 1, y, z);1066settings->mEdgeConstraints.push_back(e);1067}1068if (y < inGridSize - 1)1069{1070e.mVertex[1] = vertex_index(x, y + 1, z);1071settings->mEdgeConstraints.push_back(e);1072}1073if (z < inGridSize - 1)1074{1075e.mVertex[1] = vertex_index(x, y, z + 1);1076settings->mEdgeConstraints.push_back(e);1077}1078}1079settings->CalculateEdgeLengths();10801081// Tetrahedrons to fill a cube1082const int tetra_indices[6][4][3] = {1083{ {0, 0, 0}, {0, 1, 1}, {0, 0, 1}, {1, 1, 1} },1084{ {0, 0, 0}, {0, 1, 0}, {0, 1, 1}, {1, 1, 1} },1085{ {0, 0, 0}, {0, 0, 1}, {1, 0, 1}, {1, 1, 1} },1086{ {0, 0, 0}, {1, 0, 1}, {1, 0, 0}, {1, 1, 1} },1087{ {0, 0, 0}, {1, 1, 0}, {0, 1, 0}, {1, 1, 1} },1088{ {0, 0, 0}, {1, 0, 0}, {1, 1, 0}, {1, 1, 1} }1089};10901091// Create volume constraints1092for (uint z = 0; z < inGridSize - 1; ++z)1093for (uint y = 0; y < inGridSize - 1; ++y)1094for (uint x = 0; x < inGridSize - 1; ++x)1095for (uint t = 0; t < 6; ++t)1096{1097SoftBodySharedSettings::Volume v;1098for (uint i = 0; i < 4; ++i)1099v.mVertex[i] = vertex_index(x + tetra_indices[t][i][0], y + tetra_indices[t][i][1], z + tetra_indices[t][i][2]);1100settings->mVolumeConstraints.push_back(v);1101}11021103settings->CalculateVolumeConstraintVolumes();11041105// Create faces1106for (uint y = 0; y < inGridSize - 1; ++y)1107for (uint x = 0; x < inGridSize - 1; ++x)1108{1109SoftBodySharedSettings::Face f;11101111// Face 11112f.mVertex[0] = vertex_index(x, y, 0);1113f.mVertex[1] = vertex_index(x, y + 1, 0);1114f.mVertex[2] = vertex_index(x + 1, y + 1, 0);1115settings->AddFace(f);11161117f.mVertex[1] = vertex_index(x + 1, y + 1, 0);1118f.mVertex[2] = vertex_index(x + 1, y, 0);1119settings->AddFace(f);11201121// Face 21122f.mVertex[0] = vertex_index(x, y, inGridSize - 1);1123f.mVertex[1] = vertex_index(x + 1, y + 1, inGridSize - 1);1124f.mVertex[2] = vertex_index(x, y + 1, inGridSize - 1);1125settings->AddFace(f);11261127f.mVertex[1] = vertex_index(x + 1, y, inGridSize - 1);1128f.mVertex[2] = vertex_index(x + 1, y + 1, inGridSize - 1);1129settings->AddFace(f);11301131// Face 31132f.mVertex[0] = vertex_index(x, 0, y);1133f.mVertex[1] = vertex_index(x + 1, 0, y + 1);1134f.mVertex[2] = vertex_index(x, 0, y + 1);1135settings->AddFace(f);11361137f.mVertex[1] = vertex_index(x + 1, 0, y);1138f.mVertex[2] = vertex_index(x + 1, 0, y + 1);1139settings->AddFace(f);11401141// Face 41142f.mVertex[0] = vertex_index(x, inGridSize - 1, y);1143f.mVertex[1] = vertex_index(x, inGridSize - 1, y + 1);1144f.mVertex[2] = vertex_index(x + 1, inGridSize - 1, y + 1);1145settings->AddFace(f);11461147f.mVertex[1] = vertex_index(x + 1, inGridSize - 1, y + 1);1148f.mVertex[2] = vertex_index(x + 1, inGridSize - 1, y);1149settings->AddFace(f);11501151// Face 51152f.mVertex[0] = vertex_index(0, x, y);1153f.mVertex[1] = vertex_index(0, x, y + 1);1154f.mVertex[2] = vertex_index(0, x + 1, y + 1);1155settings->AddFace(f);11561157f.mVertex[1] = vertex_index(0, x + 1, y + 1);1158f.mVertex[2] = vertex_index(0, x + 1, y);1159settings->AddFace(f);11601161// Face 61162f.mVertex[0] = vertex_index(inGridSize - 1, x, y);1163f.mVertex[1] = vertex_index(inGridSize - 1, x + 1, y + 1);1164f.mVertex[2] = vertex_index(inGridSize - 1, x, y + 1);1165settings->AddFace(f);11661167f.mVertex[1] = vertex_index(inGridSize - 1, x + 1, y);1168f.mVertex[2] = vertex_index(inGridSize - 1, x + 1, y + 1);1169settings->AddFace(f);1170}11711172// Optimize the settings1173settings->Optimize();11741175return settings;1176}11771178JPH_NAMESPACE_END117911801181