Yesterday, 10:05 AM
(14-01-2025, 01:39 PM)Zavhoz Wrote: Hi!
How can you determine that in one emitter the particles are divided into several separate parts. And is it possible to obtain these groups of individual particles?
Regards!
Hello. I looked into this a while ago and, because Obi works by applying forces to particles that are near eachother, it conveniently has a queue of "fluidInteractions" which we can process to gather contiguous particles in the solver
Here is the basics, showing that for every pair that is "really" interacting, we run TryMergeRoots() on the two solver indices. This uses a Union Find algorithm on the disjoint set of solver particles. Importantly, the root index of any two or more particles is decided to be the -lowest- of any of the solver indices, because this ensures stability for which particles point to others.
Code:
// Use interaction pairs to detect contiguous Fluid Bodies of Obi particles
private void Merge_Fluids(ObiNativeIntList rootSolverIndices)
{
Debug.Assert(solver.implementation != null, $"{nameof(solver.implementation)} is null, is ObiSolver not yet inited?");
Debug.Assert(rootSolverIndices.count == solver.positions.count, $"Length of UnionFind roots must match solver Positions couns\n{rootSolverIndices.count} != {solver.positions.count}");
float distanceThreshold = Mathf.Pow(solverHandler.m_gamebodyMergeMaxDist, 2);
NativeArray<FluidInteraction> fluidSimplexInteractions = (solver.implementation as BurstSolverImpl).fluidInteractions; // Fluids Only
foreach (var interaction in fluidSimplexInteractions)
{
// Fluid Interactions contain Solver Indices
if (interaction.particleA >= solver.positions.count || interaction.particleB >= solver.positions.count)
{
Debug.LogError($"Solver Particle(s) interaction invalid: {interaction.particleA} {interaction.particleB} for solver positions count {solver.positions.count}");
continue;
}
// ensure that particles are active in actors
var actorinfoA = solver.particleToActor[interaction.particleA];
var actorinfoB = solver.particleToActor[interaction.particleB];
bool isactiveA = actorinfoA.indexInActor < actorinfoA.actor.activeParticleCount;
bool isactiveB = actorinfoB.indexInActor < actorinfoB.actor.activeParticleCount;
if (isactiveA == false || isactiveB == false)
{
//Debug.LogWarning($"Interaction ignored, one or both is inactive");
continue;
}
// quick distance check with sqrMagnitude
Vector3 posA = solver.positions[interaction.particleA];
Vector3 posB = solver.positions[interaction.particleB];
float sqrDist = (posA - posB).sqrMagnitude;
if (sqrDist > distanceThreshold)
continue;
// We are now positive that particles A and B have interacted, and can thus be assumed to be in the same contiguous fluid body
// Therefore, re-direct the upper index to the lower index
// So that the lowest index of any fluidbody is the root index of all particles in that body
TryMergeRoots(rootSolverIndices, interaction.particleA, interaction.particleB);
}
}
I can post the UnionFind functions later, but here is the general idea. Point all solverIndices to themselves in m_perParticle_RootSolverIndex, and then in Merge, take interacting pairs and re-direct the higher index from itself to the lower.
Once you have "rootSolverIndices", every solver index who points to itself defines a new body. Again, each root will be the lowest index of any particles in its own contiguous body.
Then, you can iterate each body and calculate centermass positions, etc
Code:
ResetUnionFindElements(m_perParticle_RootSolverIndex);
Merge_NonFluids(m_perParticle_RootSolverIndex);
Merge_Fluids(m_perParticle_RootSolverIndex);
List<int> rootSolverIndices = null;
UnionFind_GetRoots(m_perParticle_RootSolverIndex, out rootSolverIndices);
Unionfind_SanityCheckRoots(m_perParticle_RootSolverIndex);