private void IntraCellSearch(int cellIndex, ref BurstSimplex simplexShape) { int cellLength = grid.usedCells[cellIndex].Length; for (int p = 0; p < cellLength; ++p) { for (int n = p + 1; n < cellLength; ++n) { InteractionTest(grid.usedCells[cellIndex][p], grid.usedCells[cellIndex][n], ref simplexShape); } } }
private void IntraLevelSearch(int cellIndex, ref BurstSimplex simplexShape) { int4 cellCoords = grid.usedCells[cellIndex].Coords; // neighboring cells in the current level: for (int i = 0; i < 13; ++i) { int4 neighborCellCoords = new int4(cellCoords.xyz + GridHash.cellOffsets3D[i], cellCoords.w); int neighborCellIndex; if (grid.TryGetCellIndex(neighborCellCoords, out neighborCellIndex)) { InterCellSearch(cellIndex, neighborCellIndex, ref simplexShape); } } // neighboring cells in levels above the current one: int levelIndex = gridLevels.IndexOf <int, int>(cellCoords.w); if (levelIndex >= 0) { levelIndex++; for (; levelIndex < gridLevels.Length; ++levelIndex) { int level = gridLevels[levelIndex]; // calculate index of parent cell in parent level: int4 parentCellCoords = NativeMultilevelGrid <int> .GetParentCellCoords(cellCoords, level); // search in all neighbouring cells: for (int x = -1; x <= 1; ++x) { for (int y = -1; y <= 1; ++y) { for (int z = -1; z <= 1; ++z) { int4 neighborCellCoords = parentCellCoords + new int4(x, y, z, 0); int neighborCellIndex; if (grid.TryGetCellIndex(neighborCellCoords, out neighborCellIndex)) { InterCellSearch(cellIndex, neighborCellIndex, ref simplexShape); } } } } } } }
public void Execute(int i) { BurstSimplex simplexShape = new BurstSimplex() { positions = restPositions, radii = radii, simplices = simplices, }; // Looks for close particles in the same cell: IntraCellSearch(i, ref simplexShape); // Looks for close particles in neighboring cells, in the same level or higher levels. IntraLevelSearch(i, ref simplexShape); }
private void InteractionTest(int A, int B, ref BurstSimplex simplexShape) { // skip the pair if their bounds don't intersect: if (!simplexBounds[A].IntersectsAabb(simplexBounds[B])) { return; } // get the start index and size of each simplex: int simplexStartA = simplexCounts.GetSimplexStartAndSize(A, out int simplexSizeA); int simplexStartB = simplexCounts.GetSimplexStartAndSize(B, out int simplexSizeB); // immediately reject simplex pairs that share particles: for (int a = 0; a < simplexSizeA; ++a) { for (int b = 0; b < simplexSizeB; ++b) { if (simplices[simplexStartA + a] == simplices[simplexStartB + b]) { return; } } } // get phases for each simplex: bool restPositionsEnabled = false; int groupA = GetSimplexPhase(simplexStartA, simplexSizeA, out Oni.ParticleFlags flagsA, ref restPositionsEnabled); int groupB = GetSimplexPhase(simplexStartB, simplexSizeB, out Oni.ParticleFlags flagsB, ref restPositionsEnabled); // if all particles have the same group and none have self-collision, reject the pair. if (groupA == groupB && (flagsA & flagsB & Oni.ParticleFlags.SelfCollide) == 0) { return; } // if all simplices are fluid, check their smoothing radii: if ((flagsA & Oni.ParticleFlags.Fluid) != 0 && (flagsB & Oni.ParticleFlags.Fluid) != 0) { int particleA = simplices[simplexStartA]; int particleB = simplices[simplexStartB]; // for fluid we only consider the first particle in each simplex. float4 predictedPositionA = positions[particleA] + velocities[particleA] * dt; float4 predictedPositionB = positions[particleB] + velocities[particleB] * dt; // Calculate particle center distance: float d2 = math.lengthsq(predictedPositionA - predictedPositionB); float fluidDistance = math.max(fluidRadii[particleA], fluidRadii[particleB]); if (d2 <= fluidDistance * fluidDistance) { fluidInteractionsQueue.Enqueue(new FluidInteraction { particleA = particleA, particleB = particleB }); } } else // at least one solid particle is present: { // swap simplices so that B is always the one-sided one. if ((flagsA & Oni.ParticleFlags.OneSided) != 0 && groupA < groupB) { ObiUtils.Swap(ref A, ref B); ObiUtils.Swap(ref simplexStartA, ref simplexStartB); ObiUtils.Swap(ref simplexSizeA, ref simplexSizeB); ObiUtils.Swap(ref flagsA, ref flagsB); ObiUtils.Swap(ref groupA, ref groupB); } float4 simplexBary = BurstMath.BarycenterForSimplexOfSize(simplexSizeA); float4 simplexPoint; simplexShape.simplexStart = simplexStartB; simplexShape.simplexSize = simplexSizeB; simplexShape.positions = restPositions; simplexShape.CacheData(); float simplexRadiusA = 0, simplexRadiusB = 0; // skip the contact if there's self-intersection at rest: if (groupA == groupB && restPositionsEnabled) { var restPoint = BurstLocalOptimization.Optimize <BurstSimplex>(ref simplexShape, restPositions, radii, simplices, simplexStartA, simplexSizeA, ref simplexBary, out simplexPoint, 4, 0); for (int j = 0; j < simplexSizeA; ++j) { simplexRadiusA += radii[simplices[simplexStartA + j]].x * simplexBary[j]; } for (int j = 0; j < simplexSizeB; ++j) { simplexRadiusB += radii[simplices[simplexStartB + j]].x * restPoint.bary[j]; } // compare distance along contact normal with radius. if (math.dot(simplexPoint - restPoint.point, restPoint.normal) < simplexRadiusA + simplexRadiusB) { return; } } simplexBary = BurstMath.BarycenterForSimplexOfSize(simplexSizeA); simplexShape.positions = positions; simplexShape.CacheData(); var surfacePoint = BurstLocalOptimization.Optimize <BurstSimplex>(ref simplexShape, positions, radii, simplices, simplexStartA, simplexSizeA, ref simplexBary, out simplexPoint, optimizationIterations, optimizationTolerance); simplexRadiusA = 0; simplexRadiusB = 0; float4 velocityA = float4.zero, velocityB = float4.zero, normalB = float4.zero; for (int j = 0; j < simplexSizeA; ++j) { int particleIndex = simplices[simplexStartA + j]; simplexRadiusA += radii[particleIndex].x * simplexBary[j]; velocityA += velocities[particleIndex] * simplexBary[j]; } for (int j = 0; j < simplexSizeB; ++j) { int particleIndex = simplices[simplexStartB + j]; simplexRadiusB += radii[particleIndex].x * surfacePoint.bary[j]; velocityB += velocities[particleIndex] * surfacePoint.bary[j]; normalB += normals[particleIndex] * surfacePoint.bary[j]; } float dAB = math.dot(simplexPoint - surfacePoint.point, surfacePoint.normal); float vel = math.dot(velocityA - velocityB, surfacePoint.normal); // check if the projected velocity along the contact normal will get us within collision distance. if (vel * dt + dAB <= simplexRadiusA + simplexRadiusB + collisionMargin) { // adapt collision normal for one-sided simplices: if ((flagsB & Oni.ParticleFlags.OneSided) != 0 && groupB < groupA) { BurstMath.OneSidedNormal(normalB, ref surfacePoint.normal); } contactsQueue.Enqueue(new BurstContact() { bodyA = A, bodyB = B, pointA = simplexBary, pointB = surfacePoint.bary, normal = surfacePoint.normal }); } } }