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 }); } } }