#include "GridUtils.cginc" #include "CollisionMaterial.cginc" #include "ColliderDefinitions.cginc" #include "ContactHandling.cginc" #include "SolverParameters.cginc" #include "Simplex.cginc" #include "Phases.cginc" #include "Bounds.cginc" #include "Simplex.cginc" #pragma kernel Clear #pragma kernel InsertSimplices #pragma kernel FindPopulatedLevels #pragma kernel SortSimplices #pragma kernel BuildFluidDispatch #pragma kernel SortFluidSimplices #pragma kernel BuildMortonIndices #pragma kernel MortonSort #pragma kernel FindFluidNeighborsInSameLevel #pragma kernel FindFluidNeighborsInUpperLevels #pragma kernel BuildContactList StructuredBuffer solverBounds; StructuredBuffer simplexBounds; StructuredBuffer simplices; // particle indices in each simplex. StructuredBuffer positions; StructuredBuffer restPositions; StructuredBuffer principalRadii; StructuredBuffer fluidMaterials; StructuredBuffer fluidInterface; StructuredBuffer normals; StructuredBuffer orientations; StructuredBuffer restOrientations; StructuredBuffer velocities; StructuredBuffer invMasses; StructuredBuffer phases; StructuredBuffer filters; StructuredBuffer R_cellCoords; // for each item, its cell coordinates. StructuredBuffer R_offsetInCell; // for each item, its offset within the cell. StructuredBuffer R_cellOffsets; // start of each cell in the sorted item array. StructuredBuffer R_cellCounts; // number of item in each cell. StructuredBuffer R_levelPopulation; RWStructuredBuffer cellCoords; // for each item, its cell coordinates. RWStructuredBuffer offsetInCell; // for each item, its offset within the cell. RWStructuredBuffer cellOffsets; // start of each cell in the sorted item array. RWStructuredBuffer cellCounts; // number of item in each cell. RWStructuredBuffer cellHashToMortonIndex; RWStructuredBuffer mortonSortedCellHashes; RWStructuredBuffer sortedSimplexToFluid; // fluidSimplices RWStructuredBuffer sortedFluidIndices; RWStructuredBuffer sortedSimplexIndices; RWStructuredBuffer sortedPositions; RWStructuredBuffer sortedFluidMaterials; RWStructuredBuffer sortedFluidInterface; RWStructuredBuffer sortedPrincipalRadii; RWStructuredBuffer neighbors; RWStructuredBuffer neighborCounts; RWStructuredBuffer particleContacts; RWStructuredBuffer contactPairs; RWStructuredBuffer fluidDispatchBuffer; RWStructuredBuffer dispatchBuffer; RWStructuredBuffer colors; uint maxContacts; uint maxNeighbors; uint fluidParticleCount; float deltaTime; const uint groupWidth; const uint groupHeight; const uint stepIndex; /** For each cell, calculate coords and morton. This only works if there’s no collisions, so use the coord of one random particle that maps to that cell: For each simplex: Determine cell coords (any particle in it will do) and hash, store in array per cell. Sort array by morton(coords), create array that maps from cell hash to morton index, then use as cellCounts[mortonIndex]. This way we have sorted particles and cells, and can use for fluid surface. Win win!! */ [numthreads(128, 1, 1)] void Clear (uint3 id : SV_DispatchThreadID) { unsigned int i = id.x; if (i >= maxCells) return; if (i == 0) { for (int l = 0; l <= GRID_LEVELS; ++l) levelPopulation[l] = 0; } // clear all cell counts to zero, and cell offsets to invalid. cellOffsets[i] = INVALID; cellCounts[i] = 0; mortonSortedCellHashes[i] = i; } [numthreads(128, 1, 1)] void InsertSimplices (uint3 id : SV_DispatchThreadID) { unsigned int i = id.x; if (i >= pointCount + edgeCount + triangleCount) return; // calculate simplex cell index: int level = GridLevelForSize(simplexBounds[i].MaxAxisLength()); float cellSize = CellSizeOfLevel(level); int4 cellCoord = int4(floor((simplexBounds[i].Center() - solverBounds[0].min_)/ cellSize).xyz,level); // if the solver is 2D, project to the z = 0 cell. if (mode == 1) cellCoord[2] = 0; // insert simplex in cell: uint cellIndex = GridHash(cellCoord); cellCoords[i] = cellCoord; InterlockedAdd(cellCounts[cellIndex],1,offsetInCell[i]); // assign minimum morton code to cell // (there may be hash collisions mapping two coordinates to the same cell, that's why we use atomic minimum) float mortonCellSize = solverBounds[0].MaxAxisLength() / 1024.0; uint morton = EncodeMorton3(floor((simplexBounds[i].Center() - solverBounds[0].min_).xyz / mortonCellSize)); InterlockedMin(cellOffsets[cellIndex], morton); // clear neighbor count: neighborCounts[i] = 0; // atomically increase this level's population by one: InterlockedAdd(levelPopulation[1 + level],1); } [numthreads(128,1,1)] void MortonSort(uint3 id : SV_DispatchThreadID) { uint i = id.x; uint hIndex = i & (groupWidth - 1); uint indexLeft = hIndex + (groupHeight + 1) * (i / groupWidth); uint rightStepSize = stepIndex == 0 ? groupHeight - 2 * hIndex : (groupHeight + 1) / 2; uint indexRight = indexLeft + rightStepSize; // Exit if out of bounds if (indexRight >= maxCells) return; // get morton index for both cells: uint mortonL = cellOffsets[indexLeft]; uint mortonR = cellOffsets[indexRight]; // get cell counts: uint simplexIndexL = cellCounts[indexLeft]; uint simplexIndexR = cellCounts[indexRight]; uint orderL = mortonSortedCellHashes[indexLeft]; uint orderR = mortonSortedCellHashes[indexRight]; // Swap entries if order is incorrect if (mortonL > mortonR) { cellCounts[indexLeft] = simplexIndexR; cellCounts[indexRight] = simplexIndexL; cellOffsets[indexLeft] = mortonR; cellOffsets[indexRight] = mortonL; mortonSortedCellHashes[indexLeft] = orderR; mortonSortedCellHashes[indexRight] = orderL; } } [numthreads(128,1,1)] void BuildMortonIndices(uint3 id : SV_DispatchThreadID) { unsigned int i = id.x; if (i >= maxCells) return; // build map from cell hash to index in morton-sorted cell data. int index = mortonSortedCellHashes[i]; cellHashToMortonIndex[index] = i; } [numthreads(128, 1, 1)] void SortSimplices (uint3 id : SV_DispatchThreadID) { uint i = id.x; if (i >= pointCount + edgeCount + triangleCount) return; // write simplex index to its index in the grid: uint cellIndex = cellHashToMortonIndex[GridHash(R_cellCoords[i])]; uint gridIndex = R_cellOffsets[cellIndex] + R_offsetInCell[i]; sortedSimplexIndices[gridIndex] = i; // maps from index in grid to simplex index. // flag fluid simplices with 1. we'll later do a prefix sum of this array // to get a compact list of grid-sorted fluid indices. int size; int p = simplices[GetSimplexStartAndSize(i, size)]; sortedFluidIndices[gridIndex] = ((phases[p] & Fluid) != 0) ? 1:0; } [numthreads(1, 1, 1)] void BuildFluidDispatch (uint3 id : SV_DispatchThreadID) { // since we are using *exclusive* prefix sum, // we must add the last entries of both buffers together to get total count of fluid simplices. int lastEntry = pointCount + edgeCount + triangleCount - 1; fluidDispatchBuffer[3] = sortedSimplexToFluid[lastEntry] + sortedFluidIndices[lastEntry]; fluidDispatchBuffer[0] = fluidDispatchBuffer[3] / 128 + 1; } [numthreads(128, 1, 1)] void SortFluidSimplices (uint3 id : SV_DispatchThreadID) //rename to sort fluid data. { // check all simplices. uint i = id.x; if (i >= pointCount + edgeCount + triangleCount) return; uint cellIndex = cellHashToMortonIndex[GridHash(R_cellCoords[i])]; uint gridIndex = R_cellOffsets[cellIndex] + R_offsetInCell[i]; // copy the data of first particle in each fluid simplex to sorted arrays // using prefix sum results: same as grid order, but contiguous. int size; int p = simplices[GetSimplexStartAndSize(i, size)]; if ((phases[p] & Fluid) != 0) { int fluidIndex = sortedSimplexToFluid[gridIndex]; sortedFluidIndices[fluidIndex] = i; sortedPositions[fluidIndex] = positions[p]; sortedFluidMaterials[fluidIndex] = fluidMaterials[p]; sortedFluidInterface[fluidIndex] = fluidInterface[p]; sortedPrincipalRadii[fluidIndex] = principalRadii[p]; } else sortedSimplexToFluid[gridIndex] = -1; } int GetSimplexGroup(in int simplexStart,in int simplexSize, out int flags, out int category, out int mask, out bool restPositionsEnabled) { flags = 0; int group = 0; category = 0; mask = 0; restPositionsEnabled = false; for (int j = 0; j < simplexSize; ++j) { int particleIndex = simplices[simplexStart + j]; group = max(group, phases[particleIndex] & GroupMask); flags |= phases[particleIndex] & ~GroupMask; // get flags from phase category |= filters[particleIndex] & CategoryMask; // get category from filter mask |= (filters[particleIndex] & MaskMask) >> 16; // get mask from filter restPositionsEnabled = restPositionsEnabled || (restPositions[particleIndex].w > 0.5f); } return group; } struct simplexData { int index; int start; int size; int category; int mask; int flags; int group; bool restPosEnabled; }; simplexData GetSimplexData(int indexInGrid) { simplexData s; s.index = sortedSimplexIndices[indexInGrid]; s.start = GetSimplexStartAndSize(s.index, s.size); s.group = GetSimplexGroup(s.start, s.size, s.flags, s.category, s.mask, s.restPosEnabled); return s; } void InteractionTest(simplexData a, simplexData b) { if ((a.flags & Fluid) == 0 || (b.flags & Fluid) == 0) { // immediately reject simplex pairs that share particles: int j = 0; for (int i = 0; i < a.size; ++i) for (j = 0; j < b.size; ++j) if (simplices[a.start + i] == simplices[b.start + j]) return; // if all particles are in the same group: if (a.group == b.group) { // if none are self-colliding, reject the pair. if ((a.flags & b.flags & SelfCollide) == 0) return; } // category-based filtering: else if ((a.mask & b.category) == 0 || (b.mask & a.category) == 0) return; // swap simplices (except for category) so that B is always the one-sided one. int categoryA = a.category; int categoryB = b.category; if ((a.flags & OneSided) != 0 && categoryA < categoryB) { simplexData t = a; a = b; b = t; } float4 simplexBary = BarycenterForSimplexOfSize(a.size); float4 simplexPoint; Simplex simplexShape; simplexShape.simplexStart = b.start; simplexShape.simplexSize = b.size; simplexShape.simplices = simplices; simplexShape.positions = restPositions; float simplexRadiusA = 0; float simplexRadiusB = 0; // skip the contact if there's self-intersection at rest: if (a.group == b.group && (a.restPosEnabled || b.restPosEnabled)) { SurfacePoint restPoint = Optimize(simplexShape, restPositions, restOrientations, principalRadii, simplices, a.start, a.size, simplexBary, simplexPoint, 4, 0); for (j = 0; j < a.size; ++j) simplexRadiusA += principalRadii[simplices[a.start + j]].x * simplexBary[j]; for (j = 0; j < b.size; ++j) simplexRadiusB += principalRadii[simplices[b.start + j]].x * restPoint.bary[j]; // compare distance along contact normal with radius. if (dot(simplexPoint - restPoint.pos, restPoint.normal) < simplexRadiusA + simplexRadiusB) return; } simplexBary = BarycenterForSimplexOfSize(a.size); simplexShape.positions = positions; SurfacePoint surfacePoint = Optimize(simplexShape, positions, orientations, principalRadii, simplices, a.start, a.size, simplexBary, simplexPoint); simplexRadiusA = 0; simplexRadiusB = 0; float4 velocityA = FLOAT4_ZERO, velocityB = FLOAT4_ZERO, normalB = FLOAT4_ZERO; for (j = 0; j < a.size; ++j) { int particleIndex = simplices[a.start + j]; simplexRadiusA += principalRadii[particleIndex].x * simplexBary[j]; velocityA += velocities[particleIndex] * simplexBary[j]; } for (j = 0; j < b.size; ++j) { int particleIndex = simplices[b.start + j]; simplexRadiusB += principalRadii[particleIndex].x * surfacePoint.bary[j]; velocityB += velocities[particleIndex] * surfacePoint.bary[j]; normalB += asfloat(normals[particleIndex]) * surfacePoint.bary[j]; } float dAB = dot(simplexPoint - surfacePoint.pos, surfacePoint.normal); float vel = dot(velocityA - velocityB, surfacePoint.normal); // check if the projected velocity along the contact normal will get us within collision distance. if (vel * deltaTime + dAB <= simplexRadiusA + simplexRadiusB + collisionMargin) { // adapt collision normal for one-sided simplices: if ((b.flags & OneSided) != 0 && categoryA < categoryB) OneSidedNormal(normalB, surfacePoint.normal); uint count = particleContacts.IncrementCounter(); if (count < maxContacts) { contact c = (contact)0; c.normal = surfacePoint.normal; c.pointA = simplexBary; c.pointB = surfacePoint.bary; c.bodyA = a.index; c.bodyB = b.index; particleContacts[count] = c; contactPairs[count] = uint2(c.bodyA,c.bodyB); InterlockedMax(dispatchBuffer[0],(count + 1) / 128 + 1); InterlockedMax(dispatchBuffer[3], count + 1); } } } } [numthreads(128, 1, 1)] void BuildContactList (uint3 id : SV_DispatchThreadID) { unsigned int i = id.x; if (i >= pointCount + edgeCount + triangleCount) return; // current cell: int4 cellCoord = R_cellCoords[i]; uint cellIndex = cellHashToMortonIndex[GridHash(cellCoord)]; uint n = R_cellOffsets[cellIndex]; uint end = n + R_cellCounts[cellIndex]; uint indexInGrid = n + R_offsetInCell[i]; simplexData data1 = GetSimplexData(indexInGrid); // in current cell, only consider simplices that appear after this one: for (++indexInGrid; indexInGrid < end; ++indexInGrid) InteractionTest(data1,GetSimplexData(indexInGrid)); // neighbour cells ahead of the current one in the same level: for(int j = 0; j < 13; ++j) { // get first simplex in neighbor cell: cellIndex = cellHashToMortonIndex[GridHash(cellCoord + aheadCellNeighborhood[j])]; n = R_cellOffsets[cellIndex]; end = n + R_cellCounts[cellIndex]; // iterate through all simplices in neighbor cell: for (; n < end; ++n) InteractionTest(data1,GetSimplexData(n)); } // higher grid levels: for (uint m = 1; m <= R_levelPopulation[0]; ++m) { uint l = R_levelPopulation[m]; if (l <= (uint)cellCoord.w) continue; int4 parentCellCoords = GetParentCellCoords(cellCoord, l); for (int j = 0; j < 27; ++j) { // get first simplex in neighbor cell: cellIndex = cellHashToMortonIndex[GridHash(parentCellCoords + cellNeighborhood[j])]; n = R_cellOffsets[cellIndex]; end = n + R_cellCounts[cellIndex]; // iterate through all simplices in neighbor cell: for (; n < end; ++n) InteractionTest(data1,GetSimplexData(n)); } } } [numthreads(128, 1, 1)] void FindFluidNeighborsInSameLevel (uint3 id : SV_DispatchThreadID) { unsigned int i = id.x; if (i >= dispatchBuffer[3]) return; // current cell: int4 cellCoord = R_cellCoords[sortedFluidIndices[i]]; int4 neighborCoord, particleCoord; uint cellIndex, n, end; float4 d; float interactionRadius, cellSize; int fluidB, level; float4 posA = sortedPositions[i]; float radA = sortedFluidMaterials[i].x; uint count = 0; // neighbour cells in same level. We don't need atomics for this, // and we can guarantee that the neighbors for each particle will // appear in sorted order. for(int j = 0; j < 27; ++j) { // get cell start/end neighborCoord = cellCoord + cellNeighborhood[j]; cellIndex = cellHashToMortonIndex[GridHash(neighborCoord)]; n = R_cellOffsets[cellIndex]; end = n + R_cellCounts[cellIndex]; // iterate through all simplices in neighbor cell: for (; n < end; ++n) { fluidB = sortedSimplexToFluid[n]; if (fluidB >= 0 && fluidB != (int)i) { // due to hash collisions, two neighboring cells might map to the same // hash bucket, and we'll add the same set of particles twice to the neighbors list. // So we only consider particles that have the same spatial coordinates as the cell. level = GridLevelForSize(sortedFluidMaterials[fluidB].x); cellSize = CellSizeOfLevel(level); particleCoord = int4(floor((sortedPositions[fluidB] - solverBounds[0].min_)/ cellSize).xyz,level); if (any (particleCoord - neighborCoord)) continue; // calculate particle center distance: d = posA - sortedPositions[fluidB]; d.w = 0; interactionRadius = max(radA, sortedFluidMaterials[fluidB].x); if (dot(d,d) <= interactionRadius * interactionRadius && count < maxNeighbors) neighbors[maxNeighbors * i + (count++)] = fluidB; } } } neighborCounts[i] = count; } [numthreads(128, 1, 1)] void FindFluidNeighborsInUpperLevels (uint3 id : SV_DispatchThreadID) { unsigned int i = id.x; if (i >= dispatchBuffer[3]) return; int s = sortedFluidIndices[i]; int4 cellCoord = R_cellCoords[s]; int4 parentCellCoords, neighborCoord, particleCoord; uint cellIndex, n, end; float4 d; float interactionRadius, cellSize; int fluidB, level; float4 posA = sortedPositions[i]; float radA = sortedFluidMaterials[i].x; for (uint m = 1; m <= R_levelPopulation[0]; ++m) { uint l = R_levelPopulation[m]; // skip levels below this particle's level. if (l <= (uint)cellCoord.w) continue; parentCellCoords = GetParentCellCoords(cellCoord, l); for (int j = 0; j < 27; ++j) { // get cell start/end neighborCoord = parentCellCoords + cellNeighborhood[j]; cellIndex = cellHashToMortonIndex[GridHash(neighborCoord)]; n = R_cellOffsets[cellIndex]; end = n + R_cellCounts[cellIndex]; // iterate through all simplices in neighbor cell: for (; n < end; ++n) { fluidB = sortedSimplexToFluid[n]; if (fluidB >= 0) { // due to hash collisions, two neighboring cells might map to the same // hash bucket, and we'll add the same set of particles twice to the neighbors list. // So we only consider particles that have the same spatial coordinates as the cell. level = GridLevelForSize(sortedFluidMaterials[fluidB].x); cellSize = CellSizeOfLevel(level); particleCoord = int4(floor((sortedPositions[fluidB] - solverBounds[0].min_)/ cellSize).xyz,level); if (any (particleCoord - neighborCoord)) continue; // calculate particle center distance: d = posA - sortedPositions[fluidB]; d.w = 0; interactionRadius = max(radA, sortedFluidMaterials[fluidB].x); if (dot(d,d) <= interactionRadius * interactionRadius) { uint entryA, entryB; InterlockedAdd(neighborCounts[i], 1, entryA); InterlockedAdd(neighborCounts[fluidB], 1, entryB); if (entryA < maxNeighbors && entryB < maxNeighbors) { neighbors[maxNeighbors * i + entryA] = fluidB; neighbors[maxNeighbors * fluidB + entryB] = i; } } } } } } // convert simplex index to start in indices array. int o; sortedFluidIndices[i] = simplices[GetSimplexStartAndSize(s, o)]; }