我们看到上节中粒子发射器的代码实现里使用了PointHashGridSearcher3这个类,这是我为什么说粒子发生器需要注意的原因,这是个非常精妙的算法,它将3D点映射到整数,它被这样设计的意义是为了加速搜索。
它将长方体的包围盒区域划分成多个长方形块,将3D点的坐标除以长方形块的边长,将它转换成三维整形数(Vector3),再将三维整数转换成普通整形数作为3D坐标的哈希值。
代码如下
size_t CalfFluidEngine::PointHashGridSearcher3::GetHashKeyFromBucketIndex(const Vector3<size_t>& bucketIndex) const { Vector3<size_t> wrappedIndex = bucketIndex; wrappedIndex.x = bucketIndex.x % _resolution.x; wrappedIndex.y = bucketIndex.y % _resolution.y; wrappedIndex.z = bucketIndex.z % _resolution.z; return (wrappedIndex.z * _resolution.y + wrappedIndex.y) * _resolution.x + wrappedIndex.x; } Vector3<size_t> CalfFluidEngine::PointHashGridSearcher3::GetBucketIndex(const Vector3D & position) const { Vector3<size_t> bucketIndex; bucketIndex.x = static_cast<size_t>( std::floor(position.x / _gridSpacing)); bucketIndex.y = static_cast<size_t>( std::floor(position.y / _gridSpacing)); bucketIndex.z = static_cast<size_t>( std::floor(position.z / _gridSpacing)); return bucketIndex; }PointHashGridSearcher3内部有一个二维数组,或者可以称它为桶数组,桶数组的键值就是由3D坐标转换而来的key,代表一个包围盒区域内的长方形块。桶数组存储所对应长方块内的所有粒子在粒子数组中的索引。
当需要查找邻域粒子时,就可以通过粒子坐标所对应的key值查找获取粒子所在长方块附近其他长方形块的key,从而获取粒子所在长方形块和粒子附近长方形块内其他粒子。
获取附近key值代码实现如下
void CalfFluidEngine::PointHashGridSearcher3::getNearbyKeys(const Vector3D & position, size_t * nearbyKeys) const { Vector3<size_t> originIndex = GetBucketIndex(position), nearbyBucketIndices[8]; for (int i = 0; i < 8; i++) { nearbyBucketIndices[i] = originIndex; } if ((originIndex.x + 0.5f) * _gridSpacing <= position.x) { nearbyBucketIndices[4].x += 1; nearbyBucketIndices[5].x += 1; nearbyBucketIndices[6].x += 1; nearbyBucketIndices[7].x += 1; } else { nearbyBucketIndices[4].x -= 1; nearbyBucketIndices[5].x -= 1; nearbyBucketIndices[6].x -= 1; nearbyBucketIndices[7].x -= 1; } if ((originIndex.y + 0.5f) * _gridSpacing <= position.y) { nearbyBucketIndices[2].y += 1; nearbyBucketIndices[3].y += 1; nearbyBucketIndices[6].y += 1; nearbyBucketIndices[7].y += 1; } else { nearbyBucketIndices[2].y -= 1; nearbyBucketIndices[3].y -= 1; nearbyBucketIndices[6].y -= 1; nearbyBucketIndices[7].y -= 1; } if ((originIndex.z + 0.5f) * _gridSpacing <= position.z) { nearbyBucketIndices[1].z += 1; nearbyBucketIndices[3].z += 1; nearbyBucketIndices[5].z += 1; nearbyBucketIndices[7].z += 1; } else { nearbyBucketIndices[1].z -= 1; nearbyBucketIndices[3].z -= 1; nearbyBucketIndices[5].z -= 1; nearbyBucketIndices[7].z -= 1; } for (int i = 0; i < 8; i++) { nearbyKeys[i] = GetHashKeyFromBucketIndex(nearbyBucketIndices[i]); } }获取粒子附近其他粒子代码如下
size_t nearbyKeys[8]; getNearbyKeys(origin, nearbyKeys); const double queryRadiusSquared = radius * radius; for (int i = 0; i < 8; i++) { const auto& bucket = _buckets[nearbyKeys[i]]; size_t numberOfPointsInBucket = bucket.size(); for (size_t j = 0; j < numberOfPointsInBucket; ++j) { size_t pointIndex = bucket[j]; double rSquared = (_points[pointIndex] - origin).SquareMagnitude(); if (rSquared <= queryRadiusSquared) { ...... } } } 矢量算子下一节将进入流体力学的部分,在那之前先重温或学习一下关于矢算场的数学知识,如果不了解的话,会对Navier-Stocks方程的一些数学符号感到疑惑。
《Fluid Engine Development》有介绍这部分的知识,更详细的大家可以阅读另一位博主推荐的《失算场论札记》,不过这本书我淘宝也买不到,在学校图书馆借了看,后来花9块钱买了矢量分析与场论-工程数学-第四版
偏导数\(\frac{\partial f}{\Delta } = \frac{f(x + \Delta,y,z) - f(x,y,z)}{ \Delta}\)
梯度\(\nabla f(x,y,z) = (\frac{\partial f}{\partial x} , \frac{\partial f}{\partial y} , \frac{\partial f}{\partial z} )\)
\(\nabla\) 是梯度算子,用以测量标量的变化率和变换方向,
梯度是标量场增长最快的方向,梯度的长度是这个最大的变化率