Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/SoftBody/SoftBodySharedSettings.cpp
21345 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::RodStretchShear)39{40JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::RodStretchShear, mVertex)41JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::RodStretchShear, mLength)42JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::RodStretchShear, mInvMass)43JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::RodStretchShear, mCompliance)44JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::RodStretchShear, mBishop)45}4647JPH_IMPLEMENT_SERIALIZABLE_NON_VIRTUAL(SoftBodySharedSettings::RodBendTwist)48{49JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::RodBendTwist, mRod)50JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::RodBendTwist, mCompliance)51JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::RodBendTwist, mOmega0)52}5354JPH_IMPLEMENT_SERIALIZABLE_NON_VIRTUAL(SoftBodySharedSettings::DihedralBend)55{56JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::DihedralBend, mVertex)57JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::DihedralBend, mCompliance)58JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::DihedralBend, mInitialAngle)59}6061JPH_IMPLEMENT_SERIALIZABLE_NON_VIRTUAL(SoftBodySharedSettings::Volume)62{63JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::Volume, mVertex)64JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::Volume, mSixRestVolume)65JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::Volume, mCompliance)66}6768JPH_IMPLEMENT_SERIALIZABLE_NON_VIRTUAL(SoftBodySharedSettings::InvBind)69{70JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::InvBind, mJointIndex)71JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::InvBind, mInvBind)72}7374JPH_IMPLEMENT_SERIALIZABLE_NON_VIRTUAL(SoftBodySharedSettings::SkinWeight)75{76JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::SkinWeight, mInvBindIndex)77JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::SkinWeight, mWeight)78}7980JPH_IMPLEMENT_SERIALIZABLE_NON_VIRTUAL(SoftBodySharedSettings::Skinned)81{82JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::Skinned, mVertex)83JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::Skinned, mWeights)84JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::Skinned, mMaxDistance)85JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::Skinned, mBackStopDistance)86JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::Skinned, mBackStopRadius)87}8889JPH_IMPLEMENT_SERIALIZABLE_NON_VIRTUAL(SoftBodySharedSettings::LRA)90{91JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::LRA, mVertex)92JPH_ADD_ATTRIBUTE(SoftBodySharedSettings::LRA, mMaxDistance)93}9495JPH_IMPLEMENT_SERIALIZABLE_NON_VIRTUAL(SoftBodySharedSettings)96{97JPH_ADD_ATTRIBUTE(SoftBodySharedSettings, mVertices)98JPH_ADD_ATTRIBUTE(SoftBodySharedSettings, mFaces)99JPH_ADD_ATTRIBUTE(SoftBodySharedSettings, mEdgeConstraints)100JPH_ADD_ATTRIBUTE(SoftBodySharedSettings, mDihedralBendConstraints)101JPH_ADD_ATTRIBUTE(SoftBodySharedSettings, mVolumeConstraints)102JPH_ADD_ATTRIBUTE(SoftBodySharedSettings, mSkinnedConstraints)103JPH_ADD_ATTRIBUTE(SoftBodySharedSettings, mInvBindMatrices)104JPH_ADD_ATTRIBUTE(SoftBodySharedSettings, mLRAConstraints)105JPH_ADD_ATTRIBUTE(SoftBodySharedSettings, mRodStretchShearConstraints)106JPH_ADD_ATTRIBUTE(SoftBodySharedSettings, mRodBendTwistConstraints)107JPH_ADD_ATTRIBUTE(SoftBodySharedSettings, mMaterials)108}109110void SoftBodySharedSettings::CalculateClosestKinematic()111{112// Check if we already calculated this113if (!mClosestKinematic.empty())114return;115116// Reserve output size117mClosestKinematic.resize(mVertices.size());118119// Create a list of connected vertices120Array<Array<uint32>> connectivity;121connectivity.resize(mVertices.size());122for (const Edge &e : mEdgeConstraints)123{124connectivity[e.mVertex[0]].push_back(e.mVertex[1]);125connectivity[e.mVertex[1]].push_back(e.mVertex[0]);126}127for (const RodStretchShear &r : mRodStretchShearConstraints)128{129connectivity[r.mVertex[0]].push_back(r.mVertex[1]);130connectivity[r.mVertex[1]].push_back(r.mVertex[0]);131}132133// Use Dijkstra's algorithm to find the closest kinematic vertex for each vertex134// See: https://en.wikipedia.org/wiki/Dijkstra's_algorithm135//136// An element in the open list137struct Open138{139// Order so that we get the shortest distance first140bool operator < (const Open &inRHS) const141{142return mDistance > inRHS.mDistance;143}144145uint32 mVertex;146float mDistance;147};148149// Start with all kinematic elements150Array<Open> to_visit;151for (uint32 v = 0; v < mVertices.size(); ++v)152if (mVertices[v].mInvMass == 0.0f)153{154mClosestKinematic[v].mVertex = v;155mClosestKinematic[v].mHops = 0;156mClosestKinematic[v].mDistance = 0.0f;157to_visit.push_back({ v, 0.0f });158BinaryHeapPush(to_visit.begin(), to_visit.end(), std::less<Open> { });159}160161// Visit all vertices remembering the closest kinematic vertex and its distance162JPH_IF_ENABLE_ASSERTS(float last_closest = 0.0f;)163while (!to_visit.empty())164{165// Pop element from the open list166BinaryHeapPop(to_visit.begin(), to_visit.end(), std::less<Open> { });167Open current = to_visit.back();168to_visit.pop_back();169JPH_ASSERT(current.mDistance >= last_closest);170JPH_IF_ENABLE_ASSERTS(last_closest = current.mDistance;)171172// Loop through all of its connected vertices173for (uint32 v : connectivity[current.mVertex])174{175// Calculate distance from the current vertex to this target vertex and check if it is smaller176float new_distance = current.mDistance + (Vec3(mVertices[v].mPosition) - Vec3(mVertices[current.mVertex].mPosition)).Length();177if (new_distance < mClosestKinematic[v].mDistance)178{179// Remember new closest vertex180mClosestKinematic[v].mVertex = mClosestKinematic[current.mVertex].mVertex;181mClosestKinematic[v].mHops = mClosestKinematic[current.mVertex].mHops + 1;182mClosestKinematic[v].mDistance = new_distance;183to_visit.push_back({ v, new_distance });184BinaryHeapPush(to_visit.begin(), to_visit.end(), std::less<Open> { });185}186}187}188}189190void SoftBodySharedSettings::CreateConstraints(const VertexAttributes *inVertexAttributes, uint inVertexAttributesLength, EBendType inBendType, float inAngleTolerance)191{192struct EdgeHelper193{194uint32 mVertex[2];195uint32 mEdgeIdx;196};197198// Create list of all edges199Array<EdgeHelper> edges;200edges.reserve(mFaces.size() * 3);201for (const Face &f : mFaces)202for (int i = 0; i < 3; ++i)203{204uint32 v0 = f.mVertex[i];205uint32 v1 = f.mVertex[(i + 1) % 3];206207EdgeHelper e;208e.mVertex[0] = min(v0, v1);209e.mVertex[1] = max(v0, v1);210e.mEdgeIdx = uint32(&f - mFaces.data()) * 3 + i;211edges.push_back(e);212}213214// Sort the edges215QuickSort(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]); });216217// Only add edges if one of the vertices is movable218auto add_edge = [this](uint32 inVtx1, uint32 inVtx2, float inCompliance1, float inCompliance2) {219if ((mVertices[inVtx1].mInvMass > 0.0f || mVertices[inVtx2].mInvMass > 0.0f)220&& inCompliance1 < FLT_MAX && inCompliance2 < FLT_MAX)221{222Edge temp_edge;223temp_edge.mVertex[0] = inVtx1;224temp_edge.mVertex[1] = inVtx2;225temp_edge.mCompliance = 0.5f * (inCompliance1 + inCompliance2);226temp_edge.mRestLength = (Vec3(mVertices[inVtx2].mPosition) - Vec3(mVertices[inVtx1].mPosition)).Length();227JPH_ASSERT(temp_edge.mRestLength > 0.0f);228mEdgeConstraints.push_back(temp_edge);229}230};231232// Helper function to get the attributes of a vertex233auto attr = [inVertexAttributes, inVertexAttributesLength](uint32 inVertex) {234return inVertexAttributes[min(inVertex, inVertexAttributesLength - 1)];235};236237// Create the constraints238float sq_sin_tolerance = Square(Sin(inAngleTolerance));239float sq_cos_tolerance = Square(Cos(inAngleTolerance));240mEdgeConstraints.clear();241mEdgeConstraints.reserve(edges.size());242for (Array<EdgeHelper>::size_type i = 0; i < edges.size(); ++i)243{244const EdgeHelper &e0 = edges[i];245246// Get attributes for the vertices of the edge247const VertexAttributes &a0 = attr(e0.mVertex[0]);248const VertexAttributes &a1 = attr(e0.mVertex[1]);249250// 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)251bool is_shear = false;252253// Test if there are any shared edges254for (Array<EdgeHelper>::size_type j = i + 1; j < edges.size(); ++j)255{256const EdgeHelper &e1 = edges[j];257if (e0.mVertex[0] == e1.mVertex[0] && e0.mVertex[1] == e1.mVertex[1])258{259// Get opposing vertices260const Face &f0 = mFaces[e0.mEdgeIdx / 3];261const Face &f1 = mFaces[e1.mEdgeIdx / 3];262uint32 vopposite0 = f0.mVertex[(e0.mEdgeIdx + 2) % 3];263uint32 vopposite1 = f1.mVertex[(e1.mEdgeIdx + 2) % 3];264const VertexAttributes &a_opposite0 = attr(vopposite0);265const VertexAttributes &a_opposite1 = attr(vopposite1);266267// If the opposite vertices happen to be the same vertex then we have 2 triangles back to back and we skip creating shear / bend constraints268if (vopposite0 == vopposite1)269continue;270271// Faces should be roughly in a plane272Vec3 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));273Vec3 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));274float n0_dot_n1 = n0.Dot(n1);275if (n0_dot_n1 > 0.0f276&& Square(n0_dot_n1) > sq_cos_tolerance * n0.LengthSq() * n1.LengthSq())277{278// Faces should approximately form a quad279Vec3 e0_dir = Vec3(mVertices[vopposite0].mPosition) - Vec3(mVertices[e0.mVertex[0]].mPosition);280Vec3 e1_dir = Vec3(mVertices[vopposite1].mPosition) - Vec3(mVertices[e0.mVertex[0]].mPosition);281if (Square(e0_dir.Dot(e1_dir)) < sq_sin_tolerance * e0_dir.LengthSq() * e1_dir.LengthSq())282{283// Shear constraint284add_edge(vopposite0, vopposite1, a_opposite0.mShearCompliance, a_opposite1.mShearCompliance);285is_shear = true;286}287}288289// Bend constraint290switch (inBendType)291{292case EBendType::None:293// Do nothing294break;295296case EBendType::Distance:297// Create an edge constraint to represent the bend constraint298// Use the bend compliance of the shared edge299if (!is_shear)300add_edge(vopposite0, vopposite1, a0.mBendCompliance, a1.mBendCompliance);301break;302303case EBendType::Dihedral:304// Test if both opposite vertices are free to move305if ((mVertices[vopposite0].mInvMass > 0.0f || mVertices[vopposite1].mInvMass > 0.0f)306&& a0.mBendCompliance < FLT_MAX && a1.mBendCompliance < FLT_MAX)307{308// Create a bend constraint309// Use the bend compliance of the shared edge310mDihedralBendConstraints.emplace_back(e0.mVertex[0], e0.mVertex[1], vopposite0, vopposite1, 0.5f * (a0.mBendCompliance + a1.mBendCompliance));311}312break;313}314}315else316{317// Start iterating from the first non-shared edge318i = j - 1;319break;320}321}322323// Create a edge constraint for the current edge324add_edge(e0.mVertex[0], e0.mVertex[1], is_shear? a0.mShearCompliance : a0.mCompliance, is_shear? a1.mShearCompliance : a1.mCompliance);325}326mEdgeConstraints.shrink_to_fit();327328// Calculate the initial angle for all bend constraints329CalculateBendConstraintConstants();330331// Check if any vertices have LRA constraints332bool has_lra_constraints = false;333for (const VertexAttributes *va = inVertexAttributes; va < inVertexAttributes + inVertexAttributesLength; ++va)334if (va->mLRAType != ELRAType::None)335{336has_lra_constraints = true;337break;338}339if (has_lra_constraints)340{341// Ensure we have calculated the closest kinematic vertex for each vertex342CalculateClosestKinematic();343344// Find non-kinematic vertices345for (uint32 v = 0; v < (uint32)mVertices.size(); ++v)346if (mVertices[v].mInvMass > 0.0f)347{348// Check if a closest vertex was found349uint32 closest = mClosestKinematic[v].mVertex;350if (closest != 0xffffffff)351{352// Check which LRA constraint to create353const VertexAttributes &va = attr(v);354switch (va.mLRAType)355{356case ELRAType::None:357break;358359case ELRAType::EuclideanDistance:360mLRAConstraints.emplace_back(closest, v, va.mLRAMaxDistanceMultiplier * (Vec3(mVertices[closest].mPosition) - Vec3(mVertices[v].mPosition)).Length());361break;362363case ELRAType::GeodesicDistance:364mLRAConstraints.emplace_back(closest, v, va.mLRAMaxDistanceMultiplier * mClosestKinematic[v].mDistance);365break;366}367}368}369}370}371372void SoftBodySharedSettings::CalculateEdgeLengths()373{374for (Edge &e : mEdgeConstraints)375{376JPH_ASSERT(e.mVertex[0] != e.mVertex[1], "Edges need to connect 2 different vertices");377e.mRestLength = (Vec3(mVertices[e.mVertex[1]].mPosition) - Vec3(mVertices[e.mVertex[0]].mPosition)).Length();378JPH_ASSERT(e.mRestLength > 0.0f);379}380}381382void SoftBodySharedSettings::CalculateRodProperties()383{384// Mark connections through bend twist constraints385Array<Array<uint32>> connections;386connections.resize(mRodStretchShearConstraints.size());387for (const RodBendTwist &c : mRodBendTwistConstraints)388{389JPH_ASSERT(c.mRod[0] != c.mRod[1], "A bend twist constraint needs to be attached to different rods");390connections[c.mRod[1]].push_back(c.mRod[0]);391connections[c.mRod[0]].push_back(c.mRod[1]);392}393394// Now calculate the Bishop frames for all rods395struct Entry396{397uint32 mFrom; // Rod we're coming from398uint32 mTo; // Rod we're going to399};400Array<Entry> stack;401stack.reserve(mRodStretchShearConstraints.size());402for (uint32 r0_idx = 0; r0_idx < mRodStretchShearConstraints.size(); ++r0_idx)403{404RodStretchShear &r0 = mRodStretchShearConstraints[r0_idx];405406// Do not calculate a 2nd time407if (r0.mBishop == Quat::sZero())408{409// Calculate the frame for this rod410{411Vec3 tangent = Vec3(mVertices[r0.mVertex[1]].mPosition) - Vec3(mVertices[r0.mVertex[0]].mPosition);412r0.mLength = tangent.Length();413JPH_ASSERT(r0.mLength > 0.0f, "Rods of zero length are not supported!");414tangent /= r0.mLength;415Vec3 normal = tangent.GetNormalizedPerpendicular();416Vec3 binormal = tangent.Cross(normal);417r0.mBishop = Mat44(Vec4(normal, 0), Vec4(binormal, 0), Vec4(tangent, 0), Vec4(0, 0, 0, 1)).GetQuaternion().Normalized();418}419420// Add connected rods to the stack if they haven't been calculated yet421for (uint32 r1_idx : connections[r0_idx])422if (mRodStretchShearConstraints[r1_idx].mBishop == Quat::sZero())423stack.push_back({ r0_idx, r1_idx });424425// Now connect the bishop frame for all connected rods on the stack426// This follows the procedure outlined in "Discrete Elastic Rods" - M. Bergou et al.427// See: https://www.cs.columbia.edu/cg/pdfs/143-rods.pdf428while (!stack.empty())429{430uint32 r1_idx = stack.back().mFrom;431uint32 r2_idx = stack.back().mTo;432stack.pop_back();433434const RodStretchShear &r1 = mRodStretchShearConstraints[r1_idx];435RodStretchShear &r2 = mRodStretchShearConstraints[r2_idx];436437// Get the normal and tangent of the first rod's Bishop frame (that was already calculated)438Mat44 r1_frame = Mat44::sRotation(r1.mBishop);439Vec3 tangent1 = r1_frame.GetAxisZ();440Vec3 normal1 = r1_frame.GetAxisX();441442// Calculate the Bishop frame for the 2nd rod443Vec3 tangent2 = Vec3(mVertices[r2.mVertex[1]].mPosition) - Vec3(mVertices[r2.mVertex[0]].mPosition);444if (tangent1.Dot(tangent2) < 0.0f)445{446// Edge is oriented in the opposite direction of the previous edge, flip it447std::swap(r2.mVertex[0], r2.mVertex[1]);448tangent2 = -tangent2;449}450r2.mLength = tangent2.Length();451JPH_ASSERT(r2.mLength > 0.0f, "Rods of zero length are not supported!");452tangent2 /= r2.mLength;453Vec3 t1_cross_t2 = tangent1.Cross(tangent2);454float sin_angle = t1_cross_t2.Length();455Vec3 normal2 = normal1;456if (sin_angle > 1.0e-6f)457{458t1_cross_t2 /= sin_angle;459normal2 = Quat::sRotation(t1_cross_t2, ASin(sin_angle)) * normal2;460}461Vec3 binormal2 = tangent2.Cross(normal2);462r2.mBishop = Mat44(Vec4(normal2, 0), Vec4(binormal2, 0), Vec4(tangent2, 0), Vec4(0, 0, 0, 1)).GetQuaternion().Normalized();463464// Add connected rods to the stack if they haven't been calculated yet465for (uint32 r3_idx : connections[r2_idx])466if (mRodStretchShearConstraints[r3_idx].mBishop == Quat::sZero())467stack.push_back({ r2_idx, r3_idx });468}469}470}471472// Calculate inverse mass for all rods by taking the minimum inverse mass (aka the heaviest vertex) of both vertices473for (RodStretchShear &r : mRodStretchShearConstraints)474{475JPH_ASSERT(r.mVertex[0] != r.mVertex[1], "A rod stretch shear constraint requires two different vertices");476r.mInvMass = min(mVertices[r.mVertex[0]].mInvMass, mVertices[r.mVertex[1]].mInvMass);477}478479// Calculate the initial rotation between the rods480for (RodBendTwist &r : mRodBendTwistConstraints)481r.mOmega0 = (mRodStretchShearConstraints[r.mRod[0]].mBishop.Conjugated() * mRodStretchShearConstraints[r.mRod[1]].mBishop).Normalized();482}483484void SoftBodySharedSettings::CalculateLRALengths(float inMaxDistanceMultiplier)485{486for (LRA &l : mLRAConstraints)487{488JPH_ASSERT(l.mVertex[0] != l.mVertex[1], "LRA constraints need to connect 2 different vertices");489l.mMaxDistance = inMaxDistanceMultiplier * (Vec3(mVertices[l.mVertex[1]].mPosition) - Vec3(mVertices[l.mVertex[0]].mPosition)).Length();490JPH_ASSERT(l.mMaxDistance > 0.0f);491}492}493494void SoftBodySharedSettings::CalculateBendConstraintConstants()495{496for (DihedralBend &b : mDihedralBendConstraints)497{498JPH_ASSERT(b.mVertex[0] != b.mVertex[1] && b.mVertex[0] != b.mVertex[2] && b.mVertex[0] != b.mVertex[3]499&& b.mVertex[1] != b.mVertex[2] && b.mVertex[1] != b.mVertex[3]500&& b.mVertex[2] != b.mVertex[3], "Bend constraints need 4 different vertices");501502// Get positions503Vec3 x0 = Vec3(mVertices[b.mVertex[0]].mPosition);504Vec3 x1 = Vec3(mVertices[b.mVertex[1]].mPosition);505Vec3 x2 = Vec3(mVertices[b.mVertex[2]].mPosition);506Vec3 x3 = Vec3(mVertices[b.mVertex[3]].mPosition);507508/*509x2510e1/ \e3511/ \512x0----x1513\ e0 /514e2\ /e4515x3516*/517518// Calculate edges519Vec3 e0 = x1 - x0;520Vec3 e1 = x2 - x0;521Vec3 e2 = x3 - x0;522523// Normals of both triangles524Vec3 n1 = e0.Cross(e1);525Vec3 n2 = e2.Cross(e0);526float denom = sqrt(n1.LengthSq() * n2.LengthSq());527if (denom < 1.0e-12f)528b.mInitialAngle = 0.0f;529else530{531float sign = Sign(n2.Cross(n1).Dot(e0));532b.mInitialAngle = sign * ACosApproximate(n1.Dot(n2) / denom); // Runtime uses the approximation too533}534}535}536537void SoftBodySharedSettings::CalculateVolumeConstraintVolumes()538{539for (Volume &v : mVolumeConstraints)540{541JPH_ASSERT(v.mVertex[0] != v.mVertex[1] && v.mVertex[0] != v.mVertex[2] && v.mVertex[0] != v.mVertex[3]542&& v.mVertex[1] != v.mVertex[2] && v.mVertex[1] != v.mVertex[3]543&& v.mVertex[2] != v.mVertex[3], "Volume constraints need 4 different vertices");544545Vec3 x1(mVertices[v.mVertex[0]].mPosition);546Vec3 x2(mVertices[v.mVertex[1]].mPosition);547Vec3 x3(mVertices[v.mVertex[2]].mPosition);548Vec3 x4(mVertices[v.mVertex[3]].mPosition);549550Vec3 x1x2 = x2 - x1;551Vec3 x1x3 = x3 - x1;552Vec3 x1x4 = x4 - x1;553554v.mSixRestVolume = abs(x1x2.Cross(x1x3).Dot(x1x4));555}556}557558void SoftBodySharedSettings::CalculateSkinnedConstraintNormals()559{560// Clear any previous results561mSkinnedConstraintNormals.clear();562563// If there are no skinned constraints, we're done564if (mSkinnedConstraints.empty())565return;566567// First collect all vertices that are skinned568using VertexIndexSet = UnorderedSet<uint32>;569VertexIndexSet skinned_vertices;570skinned_vertices.reserve(VertexIndexSet::size_type(mSkinnedConstraints.size()));571for (const Skinned &s : mSkinnedConstraints)572skinned_vertices.insert(s.mVertex);573574// Now collect all faces that connect only to skinned vertices575using ConnectedFacesMap = UnorderedMap<uint32, VertexIndexSet>;576ConnectedFacesMap connected_faces;577connected_faces.reserve(ConnectedFacesMap::size_type(mVertices.size()));578for (const Face &f : mFaces)579{580// Must connect to only skinned vertices581bool valid = true;582for (uint32 v : f.mVertex)583valid &= skinned_vertices.find(v) != skinned_vertices.end();584if (!valid)585continue;586587// Store faces that connect to vertices588for (uint32 v : f.mVertex)589connected_faces[v].insert(uint32(&f - mFaces.data()));590}591592// Populate the list of connecting faces per skinned vertex593mSkinnedConstraintNormals.reserve(mFaces.size());594for (Skinned &s : mSkinnedConstraints)595{596uint32 start = uint32(mSkinnedConstraintNormals.size());597JPH_ASSERT((start >> 24) == 0);598ConnectedFacesMap::const_iterator connected_faces_it = connected_faces.find(s.mVertex);599if (connected_faces_it != connected_faces.cend())600{601const VertexIndexSet &faces = connected_faces_it->second;602uint32 num = uint32(faces.size());603JPH_ASSERT(num < 256);604mSkinnedConstraintNormals.insert(mSkinnedConstraintNormals.end(), faces.begin(), faces.end());605QuickSort(mSkinnedConstraintNormals.begin() + start, mSkinnedConstraintNormals.begin() + start + num);606s.mNormalInfo = start + (num << 24);607}608else609s.mNormalInfo = 0;610}611mSkinnedConstraintNormals.shrink_to_fit();612}613614void SoftBodySharedSettings::Optimize(OptimizationResults &outResults)615{616// Clear any previous results617mUpdateGroups.clear();618619// Create a list of connected vertices620struct Connection621{622uint32 mVertex;623uint32 mCount;624};625Array<Array<Connection>> connectivity;626connectivity.resize(mVertices.size());627auto add_connection = [&connectivity](uint inV1, uint inV2) {628for (int i = 0; i < 2; ++i)629{630bool found = false;631for (Connection &c : connectivity[inV1])632if (c.mVertex == inV2)633{634c.mCount++;635found = true;636break;637}638if (!found)639connectivity[inV1].push_back({ inV2, 1 });640641std::swap(inV1, inV2);642}643};644for (const Edge &c : mEdgeConstraints)645add_connection(c.mVertex[0], c.mVertex[1]);646for (const LRA &c : mLRAConstraints)647add_connection(c.mVertex[0], c.mVertex[1]);648for (const RodStretchShear &c : mRodStretchShearConstraints)649add_connection(c.mVertex[0], c.mVertex[1]);650for (const RodBendTwist &c : mRodBendTwistConstraints)651{652add_connection(mRodStretchShearConstraints[c.mRod[0]].mVertex[0], mRodStretchShearConstraints[c.mRod[1]].mVertex[0]);653add_connection(mRodStretchShearConstraints[c.mRod[0]].mVertex[1], mRodStretchShearConstraints[c.mRod[1]].mVertex[0]);654add_connection(mRodStretchShearConstraints[c.mRod[0]].mVertex[0], mRodStretchShearConstraints[c.mRod[1]].mVertex[1]);655add_connection(mRodStretchShearConstraints[c.mRod[0]].mVertex[1], mRodStretchShearConstraints[c.mRod[1]].mVertex[1]);656}657for (const DihedralBend &c : mDihedralBendConstraints)658{659add_connection(c.mVertex[0], c.mVertex[1]);660add_connection(c.mVertex[0], c.mVertex[2]);661add_connection(c.mVertex[0], c.mVertex[3]);662add_connection(c.mVertex[1], c.mVertex[2]);663add_connection(c.mVertex[1], c.mVertex[3]);664add_connection(c.mVertex[2], c.mVertex[3]);665}666for (const Volume &c : mVolumeConstraints)667{668add_connection(c.mVertex[0], c.mVertex[1]);669add_connection(c.mVertex[0], c.mVertex[2]);670add_connection(c.mVertex[0], c.mVertex[3]);671add_connection(c.mVertex[1], c.mVertex[2]);672add_connection(c.mVertex[1], c.mVertex[3]);673add_connection(c.mVertex[2], c.mVertex[3]);674}675// Skinned constraints only update 1 vertex, so we don't need special logic here676677// Maps each of the vertices to a group index678Array<int> group_idx;679group_idx.resize(mVertices.size(), -1);680681// Which group we are currently filling and its vertices682int current_group_idx = 0;683Array<uint> current_group;684685// Start greedy algorithm to group vertices686for (;;)687{688// Find the bounding box of the ungrouped vertices689AABox bounds;690for (uint i = 0; i < (uint)mVertices.size(); ++i)691if (group_idx[i] == -1)692bounds.Encapsulate(Vec3(mVertices[i].mPosition));693694// If the bounds are invalid, it means that there were no ungrouped vertices695if (!bounds.IsValid())696break;697698// Determine longest and shortest axis699Vec3 bounds_size = bounds.GetSize();700uint max_axis = bounds_size.GetHighestComponentIndex();701uint min_axis = bounds_size.GetLowestComponentIndex();702if (min_axis == max_axis)703min_axis = (min_axis + 1) % 3;704uint mid_axis = 3 - min_axis - max_axis;705706// Find the vertex that has the lowest value on the axis with the largest extent707uint current_vertex = UINT_MAX;708Float3 current_vertex_position { FLT_MAX, FLT_MAX, FLT_MAX };709for (uint i = 0; i < (uint)mVertices.size(); ++i)710if (group_idx[i] == -1)711{712const Float3 &vertex_position = mVertices[i].mPosition;713float max_axis_value = vertex_position[max_axis];714float mid_axis_value = vertex_position[mid_axis];715float min_axis_value = vertex_position[min_axis];716717if (max_axis_value < current_vertex_position[max_axis]718|| (max_axis_value == current_vertex_position[max_axis]719&& (mid_axis_value < current_vertex_position[mid_axis]720|| (mid_axis_value == current_vertex_position[mid_axis]721&& min_axis_value < current_vertex_position[min_axis]))))722{723current_vertex_position = mVertices[i].mPosition;724current_vertex = i;725}726}727if (current_vertex == UINT_MAX)728break;729730// Initialize the current group with 1 vertex731current_group.push_back(current_vertex);732group_idx[current_vertex] = current_group_idx;733734// Fill up the group735for (;;)736{737// Find the vertex that is most connected to the current group738uint best_vertex = UINT_MAX;739uint best_num_connections = 0;740float best_dist_sq = FLT_MAX;741for (uint i = 0; i < (uint)current_group.size(); ++i) // For all vertices in the current group742for (const Connection &c : connectivity[current_group[i]]) // For all connections to other vertices743{744uint v = c.mVertex;745if (group_idx[v] == -1) // Ungrouped vertices only746{747// Count the number of connections to this group748uint num_connections = 0;749for (const Connection &v2 : connectivity[v])750if (group_idx[v2.mVertex] == current_group_idx)751num_connections += v2.mCount;752753// Calculate distance to group centroid754float dist_sq = (Vec3(mVertices[v].mPosition) - Vec3(mVertices[current_group.front()].mPosition)).LengthSq();755756if (best_vertex == UINT_MAX757|| num_connections > best_num_connections758|| (num_connections == best_num_connections && dist_sq < best_dist_sq))759{760best_vertex = v;761best_num_connections = num_connections;762best_dist_sq = dist_sq;763}764}765}766767// Add the best vertex to the current group768if (best_vertex != UINT_MAX)769{770current_group.push_back(best_vertex);771group_idx[best_vertex] = current_group_idx;772}773774// Create a new group?775if (current_group.size() >= SoftBodyUpdateContext::cVertexConstraintBatch // If full, yes776|| (current_group.size() > SoftBodyUpdateContext::cVertexConstraintBatch / 2 && best_vertex == UINT_MAX)) // If half full and we found no connected vertex, yes777{778current_group.clear();779current_group_idx++;780break;781}782783// If we didn't find a connected vertex, we need to find a new starting vertex784if (best_vertex == UINT_MAX)785break;786}787}788789// 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' group790if (current_group.size() > SoftBodyUpdateContext::cVertexConstraintBatch / 2)791++current_group_idx;792793// We no longer need the current group array, free the memory794current_group.clear();795current_group.shrink_to_fit();796797// We're done with the connectivity list, free the memory798connectivity.clear();799connectivity.shrink_to_fit();800801// Assign the constraints to their groups802struct Group803{804uint GetSize() const805{806return (uint)mEdgeConstraints.size() + (uint)mLRAConstraints.size() + (uint)mRodStretchShearConstraints.size() + (uint)mRodBendTwistConstraints.size() + (uint)mDihedralBendConstraints.size() + (uint)mVolumeConstraints.size() + (uint)mSkinnedConstraints.size();807}808809Array<uint> mEdgeConstraints;810Array<uint> mLRAConstraints;811Array<uint> mRodStretchShearConstraints;812Array<uint> mRodBendTwistConstraints;813Array<uint> mDihedralBendConstraints;814Array<uint> mVolumeConstraints;815Array<uint> mSkinnedConstraints;816};817Array<Group> groups;818groups.resize(current_group_idx + 1); // + non parallel group819for (const Edge &e : mEdgeConstraints)820{821int g1 = group_idx[e.mVertex[0]];822int g2 = group_idx[e.mVertex[1]];823JPH_ASSERT(g1 >= 0 && g2 >= 0);824if (g1 == g2) // In the same group825groups[g1].mEdgeConstraints.push_back(uint(&e - mEdgeConstraints.data()));826else // In different groups -> parallel group827groups.back().mEdgeConstraints.push_back(uint(&e - mEdgeConstraints.data()));828}829for (const LRA &l : mLRAConstraints)830{831int g1 = group_idx[l.mVertex[0]];832int g2 = group_idx[l.mVertex[1]];833JPH_ASSERT(g1 >= 0 && g2 >= 0);834if (g1 == g2) // In the same group835groups[g1].mLRAConstraints.push_back(uint(&l - mLRAConstraints.data()));836else // In different groups -> parallel group837groups.back().mLRAConstraints.push_back(uint(&l - mLRAConstraints.data()));838}839for (const RodStretchShear &r : mRodStretchShearConstraints)840{841int g1 = group_idx[r.mVertex[0]];842int g2 = group_idx[r.mVertex[1]];843JPH_ASSERT(g1 >= 0 && g2 >= 0);844if (g1 == g2) // In the same group845groups[g1].mRodStretchShearConstraints.push_back(uint(&r - mRodStretchShearConstraints.data()));846else // In different groups -> parallel group847groups.back().mRodStretchShearConstraints.push_back(uint(&r - mRodStretchShearConstraints.data()));848}849for (const RodBendTwist &r : mRodBendTwistConstraints)850{851int g1 = group_idx[mRodStretchShearConstraints[r.mRod[0]].mVertex[0]];852int g2 = group_idx[mRodStretchShearConstraints[r.mRod[0]].mVertex[1]];853int g3 = group_idx[mRodStretchShearConstraints[r.mRod[1]].mVertex[0]];854int g4 = group_idx[mRodStretchShearConstraints[r.mRod[1]].mVertex[1]];855JPH_ASSERT(g1 >= 0 && g2 >= 0 && g3 >= 0 && g4 >= 0);856if (g1 == g2 && g1 == g3 && g1 == g4) // In the same group857groups[g1].mRodBendTwistConstraints.push_back(uint(&r - mRodBendTwistConstraints.data()));858else // In different groups -> parallel group859groups.back().mRodBendTwistConstraints.push_back(uint(&r - mRodBendTwistConstraints.data()));860}861for (const DihedralBend &d : mDihedralBendConstraints)862{863int g1 = group_idx[d.mVertex[0]];864int g2 = group_idx[d.mVertex[1]];865int g3 = group_idx[d.mVertex[2]];866int g4 = group_idx[d.mVertex[3]];867JPH_ASSERT(g1 >= 0 && g2 >= 0 && g3 >= 0 && g4 >= 0);868if (g1 == g2 && g1 == g3 && g1 == g4) // In the same group869groups[g1].mDihedralBendConstraints.push_back(uint(&d - mDihedralBendConstraints.data()));870else // In different groups -> parallel group871groups.back().mDihedralBendConstraints.push_back(uint(&d - mDihedralBendConstraints.data()));872}873for (const Volume &v : mVolumeConstraints)874{875int g1 = group_idx[v.mVertex[0]];876int g2 = group_idx[v.mVertex[1]];877int g3 = group_idx[v.mVertex[2]];878int g4 = group_idx[v.mVertex[3]];879JPH_ASSERT(g1 >= 0 && g2 >= 0 && g3 >= 0 && g4 >= 0);880if (g1 == g2 && g1 == g3 && g1 == g4) // In the same group881groups[g1].mVolumeConstraints.push_back(uint(&v - mVolumeConstraints.data()));882else // In different groups -> parallel group883groups.back().mVolumeConstraints.push_back(uint(&v - mVolumeConstraints.data()));884}885for (const Skinned &s : mSkinnedConstraints)886{887int g1 = group_idx[s.mVertex];888JPH_ASSERT(g1 >= 0);889groups[g1].mSkinnedConstraints.push_back(uint(&s - mSkinnedConstraints.data()));890}891892// Sort the parallel groups from big to small (this means the big groups will be scheduled first and have more time to complete)893QuickSort(groups.begin(), groups.end() - 1, [](const Group &inLHS, const Group &inRHS) { return inLHS.GetSize() > inRHS.GetSize(); });894895// Make sure we know the closest kinematic vertex so we can sort896CalculateClosestKinematic();897898// Sort within each group899for (Group &group : groups)900{901// Sort the edge constraints902QuickSort(group.mEdgeConstraints.begin(), group.mEdgeConstraints.end(), [this](uint inLHS, uint inRHS)903{904const Edge &e1 = mEdgeConstraints[inLHS];905const Edge &e2 = mEdgeConstraints[inRHS];906907// First sort so that the edge with the smallest distance to a kinematic vertex comes first908float d1 = min(mClosestKinematic[e1.mVertex[0]].mDistance, mClosestKinematic[e1.mVertex[1]].mDistance);909float d2 = min(mClosestKinematic[e2.mVertex[0]].mDistance, mClosestKinematic[e2.mVertex[1]].mDistance);910if (d1 != d2)911return d1 < d2;912913// Order the edges so that the ones with the smallest index go first (hoping to get better cache locality when we process the edges).914// Note we could also re-order the vertices but that would be much more of a burden to the end user915uint32 m1 = e1.GetMinVertexIndex();916uint32 m2 = e2.GetMinVertexIndex();917if (m1 != m2)918return m1 < m2;919920return inLHS < inRHS;921});922923// Sort the LRA constraints924QuickSort(group.mLRAConstraints.begin(), group.mLRAConstraints.end(), [this](uint inLHS, uint inRHS)925{926const LRA &l1 = mLRAConstraints[inLHS];927const LRA &l2 = mLRAConstraints[inRHS];928929// First sort so that the longest constraint comes first (meaning the shortest constraint has the most influence on the end result)930// Most of the time there will be a single LRA constraint per vertex and since the LRA constraint only modifies a single vertex,931// updating one constraint will not violate another constraint.932if (l1.mMaxDistance != l2.mMaxDistance)933return l1.mMaxDistance > l2.mMaxDistance;934935// Order constraints so that the ones with the smallest index go first936uint32 m1 = l1.GetMinVertexIndex();937uint32 m2 = l2.GetMinVertexIndex();938if (m1 != m2)939return m1 < m2;940941return inLHS < inRHS;942});943944// Sort the rod stretch shear constraints945QuickSort(group.mRodStretchShearConstraints.begin(), group.mRodStretchShearConstraints.end(), [this](uint inLHS, uint inRHS)946{947const RodStretchShear &r1 = mRodStretchShearConstraints[inLHS];948const RodStretchShear &r2 = mRodStretchShearConstraints[inRHS];949950// First sort so that the rod with the smallest distance to a kinematic vertex comes first951float d1 = min(mClosestKinematic[r1.mVertex[0]].mDistance, mClosestKinematic[r1.mVertex[1]].mDistance);952float d2 = min(mClosestKinematic[r2.mVertex[0]].mDistance, mClosestKinematic[r2.mVertex[1]].mDistance);953if (d1 != d2)954return d1 < d2;955956// Then sort on the rod that connects to the smallest kinematic vertex957uint32 m1 = min(mClosestKinematic[r1.mVertex[0]].mVertex, mClosestKinematic[r1.mVertex[1]].mVertex);958uint32 m2 = min(mClosestKinematic[r2.mVertex[0]].mVertex, mClosestKinematic[r2.mVertex[1]].mVertex);959if (m1 != m2)960return m1 < m2;961962// Order the rods so that the ones with the smallest index go first (hoping to get better cache locality when we process the rods).963m1 = r1.GetMinVertexIndex();964m2 = r2.GetMinVertexIndex();965if (m1 != m2)966return m1 < m2;967968return inLHS < inRHS;969});970971// Sort the rod bend twist constraints972QuickSort(group.mRodBendTwistConstraints.begin(), group.mRodBendTwistConstraints.end(), [this](uint inLHS, uint inRHS)973{974const RodBendTwist &b1 = mRodBendTwistConstraints[inLHS];975const RodStretchShear &b1_r1 = mRodStretchShearConstraints[b1.mRod[0]];976const RodStretchShear &b1_r2 = mRodStretchShearConstraints[b1.mRod[1]];977978const RodBendTwist &b2 = mRodBendTwistConstraints[inRHS];979const RodStretchShear &b2_r1 = mRodStretchShearConstraints[b2.mRod[0]];980const RodStretchShear &b2_r2 = mRodStretchShearConstraints[b2.mRod[1]];981982// First sort so that the rod with the smallest number of hops to a kinematic vertex comes first.983// Note that we don't use distance because of the bilateral interleaving below.984uint32 m1 = min(985min(mClosestKinematic[b1_r1.mVertex[0]].mHops, mClosestKinematic[b1_r1.mVertex[1]].mHops),986min(mClosestKinematic[b1_r2.mVertex[0]].mHops, mClosestKinematic[b1_r2.mVertex[1]].mHops));987uint32 m2 = min(988min(mClosestKinematic[b2_r1.mVertex[0]].mHops, mClosestKinematic[b2_r1.mVertex[1]].mHops),989min(mClosestKinematic[b2_r2.mVertex[0]].mHops, mClosestKinematic[b2_r2.mVertex[1]].mHops));990if (m1 != m2)991return m1 < m2;992993// Then sort on the rod that connects to the kinematic vertex with lowest index.994// This ensures that we consistently order the rods that are attached to other kinematic constraints.995// Again, this helps bilateral interleaving below.996m1 = min(997min(mClosestKinematic[b1_r1.mVertex[0]].mVertex, mClosestKinematic[b1_r1.mVertex[1]].mVertex),998min(mClosestKinematic[b1_r2.mVertex[0]].mVertex, mClosestKinematic[b1_r2.mVertex[1]].mVertex));999m2 = min(1000min(mClosestKinematic[b2_r1.mVertex[0]].mVertex, mClosestKinematic[b2_r1.mVertex[1]].mVertex),1001min(mClosestKinematic[b2_r2.mVertex[0]].mVertex, mClosestKinematic[b2_r2.mVertex[1]].mVertex));1002if (m1 != m2)1003return m1 < m2;10041005// Finally order so that the smallest vertex index goes first1006m1 = min(b1_r1.GetMinVertexIndex(), b1_r2.GetMinVertexIndex());1007m2 = min(b2_r1.GetMinVertexIndex(), b2_r2.GetMinVertexIndex());1008if (m1 != m2)1009return m1 < m2;10101011return inLHS < inRHS;1012});10131014// Bilateral interleaving, see figure 4 of "Position and Orientation Based Cosserat Rods" - Kugelstadt and Schoemer - SIGGRAPH 20161015// Keeping the twist constraints sorted often results in an unstable simulation1016for (Array<uint>::size_type i = 1, s = group.mRodBendTwistConstraints.size(), s2 = s >> 1; i < s2; i += 2)1017std::swap(group.mRodBendTwistConstraints[i], group.mRodBendTwistConstraints[s - i]);10181019// Sort the dihedral bend constraints1020QuickSort(group.mDihedralBendConstraints.begin(), group.mDihedralBendConstraints.end(), [this](uint inLHS, uint inRHS)1021{1022const DihedralBend &b1 = mDihedralBendConstraints[inLHS];1023const DihedralBend &b2 = mDihedralBendConstraints[inRHS];10241025// First sort so that the constraint with the smallest distance to a kinematic vertex comes first1026float d1 = min(1027min(mClosestKinematic[b1.mVertex[0]].mDistance, mClosestKinematic[b1.mVertex[1]].mDistance),1028min(mClosestKinematic[b1.mVertex[2]].mDistance, mClosestKinematic[b1.mVertex[3]].mDistance));1029float d2 = min(1030min(mClosestKinematic[b2.mVertex[0]].mDistance, mClosestKinematic[b2.mVertex[1]].mDistance),1031min(mClosestKinematic[b2.mVertex[2]].mDistance, mClosestKinematic[b2.mVertex[3]].mDistance));1032if (d1 != d2)1033return d1 < d2;10341035// Finally order so that the smallest vertex index goes first1036uint32 m1 = b1.GetMinVertexIndex();1037uint32 m2 = b2.GetMinVertexIndex();1038if (m1 != m2)1039return m1 < m2;10401041return inLHS < inRHS;1042});10431044// Sort the volume constraints1045QuickSort(group.mVolumeConstraints.begin(), group.mVolumeConstraints.end(), [this](uint inLHS, uint inRHS)1046{1047const Volume &v1 = mVolumeConstraints[inLHS];1048const Volume &v2 = mVolumeConstraints[inRHS];10491050// First sort so that the constraint with the smallest distance to a kinematic vertex comes first1051float d1 = min(1052min(mClosestKinematic[v1.mVertex[0]].mDistance, mClosestKinematic[v1.mVertex[1]].mDistance),1053min(mClosestKinematic[v1.mVertex[2]].mDistance, mClosestKinematic[v1.mVertex[3]].mDistance));1054float d2 = min(1055min(mClosestKinematic[v2.mVertex[0]].mDistance, mClosestKinematic[v2.mVertex[1]].mDistance),1056min(mClosestKinematic[v2.mVertex[2]].mDistance, mClosestKinematic[v2.mVertex[3]].mDistance));1057if (d1 != d2)1058return d1 < d2;10591060// Order constraints so that the ones with the smallest index go first1061uint32 m1 = v1.GetMinVertexIndex();1062uint32 m2 = v2.GetMinVertexIndex();1063if (m1 != m2)1064return m1 < m2;10651066return inLHS < inRHS;1067});10681069// Sort the skinned constraints1070QuickSort(group.mSkinnedConstraints.begin(), group.mSkinnedConstraints.end(), [this](uint inLHS, uint inRHS)1071{1072const Skinned &s1 = mSkinnedConstraints[inLHS];1073const Skinned &s2 = mSkinnedConstraints[inRHS];10741075// 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).1076if (s1.mVertex != s2.mVertex)1077return s1.mVertex < s2.mVertex;10781079return inLHS < inRHS;1080});1081}10821083// Temporary store constraints as we reorder them1084Array<Edge> temp_edges;1085temp_edges.swap(mEdgeConstraints);1086mEdgeConstraints.reserve(temp_edges.size());1087outResults.mEdgeRemap.resize(temp_edges.size(), ~uint(0));10881089Array<LRA> temp_lra;1090temp_lra.swap(mLRAConstraints);1091mLRAConstraints.reserve(temp_lra.size());1092outResults.mLRARemap.resize(temp_lra.size(), ~uint(0));10931094Array<RodStretchShear> temp_rod_stretch_shear;1095temp_rod_stretch_shear.swap(mRodStretchShearConstraints);1096mRodStretchShearConstraints.reserve(temp_rod_stretch_shear.size());1097outResults.mRodStretchShearConstraintRemap.resize(temp_rod_stretch_shear.size(), ~uint(0));10981099Array<RodBendTwist> temp_rod_bend_twist;1100temp_rod_bend_twist.swap(mRodBendTwistConstraints);1101mRodBendTwistConstraints.reserve(temp_rod_bend_twist.size());1102outResults.mRodBendTwistConstraintRemap.resize(temp_rod_bend_twist.size(), ~uint(0));11031104Array<DihedralBend> temp_dihedral_bend;1105temp_dihedral_bend.swap(mDihedralBendConstraints);1106mDihedralBendConstraints.reserve(temp_dihedral_bend.size());1107outResults.mDihedralBendRemap.resize(temp_dihedral_bend.size(), ~uint(0));11081109Array<Volume> temp_volume;1110temp_volume.swap(mVolumeConstraints);1111mVolumeConstraints.reserve(temp_volume.size());1112outResults.mVolumeRemap.resize(temp_volume.size(), ~uint(0));11131114Array<Skinned> temp_skinned;1115temp_skinned.swap(mSkinnedConstraints);1116mSkinnedConstraints.reserve(temp_skinned.size());1117outResults.mSkinnedRemap.resize(temp_skinned.size(), ~uint(0));11181119// Finalize update groups1120for (const Group &group : groups)1121{1122// Reorder edge constraints for this group1123for (uint idx : group.mEdgeConstraints)1124{1125outResults.mEdgeRemap[idx] = (uint)mEdgeConstraints.size();1126mEdgeConstraints.push_back(temp_edges[idx]);1127}11281129// Reorder LRA constraints for this group1130for (uint idx : group.mLRAConstraints)1131{1132outResults.mLRARemap[idx] = (uint)mLRAConstraints.size();1133mLRAConstraints.push_back(temp_lra[idx]);1134}11351136// Reorder rod stretch shear constraints for this group1137for (uint idx : group.mRodStretchShearConstraints)1138{1139outResults.mRodStretchShearConstraintRemap[idx] = (uint)mRodStretchShearConstraints.size();1140mRodStretchShearConstraints.push_back(temp_rod_stretch_shear[idx]);1141}11421143// Reorder rod bend twist constraints for this group1144for (uint idx : group.mRodBendTwistConstraints)1145{1146outResults.mRodBendTwistConstraintRemap[idx] = (uint)mRodBendTwistConstraints.size();1147mRodBendTwistConstraints.push_back(temp_rod_bend_twist[idx]);1148}11491150// Reorder dihedral bend constraints for this group1151for (uint idx : group.mDihedralBendConstraints)1152{1153outResults.mDihedralBendRemap[idx] = (uint)mDihedralBendConstraints.size();1154mDihedralBendConstraints.push_back(temp_dihedral_bend[idx]);1155}11561157// Reorder volume constraints for this group1158for (uint idx : group.mVolumeConstraints)1159{1160outResults.mVolumeRemap[idx] = (uint)mVolumeConstraints.size();1161mVolumeConstraints.push_back(temp_volume[idx]);1162}11631164// Reorder skinned constraints for this group1165for (uint idx : group.mSkinnedConstraints)1166{1167outResults.mSkinnedRemap[idx] = (uint)mSkinnedConstraints.size();1168mSkinnedConstraints.push_back(temp_skinned[idx]);1169}11701171// Store end indices1172mUpdateGroups.push_back({ (uint)mEdgeConstraints.size(), (uint)mLRAConstraints.size(), (uint)mRodStretchShearConstraints.size(), (uint)mRodBendTwistConstraints.size(), (uint)mDihedralBendConstraints.size(), (uint)mVolumeConstraints.size(), (uint)mSkinnedConstraints.size() });1173}11741175// Remap bend twist indices because mRodStretchShearConstraints has been reordered1176for (RodBendTwist &r : mRodBendTwistConstraints)1177for (int i = 0; i < 2; ++i)1178r.mRod[i] = outResults.mRodStretchShearConstraintRemap[r.mRod[i]];11791180// Free closest kinematic buffer1181mClosestKinematic.clear();1182mClosestKinematic.shrink_to_fit();1183}11841185Ref<SoftBodySharedSettings> SoftBodySharedSettings::Clone() const1186{1187Ref<SoftBodySharedSettings> clone = new SoftBodySharedSettings;1188clone->mVertices = mVertices;1189clone->mFaces = mFaces;1190clone->mEdgeConstraints = mEdgeConstraints;1191clone->mDihedralBendConstraints = mDihedralBendConstraints;1192clone->mVolumeConstraints = mVolumeConstraints;1193clone->mSkinnedConstraints = mSkinnedConstraints;1194clone->mSkinnedConstraintNormals = mSkinnedConstraintNormals;1195clone->mInvBindMatrices = mInvBindMatrices;1196clone->mLRAConstraints = mLRAConstraints;1197clone->mRodStretchShearConstraints = mRodStretchShearConstraints;1198clone->mRodBendTwistConstraints = mRodBendTwistConstraints;1199clone->mMaterials = mMaterials;1200clone->mUpdateGroups = mUpdateGroups;1201return clone;1202}12031204void SoftBodySharedSettings::SaveBinaryState(StreamOut &inStream) const1205{1206inStream.Write(mVertices);1207inStream.Write(mFaces);1208inStream.Write(mEdgeConstraints);1209inStream.Write(mDihedralBendConstraints);1210inStream.Write(mVolumeConstraints);1211inStream.Write(mSkinnedConstraints);1212inStream.Write(mSkinnedConstraintNormals);1213inStream.Write(mLRAConstraints);1214inStream.Write(mUpdateGroups);12151216// Can't write mRodStretchShearConstraints directly because the class contains padding1217inStream.Write(mRodStretchShearConstraints, [](const RodStretchShear &inElement, StreamOut &inS) {1218inS.Write(inElement.mVertex);1219inS.Write(inElement.mLength);1220inS.Write(inElement.mInvMass);1221inS.Write(inElement.mCompliance);1222inS.Write(inElement.mBishop);1223});12241225// Can't write mRodBendTwistConstraints directly because the class contains padding1226inStream.Write(mRodBendTwistConstraints, [](const RodBendTwist &inElement, StreamOut &inS) {1227inS.Write(inElement.mRod);1228inS.Write(inElement.mCompliance);1229inS.Write(inElement.mOmega0);1230});12311232// Can't write mInvBindMatrices directly because the class contains padding1233inStream.Write(mInvBindMatrices, [](const InvBind &inElement, StreamOut &inS) {1234inS.Write(inElement.mJointIndex);1235inS.Write(inElement.mInvBind);1236});1237}12381239void SoftBodySharedSettings::RestoreBinaryState(StreamIn &inStream)1240{1241inStream.Read(mVertices);1242inStream.Read(mFaces);1243inStream.Read(mEdgeConstraints);1244inStream.Read(mDihedralBendConstraints);1245inStream.Read(mVolumeConstraints);1246inStream.Read(mSkinnedConstraints);1247inStream.Read(mSkinnedConstraintNormals);1248inStream.Read(mLRAConstraints);1249inStream.Read(mUpdateGroups);12501251inStream.Read(mRodStretchShearConstraints, [](StreamIn &inS, RodStretchShear &outElement) {1252inS.Read(outElement.mVertex);1253inS.Read(outElement.mLength);1254inS.Read(outElement.mInvMass);1255inS.Read(outElement.mCompliance);1256inS.Read(outElement.mBishop);1257});12581259inStream.Read(mRodBendTwistConstraints, [](StreamIn &inS, RodBendTwist &outElement) {1260inS.Read(outElement.mRod);1261inS.Read(outElement.mCompliance);1262inS.Read(outElement.mOmega0);1263});12641265inStream.Read(mInvBindMatrices, [](StreamIn &inS, InvBind &outElement) {1266inS.Read(outElement.mJointIndex);1267inS.Read(outElement.mInvBind);1268});1269}12701271void SoftBodySharedSettings::SaveWithMaterials(StreamOut &inStream, SharedSettingsToIDMap &ioSettingsMap, MaterialToIDMap &ioMaterialMap) const1272{1273SharedSettingsToIDMap::const_iterator settings_iter = ioSettingsMap.find(this);1274if (settings_iter == ioSettingsMap.end())1275{1276// Write settings ID1277uint32 settings_id = ioSettingsMap.size();1278ioSettingsMap[this] = settings_id;1279inStream.Write(settings_id);12801281// Write the settings1282SaveBinaryState(inStream);12831284// Write materials1285StreamUtils::SaveObjectArray(inStream, mMaterials, &ioMaterialMap);1286}1287else1288{1289// Known settings, just write the ID1290inStream.Write(settings_iter->second);1291}1292}12931294SoftBodySharedSettings::SettingsResult SoftBodySharedSettings::sRestoreWithMaterials(StreamIn &inStream, IDToSharedSettingsMap &ioSettingsMap, IDToMaterialMap &ioMaterialMap)1295{1296SettingsResult result;12971298// Read settings id1299uint32 settings_id;1300inStream.Read(settings_id);1301if (inStream.IsEOF() || inStream.IsFailed())1302{1303result.SetError("Failed to read settings id");1304return result;1305}13061307// Check nullptr settings1308if (settings_id == ~uint32(0))1309{1310result.Set(nullptr);1311return result;1312}13131314// Check if we already read this settings1315if (settings_id < ioSettingsMap.size())1316{1317result.Set(ioSettingsMap[settings_id]);1318return result;1319}13201321// Create new object1322Ref<SoftBodySharedSettings> settings = new SoftBodySharedSettings;13231324// Read state1325settings->RestoreBinaryState(inStream);13261327// Read materials1328Result mlresult = StreamUtils::RestoreObjectArray<PhysicsMaterialList>(inStream, ioMaterialMap);1329if (mlresult.HasError())1330{1331result.SetError(mlresult.GetError());1332return result;1333}1334settings->mMaterials = mlresult.Get();13351336// Add the settings to the map1337ioSettingsMap.push_back(settings);13381339result.Set(settings);1340return result;1341}13421343Ref<SoftBodySharedSettings> SoftBodySharedSettings::sCreateCube(uint inGridSize, float inGridSpacing)1344{1345const Vec3 cOffset = Vec3::sReplicate(-0.5f * inGridSpacing * (inGridSize - 1));13461347// Create settings1348SoftBodySharedSettings *settings = new SoftBodySharedSettings;1349for (uint z = 0; z < inGridSize; ++z)1350for (uint y = 0; y < inGridSize; ++y)1351for (uint x = 0; x < inGridSize; ++x)1352{1353SoftBodySharedSettings::Vertex v;1354(cOffset + Vec3::sReplicate(inGridSpacing) * Vec3(float(x), float(y), float(z))).StoreFloat3(&v.mPosition);1355settings->mVertices.push_back(v);1356}13571358// Function to get the vertex index of a point on the cube1359auto vertex_index = [inGridSize](uint inX, uint inY, uint inZ)1360{1361return inX + inY * inGridSize + inZ * inGridSize * inGridSize;1362};13631364// Create edges1365for (uint z = 0; z < inGridSize; ++z)1366for (uint y = 0; y < inGridSize; ++y)1367for (uint x = 0; x < inGridSize; ++x)1368{1369SoftBodySharedSettings::Edge e;1370e.mVertex[0] = vertex_index(x, y, z);1371if (x < inGridSize - 1)1372{1373e.mVertex[1] = vertex_index(x + 1, y, z);1374settings->mEdgeConstraints.push_back(e);1375}1376if (y < inGridSize - 1)1377{1378e.mVertex[1] = vertex_index(x, y + 1, z);1379settings->mEdgeConstraints.push_back(e);1380}1381if (z < inGridSize - 1)1382{1383e.mVertex[1] = vertex_index(x, y, z + 1);1384settings->mEdgeConstraints.push_back(e);1385}1386}1387settings->CalculateEdgeLengths();13881389// Tetrahedrons to fill a cube1390const int tetra_indices[6][4][3] = {1391{ {0, 0, 0}, {0, 1, 1}, {0, 0, 1}, {1, 1, 1} },1392{ {0, 0, 0}, {0, 1, 0}, {0, 1, 1}, {1, 1, 1} },1393{ {0, 0, 0}, {0, 0, 1}, {1, 0, 1}, {1, 1, 1} },1394{ {0, 0, 0}, {1, 0, 1}, {1, 0, 0}, {1, 1, 1} },1395{ {0, 0, 0}, {1, 1, 0}, {0, 1, 0}, {1, 1, 1} },1396{ {0, 0, 0}, {1, 0, 0}, {1, 1, 0}, {1, 1, 1} }1397};13981399// Create volume constraints1400for (uint z = 0; z < inGridSize - 1; ++z)1401for (uint y = 0; y < inGridSize - 1; ++y)1402for (uint x = 0; x < inGridSize - 1; ++x)1403for (uint t = 0; t < 6; ++t)1404{1405SoftBodySharedSettings::Volume v;1406for (uint i = 0; i < 4; ++i)1407v.mVertex[i] = vertex_index(x + tetra_indices[t][i][0], y + tetra_indices[t][i][1], z + tetra_indices[t][i][2]);1408settings->mVolumeConstraints.push_back(v);1409}14101411settings->CalculateVolumeConstraintVolumes();14121413// Create faces1414for (uint y = 0; y < inGridSize - 1; ++y)1415for (uint x = 0; x < inGridSize - 1; ++x)1416{1417SoftBodySharedSettings::Face f;14181419// Face 11420f.mVertex[0] = vertex_index(x, y, 0);1421f.mVertex[1] = vertex_index(x, y + 1, 0);1422f.mVertex[2] = vertex_index(x + 1, y + 1, 0);1423settings->AddFace(f);14241425f.mVertex[1] = vertex_index(x + 1, y + 1, 0);1426f.mVertex[2] = vertex_index(x + 1, y, 0);1427settings->AddFace(f);14281429// Face 21430f.mVertex[0] = vertex_index(x, y, inGridSize - 1);1431f.mVertex[1] = vertex_index(x + 1, y + 1, inGridSize - 1);1432f.mVertex[2] = vertex_index(x, y + 1, inGridSize - 1);1433settings->AddFace(f);14341435f.mVertex[1] = vertex_index(x + 1, y, inGridSize - 1);1436f.mVertex[2] = vertex_index(x + 1, y + 1, inGridSize - 1);1437settings->AddFace(f);14381439// Face 31440f.mVertex[0] = vertex_index(x, 0, y);1441f.mVertex[1] = vertex_index(x + 1, 0, y + 1);1442f.mVertex[2] = vertex_index(x, 0, y + 1);1443settings->AddFace(f);14441445f.mVertex[1] = vertex_index(x + 1, 0, y);1446f.mVertex[2] = vertex_index(x + 1, 0, y + 1);1447settings->AddFace(f);14481449// Face 41450f.mVertex[0] = vertex_index(x, inGridSize - 1, y);1451f.mVertex[1] = vertex_index(x, inGridSize - 1, y + 1);1452f.mVertex[2] = vertex_index(x + 1, inGridSize - 1, y + 1);1453settings->AddFace(f);14541455f.mVertex[1] = vertex_index(x + 1, inGridSize - 1, y + 1);1456f.mVertex[2] = vertex_index(x + 1, inGridSize - 1, y);1457settings->AddFace(f);14581459// Face 51460f.mVertex[0] = vertex_index(0, x, y);1461f.mVertex[1] = vertex_index(0, x, y + 1);1462f.mVertex[2] = vertex_index(0, x + 1, y + 1);1463settings->AddFace(f);14641465f.mVertex[1] = vertex_index(0, x + 1, y + 1);1466f.mVertex[2] = vertex_index(0, x + 1, y);1467settings->AddFace(f);14681469// Face 61470f.mVertex[0] = vertex_index(inGridSize - 1, x, y);1471f.mVertex[1] = vertex_index(inGridSize - 1, x + 1, y + 1);1472f.mVertex[2] = vertex_index(inGridSize - 1, x, y + 1);1473settings->AddFace(f);14741475f.mVertex[1] = vertex_index(inGridSize - 1, x + 1, y);1476f.mVertex[2] = vertex_index(inGridSize - 1, x + 1, y + 1);1477settings->AddFace(f);1478}14791480// Optimize the settings1481settings->Optimize();14821483return settings;1484}14851486JPH_NAMESPACE_END148714881489