起因:

上期我们写了流体仿真的经典案例: 通过LBM,模拟计算涡流的形成,

当时承诺: 只要验证通过,就把代码开源出来;

ok.验证通过了,那么我也就将代码全都贴出来

代码开源并贴出:

   public class LidDrivenCavityFlow : IDisposable{public LidDrivenCavityFlow(int width = 200, int height = 100){Width = width;Height = height;Initialize();}public int Width { get; set; } = 256;public int Height { get; set; } = 256;public double Error { get; set; } = 0;public int TimeStep { get; set; } = 0;public bool IsRunning { get; set; } = false;private double dx = 1.0;//X方向的步长private double dy = 1.0;//Y方向的步长private double Lx = 1.0;//X方向的长度;private double Ly = 1.0;//Y方向的长度;private double dt = 1.0;//时间步长;private double c = 1.0;//格子的速度// 模拟参数public double rho0 = 1.0; //密度public double U = 0.1;//顶盖的速度;public double Re = 1000; //雷诺数private double niu = 0; //粘力-运动粘度系数vprivate double tau_f = 1.0;//松弛时间-无量纲public double Omega { get; set; } = 1.0;//松弛时间private int[,] e;    // 离散速度;// LBM 计算变量private double[,,] f;          // 演化前的分布函数private double[,,] F;          // 演化后的分布函数;private double[,] rho;         // 密度场private double[,] ux0;         // n层时的速度Xprivate double[,] uy0;         // n层时的速度Yprivate double[,] ux;          //n+1层 x方向速度private double[,] uy;          //n+1层 y方向速度// D2Q9 模型常量private const int Q = 9;//权重系数;private readonly double[] w = { 4.0/9, 1.0/9, 1.0/9, 1.0/9, 1.0/9, 1.0/36, 1.0/36, 1.0/36, 1.0/36 };private readonly int[] cx = { 0, 1, 0, -1, 0, 1, -1, -1, 1 };private readonly int[] cy = { 0, 0, 1, 0, -1, 1, 1, -1, -1 };// 可视化相关public VisualizationMode VisualizationMode { get; set; } = VisualizationMode.Streamline;private List<PointF>[] streamlines;private int streamlineCounter = 0;public event Action<Bitmap> VisualizationUpdated;public event Action<int> StepCompleted;public void Initialize(){dx = 1.0;dy = 1.0;Lx = dx*Width;Ly = dy*Height;//长度;dt = dx;c = dx / dt; //格子的速度;// 计算粘度niu = U * Width / Re;tau_f = 3.0 * niu + 0.5;// 松弛步长Omega = 1.0 / tau_f;// 松弛时间-无量纲e = new int[Q, 2]; //离散速度;for (int i = 0; i < Q; i++){e[i, 0] = cx[i];e[i, 1] = cy[i];}// 初始化数组f = new double[Width + 1, Height + 1, Q];F = new double[Width + 1, Height + 1, Q];rho = new double[Width + 1, Height + 1];ux = new double[Width + 1, Height + 1];uy = new double[Width + 1, Height + 1];ux0 = new double[Width + 1, Height + 1];uy0 = new double[Width + 1, Height + 1];TimeStep = 0;Error = 0;// 初始化为静止流体for (int i = 0; i <= Width; i++){for (int j = 0; j <= Height; j++){rho[i, j] = rho0;ux[i, j] = 0;uy[i, j] = 0;ux0[i, j] = 0;uy0[i, j] = 0;ux[i, Height] = U; //顶盖速度为U;for (int k = 0; k < Q; k++){f[i, j, k] = feq(k, rho[i, j], ux[i, j], uy[i, j]);}}}InitializeStreamlines();UpdateVisualization();}public void Step(){if (!IsRunning) return;// 碰撞迁移步骤,去除边界.for (int i = 1; i < Width; i++){for (int j = 1; j < Height; j++){for (int k = 0; k < Q; k++){int ip = i - e[k, 0];int jp = j - e[k, 1];F[i, j, k] = f[ip, jp, k] +(feq(k, rho[ip, jp], ux[ip, jp], uy[ip, jp]) - f[ip, jp, k]) / tau_f;}}}// 计算宏观量for (int i = 1; i < Width; i++){for (int j = 1; j < Height; j++){ux0[i, j] =  ux[i, j]; //记录上一次结果uy0[i, j] =  ux[i, j];rho[i, j] = 0;ux[i, j] = 0.0;uy[i, j] = 0.0;for (int k = 0; k < Q; k++){f[i, j, k] = F[i, j, k];rho[i, j] += f[i, j, k];ux[i, j] += e[k, 0]* f[i, j, k];uy[i, j] += e[k, 1]* f[i, j, k];}ux[i, j] /= rho[i, j];uy[i, j] /= rho[i, j];}}// ➤ 左右边界处理 (非平衡外推法)for (int j = 1; j < Height; j++){for (int k = 0; k < Q; k++){var NX = Width;// 右边界(i = Width - 1)rho[NX, j] = rho[NX-1, j];f[NX, j, k] = feq(k, rho[NX, j], ux[NX, j], uy[NX, j]) +(f[NX-1, j, k] - feq(k, rho[NX-1, j], ux[NX-1, j], uy[NX-1, j]));// 左边界(i = 0)rho[0, j] = rho[1, j];f[0, j, k] = feq(k, rho[0, j], ux[0, j], uy[0, j]) +(f[1, j, k] - feq(k, rho[1, j], ux[1, j], uy[1, j]));}}// ➤ 上下边界处理for (int i = 0; i <= Width; i++) //注意这里{for (int k = 0; k < Q; k++){var NY = Height;// 下边界(j = 0)rho[i, 0] = rho[i, 1];f[i, 0, k] = feq(k, rho[i, 0], ux[i, 0], uy[i, 0]) +(f[i, 1, k] - feq(k, rho[i, 1], ux[i, 1], uy[i, 1]));// 上边界(j = Height - 1)rho[i, NY] = rho[i, NY-1];ux[i, NY] = U;            // 设置移动壁速度uy[i, NY] = 0.0;          // 假设只在 x 方向移动f[i, NY, k] = feq(k, rho[i, NY], ux[i, NY], uy[i, NY]) +(f[i, NY-1, k] - feq(k, rho[i, NY-1], ux[i, NY-1], uy[i, NY-1]));}}// 计算误差if (TimeStep % 100 == 0){CalculateError();}TimeStep++;UpdateVisualization();StepCompleted?.Invoke(TimeStep);}public void OutputData(int step, string filePath = null){// 如果没有指定路径,使用默认文件名string fileName = filePath ?? $"cavity_{step}.dat";using (StreamWriter outFile = new StreamWriter(fileName)){// 写入文件头outFile.WriteLine("Title=\"LBM顶盖驱动流模拟\"");outFile.WriteLine("VARIABLES=\"X\",\"Y\",\"U\",\"V\"");outFile.WriteLine($"ZONE T=\"Box\", I={Width + 1}, J={Height + 1}, F=POINT");// 写入数据(注意行列顺序与C++保持一致)for (int j = 0; j <= Height; j++){for (int i = 0; i <= Width; i++){// 使用InvariantCulture确保小数点格式一致string line = string.Format(System.Globalization.CultureInfo.InvariantCulture,"{0:F8} {1:F8} {2:F8} {3:F8}",(double)i / Width,    // X坐标归一化(double)j / Height,   // Y坐标归一化ux[i, j],             // X方向速度uy[i, j]);            // Y方向速度outFile.WriteLine(line);}}}Console.WriteLine($"结果已写入文件: {fileName}");}private void CalculateError(){double temp1 = 0.0, temp2 = 0.0;for (int i = 1; i < Width; i++){for (int j = 1; j < Height; j++){temp1 += (ux[i, j] - ux0[i, j]) * (ux[i, j] - ux0[i, j]) +(uy[i, j] - uy0[i, j]) * (uy[i, j] - uy0[i, j]);temp2 += ux[i, j] * ux[i, j] + uy[i, j] * uy[i, j];}}Error = Math.Sqrt(temp1) / (Math.Sqrt(temp2) + 1e-30);}private double feq(int k, double rho_val, double ux_val, double uy_val){double eu = cx[k] * ux_val + cy[k] * uy_val;double uv = ux_val * ux_val + uy_val * uy_val;return w[k] * rho_val * (1.0 + 3.0 * eu + 4.5 * eu * eu - 1.5 * uv);}#region 可视化方法public void UpdateVisualization(){if (streamlineCounter % 8 == 0){InitializeStreamlines();}streamlineCounter++;Bitmap fluidImage = VisualizationMode switch{VisualizationMode.Density => RenderDensity(),VisualizationMode.Velocity => RenderVelocity(),VisualizationMode.Vorticity => RenderVorticity(),VisualizationMode.Pressure => RenderPressure(),  // 新增压力场渲染VisualizationMode.Combined => RenderCombined(),VisualizationMode.Streamline => RenderStreamline(),_ => RenderCombined(), // 默认渲染密度};VisualizationUpdated?.Invoke(fluidImage);}// 辅助方法:获取双线性插值的速度值(提高流线平滑度)private (double ux, double uy) GetInterpolatedVelocity(double x, double y){// 确保坐标在有效范围内x = Math.Clamp(x, 0, Width);y = Math.Clamp(y, 0, Height);// 找到当前点所在的网格单元(i,j)int i = (int)Math.Floor(x);int j = (int)Math.Floor(y);// 确保索引在有效范围内i = Math.Clamp(i, 0, Width - 2);  // 确保i+1不会越界j = Math.Clamp(j, 0, Height - 2); // 确保j+1不会越界// 四个角点的坐标double x0 = i;double x1 = i + 1;double y0 = j;double y1 = j + 1;// 双线性插值权重double dx = x - x0;double dy = y - y0;// 确保权重在[0,1]范围内dx = Math.Clamp(dx, 0, 1);dy = Math.Clamp(dy, 0, 1);// 获取四个角点的速度值double ux00 = ux[i, j];double ux10 = ux[i + 1, j];double ux01 = ux[i, j + 1];double ux11 = ux[i + 1, j + 1];double uy00 = uy[i, j];double uy10 = uy[i + 1, j];double uy01 = uy[i, j + 1];double uy11 = uy[i + 1, j + 1];// 双线性插值计算uxdouble ux_val =(1 - dx) * (1 - dy) * ux00 +dx * (1 - dy) * ux10 +(1 - dx) * dy * ux01 +dx * dy * ux11;// 双线性插值计算uydouble uy_val =(1 - dx) * (1 - dy) * uy00 +dx * (1 - dy) * uy10 +(1 - dx) * dy * uy01 +dx * dy * uy11;return (ux_val, uy_val);}private Bitmap RenderPressure(){Bitmap bmp = new Bitmap(Width + 1, Height + 1);BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height),ImageLockMode.WriteOnly,PixelFormat.Format32bppArgb);// 计算压力范围double minPressure = double.MaxValue;double maxPressure = double.MinValue;for (int i = 0; i < Width; i++){for (int j = 0; j < Height; j++){double pressure = rho[i, j] / 3.0; // 压力计算if (pressure < minPressure) minPressure = pressure;if (pressure > maxPressure) maxPressure = pressure;}}double range = Math.Max(maxPressure - minPressure, 0.001);unsafe{byte* ptr = (byte*)bmpData.Scan0;int bytesPerPixel = 4;int stride = bmpData.Stride;Parallel.For(0, Height, j =>{byte* row = ptr + (j * stride);for (int i = 0; i < Width; i++){double pressure = rho[i, j] / 3.0;double normalized = (pressure - minPressure) / range;// 使用蓝-白-红色谱表示压力Color color;if (normalized < 0.5){// 蓝色到白色渐变int blue = 255;int green = (int)(normalized * 2 * 255);int red = (int)(normalized * 2 * 255);color = Color.FromArgb(red, green, blue);}else{// 白色到红色渐变int red = 255;int green = (int)((1.0 - (normalized - 0.5) * 2) * 255);int blue = (int)((1.0 - (normalized - 0.5) * 2) * 255);color = Color.FromArgb(red, green, blue);}row[i * bytesPerPixel] = color.B;     // Bluerow[i * bytesPerPixel + 1] = color.G; // Greenrow[i * bytesPerPixel + 2] = color.R; // Redrow[i * bytesPerPixel + 3] = 255;     // Alpha}});}bmp.UnlockBits(bmpData);return bmp;}#region 绘制密度./// <summary>/// 绘制密度/// </summary>/// <returns></returns>private Bitmap RenderDensity(){Bitmap fluidImage = new Bitmap(Width, Height);// 使用LockBits提高性能BitmapData bmpData = fluidImage.LockBits(new Rectangle(0, 0, Width, Height),ImageLockMode.WriteOnly,PixelFormat.Format32bppArgb);// 计算密度范围(动态适应)double minDensity = double.MaxValue;double maxDensity = double.MinValue;for (int x = 0; x < Width; x++){for (int y = 0; y < Height; y++){if (rho[x, y] < minDensity) minDensity = rho[x, y];if (rho[x, y] > maxDensity) maxDensity = rho[x, y];}}double densityRange = maxDensity - minDensity;if (densityRange < 0.0001) densityRange = 0.0001; // 避免除零unsafe{byte* ptr = (byte*)bmpData.Scan0;int bytesPerPixel = 4;int stride = bmpData.Stride;for (int y = 0; y < Height; y++){byte* row = ptr + (y * stride);for (int x = 0; x < Width; x++){// 动态映射密度到灰度值double normalized = (rho[x, y] - minDensity) / densityRange;byte value = (byte)(normalized * 255);// BGRA格式row[x * bytesPerPixel] = value;     // Bluerow[x * bytesPerPixel + 1] = value; // Greenrow[x * bytesPerPixel + 2] = value; // Redrow[x * bytesPerPixel + 3] = 255;   // Alpha}}}fluidImage.UnlockBits(bmpData);return fluidImage;}/// <summary>/// 绘制密度(增强对比度黑白灰版)/// </summary>/// <returns></returns>private Bitmap RenderDensityGama(){if (rho == null || Width <= 0 || Height <= 0)return new Bitmap(1, 1); // 返回空图像以防无效状态Bitmap fluidImage = new Bitmap(Width, Height);// 使用LockBits提高性能BitmapData bmpData = fluidImage.LockBits(new Rectangle(0, 0, Width, Height),ImageLockMode.WriteOnly,PixelFormat.Format32bppArgb);// 初始化修正 - 使用安全的初始值double minDensity = double.MaxValue;double maxDensity = double.MinValue;// 首次遍历找出大致范围for (int x = 0; x < Width; x++){for (int y = 0; y < Height; y++){double density = rho[x, y];if (!double.IsFinite(density)) continue; // 跳过非数值if (density < minDensity) minDensity = density;if (density > maxDensity) maxDensity = density;}}// 范围有效性检查const double defaultRange = 1.0;if (minDensity > maxDensity){minDensity = 0;maxDensity = defaultRange;}// 计算裁剪边界double range = Math.Max(maxDensity - minDensity, defaultRange * 0.01); // 最小范围保护double lowerBound = minDensity + range * 0.05;double upperBound = maxDensity - range * 0.05;// 确保边界有效if (lowerBound >= upperBound){lowerBound = minDensity;upperBound = maxDensity;}// 二次遍历计算实际有效范围double effectiveMin = double.MaxValue;double effectiveMax = double.MinValue;int validPixelCount = 0; // 有效像素计数器for (int x = 0; x < Width; x++){for (int y = 0; y < Height; y++){double density = rho[x, y];// 跳过无效值和极端值if (!double.IsFinite(density)) continue;if (density < lowerBound || density > upperBound) continue;validPixelCount++;if (density < effectiveMin) effectiveMin = density;if (density > effectiveMax) effectiveMax = density;}}// 当有效像素不足时的处理if (validPixelCount < 10) // 至少需要10个有效点{effectiveMin = minDensity;effectiveMax = maxDensity;}// 确保有效范围不为零double densityRange = effectiveMax - effectiveMin;if (densityRange < double.Epsilon)densityRange = defaultRange;unsafe{byte* ptr = (byte*)bmpData.Scan0;int bytesPerPixel = 4;int stride = bmpData.Stride;// 对比度增强参数(带保护)const double gamma = 1.8;double contrastBoost = Math.Clamp(1.2, 0.1, 5.0);for (int y = 0; y < Height; y++){byte* row = ptr + (y * stride);for (int x = 0; x < Width; x++){double rawValue = rho[x, y];// 处理非数值和极端值if (!double.IsFinite(rawValue)){// 异常值显示为品红色row[x * bytesPerPixel] = 255;     // Bluerow[x * bytesPerPixel + 1] = 0;   // Greenrow[x * bytesPerPixel + 2] = 255; // Redrow[x * bytesPerPixel + 3] = 255; // Alphacontinue;}// 安全映射密度到[0,1]范围double clamped = Math.Clamp(rawValue, effectiveMin, effectiveMax);double normalized = (clamped - effectiveMin) / densityRange;normalized = Math.Clamp(normalized, 0.0, 1.0); // 二次保护// 伽马校正增强对比度(带安全保护)double gammaCorrected = Math.Pow(normalized, gamma);gammaCorrected = Math.Clamp(gammaCorrected, 0.0, 1.0);// S曲线增强(带安全计算)double contrastEnhanced = gammaCorrected;if (gammaCorrected < 0.5){double factor = Math.Clamp(gammaCorrected * 2, 0.0, 1.0);contrastEnhanced = Math.Pow(factor, contrastBoost) * 0.5;}else{double factor = Math.Clamp((1 - gammaCorrected) * 2, 0.0, 1.0);contrastEnhanced = 1 - Math.Pow(factor, contrastBoost) * 0.5;}byte value = (byte)(Math.Clamp(contrastEnhanced, 0.0, 1.0) * 255);// BGRA格式row[x * bytesPerPixel] = value;     // Bluerow[x * bytesPerPixel + 1] = value; // Greenrow[x * bytesPerPixel + 2] = value; // Redrow[x * bytesPerPixel + 3] = 255;   // Alpha}}}fluidImage.UnlockBits(bmpData);return fluidImage;}#endregionprivate Bitmap RenderVelocity(){Bitmap fluidImage = new Bitmap(Width, Height);BitmapData bmpData = fluidImage.LockBits(new Rectangle(0, 0, Width, Height),ImageLockMode.WriteOnly,PixelFormat.Format32bppArgb);// 计算当前帧最大速度double maxSpeed = 0.001;for (int x = 0; x < Width; x++){for (int y = 0; y < Height; y++){double speed = Math.Sqrt(ux[x, y] * ux[x, y] + uy[x, y] * uy[x, y]);if (speed > maxSpeed) maxSpeed = speed;}}unsafe{byte* ptr = (byte*)bmpData.Scan0;int bytesPerPixel = 4;int stride = bmpData.Stride;for (int y = 0; y < Height; y++){byte* row = ptr + (y * stride);for (int x = 0; x < Width; x++){double speed = Math.Sqrt(ux[x, y] * ux[x, y] + uy[x, y] * uy[x, y]);double normalizedSpeed = speed / maxSpeed;// 使用HSV色相表示速度方向double angle = Math.Atan2(uy[x, y], ux[x, y]);double hue = (angle + Math.PI) / (2 * Math.PI);// 饱和度表示速度大小(非线性增强)double saturation = Math.Pow(normalizedSpeed, 0.5); // 平方根增强低速度可见性Color color = HsvToRgb(hue, saturation, 1.0);row[x * bytesPerPixel] = color.B;     // Bluerow[x * bytesPerPixel + 1] = color.G; // Greenrow[x * bytesPerPixel + 2] = color.R; // Redrow[x * bytesPerPixel + 3] = 255;     // Alpha}}}fluidImage.UnlockBits(bmpData);return fluidImage;}private Bitmap RenderCombined(){int scale = 4;Bitmap fluidImage = new Bitmap(Width * scale, Height * scale);using (Graphics g = Graphics.FromImage(fluidImage)){g.SmoothingMode = SmoothingMode.AntiAlias;g.Clear(Color.Black);// 绘制密度背景(使用纹理刷提高性能)using (TextureBrush brush = new TextureBrush(densityBmp)){brush.ScaleTransform(scale, scale);g.FillRectangle(brush, 0, 0, fluidImage.Width, fluidImage.Height);}// 自适应箭头参数int arrowCount = Math.Max(10, Width / 15); // 箭头数量自适应int step = Math.Max(3, Width / arrowCount);double minArrowSpeed = 0.01 * U; // 基于顶盖速度for (int x = step / 2; x < Width; x += step){for (int y = step / 2; y < Height; y += step){double speed = Math.Sqrt(ux[x, y] * ux[x, y] + uy[x, y] * uy[x, y]);//只绘制有显著速度的区域if (speed > minArrowSpeed){Point start = new Point(x * scale + scale / 2, y * scale + scale / 2);// 箭头长度自适应(速度越大箭头越长)double arrowLength = scale * 2 + speed * scale * 5;Point end = new Point((int)(start.X + ux[x, y] * arrowLength),(int)(start.Y + uy[x, y] * arrowLength));// 箭头颜色根据速度大小变化// 计算速度比例值,限制在0-255范围内int sv = Math.Clamp((int)(255 * speed / U), 0, 255);Color arrowColor = Color.FromArgb(255,    // Alpha (完全不透明)sv,     // Red2,      // Green2       // Blue);DrawArrow(g, start, end, arrowColor, Math.Max(1, scale / 4));}}}}return fluidImage;}/// <summary>/// 漩涡/// </summary>/// <returns></returns>private Bitmap RenderVorticity()//{Bitmap fluidImage = new Bitmap(Width, Height);BitmapData bmpData = fluidImage.LockBits(new Rectangle(0, 0, Width, Height),ImageLockMode.WriteOnly,PixelFormat.Format32bppArgb);double[,] vorticity = new double[Width, Height];double maxVorticity = 0.001;// 计算涡量场(内部点)for (int x = 1; x < Width - 1; x++){for (int y = 1; y < Height - 1; y++){// 中心差分计算涡量double dudy = (uy[x, y + 1] - uy[x, y - 1]) / 2.0;double dvdx = (ux[x + 1, y] - ux[x - 1, y]) / 2.0;vorticity[x, y] = dvdx - dudy;if (Math.Abs(vorticity[x, y]) > maxVorticity)maxVorticity = Math.Abs(vorticity[x, y]);}}// 边界处理(置零)for (int x = 0; x < Width; x++){vorticity[x, 0] = 0;vorticity[x, Height - 1] = 0;}for (int y = 0; y < Height; y++){vorticity[0, y] = 0;vorticity[Width - 1, y] = 0;}unsafe{byte* ptr = (byte*)bmpData.Scan0;int bytesPerPixel = 4;int stride = bmpData.Stride;for (int y = 0; y < Height; y++){byte* row = ptr + (y * stride);for (int x = 0; x < Width; x++){double v = vorticity[x, y] / maxVorticity;int r = (v > 0) ? (int)(v * 255) : 0;int b = (v < 0) ? (int)(-v * 255) : 0;int g = (int)((1 - Math.Abs(v)) * 200); // 动态绿色分量r = Math.Clamp(r, 0, 255);g = Math.Clamp(g, 0, 255);b = Math.Clamp(b, 0, 255);row[x * bytesPerPixel] = (byte)b;     // Bluerow[x * bytesPerPixel + 1] = (byte)g; // Greenrow[x * bytesPerPixel + 2] = (byte)r; // Redrow[x * bytesPerPixel + 3] = 255;     // Alpha}}}fluidImage.UnlockBits(bmpData);return fluidImage;}#region 流线private Bitmap RenderStreamline2(){// 每4步重置一次流线if (streamlineCounter % 4 == 0 || streamlines == null){InitializeStreamlines();}streamlineCounter++;Bitmap fluidImage = new Bitmap(Width, Height);using (Graphics g = Graphics.FromImage(fluidImage)){// 使用纯黑色背景g.Clear(Color.Black);using (Bitmap densityBmp = RenderDensityColor()){ColorMatrix matrix = new ColorMatrix();matrix.Matrix33 = .5f; // 极低透明度ImageAttributes attributes = new ImageAttributes();attributes.SetColorMatrix(matrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);g.DrawImage(densityBmp,new Rectangle(0, 0, Width, Height),0, 0, Width, Height,GraphicsUnit.Pixel,attributes);}// 使用抗锯齿保证线条平滑g.SmoothingMode = SmoothingMode.AntiAlias;// 绘制所有流线for (int i = 0; i < streamlines.Length; i++){var line = streamlines[i];if (line.Count > 1){// 使用朴素的细线(宽度0.5-1.0)float lineWidth = 0.3f + (i % 3) * 0.25f;// 使用固定颜色(白色)或根据速度动态着色//Color lineColor = Color.White;// 或者根据速度动态着色(可选)Color lineColor = GetDynamicLineColor(line);using (Pen pen = new Pen(lineColor, lineWidth)){// 绘制平滑曲线g.DrawLines(pen, line.ToArray());}}}}return fluidImage;}private StreamlineRenderer mSteamHelper { get; set; }private Bitmap RenderStreamline(){// 根据速度场数据完成流线渲染if (mSteamHelper == null)mSteamHelper = new StreamlineRenderer();// ⚠️ 这里建议确认 Width 和 Height 是否已经 +1,否则可能越界或空白边缘mSteamHelper.SetDataParams(Width + 1, Height + 1, ux, uy);// 创建最终的渲染图像Bitmap fluidImage = new Bitmap(Width, Height);using (Graphics g = Graphics.FromImage(fluidImage)){// 使用黑色背景清除画布g.Clear(Color.Black);// 渲染密度图(带低透明度叠加)using (Bitmap densityBmp = RenderDensityColor()){ColorMatrix matrix = new ColorMatrix{Matrix33 = 0.5f // 设置图像整体 alpha 为 50%};using (ImageAttributes attributes = new ImageAttributes()){attributes.SetColorMatrix(matrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);g.DrawImage(densityBmp,new Rectangle(0, 0, Width, Height),0, 0, Width, Height,GraphicsUnit.Pixel,attributes);}}// 生成流线图像并绘制在画布上(叠加方式)mSteamHelper.RenderToBitmap(g);}return fluidImage;}// 绘制发光点(带光晕效果)private void DrawGlowingPoint(Graphics g, PointF point, int size, Color color){RectangleF rect = new RectangleF(point.X - size/2, point.Y - size/2, size, size);// 绘制光晕using (SolidBrush glowBrush = new SolidBrush(Color.FromArgb(100, Color.White))){g.FillEllipse(glowBrush, point.X - size, point.Y - size, size*2, size*2);}// 绘制主点using (SolidBrush brush = new SolidBrush(color)){g.FillEllipse(brush, rect);}}// 动态计算流线颜色(基于平均速度)private Color GetDynamicLineColor(List<PointF> line){if (line.Count < 5) return Color.Red;double totalSpeed = 0;int count = 0;// 计算流线上点的平均速度foreach (PointF point in line){int x = (int)Math.Floor(point.X);int y = (int)Math.Floor(point.Y);if (x >= 0 && x < Width && y >= 0 && y < Height){double speed = Math.Sqrt(ux[x, y] * ux[x, y] + uy[x, y] * uy[x, y]);totalSpeed += speed;count++;}}if (count == 0) return Color.Red;double avgSpeed = totalSpeed / count;double normalizedSpeed = Math.Min(1.0, avgSpeed / U); // U是顶盖速度// 根据速度大小返回不同颜色if (normalizedSpeed > 0.8) return Color.FromArgb(255, 255, 50, 50);   // 高速: 亮红色if (normalizedSpeed > 0.5) return Color.FromArgb(255, 255, 150, 50);  // 中高速: 橙色if (normalizedSpeed > 0.3) return Color.FromArgb(255, 255, 255, 100); // 中速: 亮黄色return Color.FromArgb(255, 100, 200, 255);                           // 低速: 亮蓝色}private void InitializeStreamlines(){// 减少流线数量(4-8条)int lineCount = Math.Max(10, Math.Min(8, Width / 40));streamlines = new List<PointF>[lineCount];streamlineCounter = 0;  // 重置流线计数器Random rand = new Random();for (int i = 0; i < lineCount; i++){streamlines[i] = new List<PointF>();float startX, startY;// 在主要涡旋区域放置起点(避开固体边界)do{if (i % 2 == 0){// 主涡旋区域(靠近顶盖中心)startX = (float)(Width * (0.3 + 0.4 * rand.NextDouble()));  // X: 30%-70%startY = (float)(Height * (0.6 + 0.3 * rand.NextDouble()));  // Y: 60%-90%(靠近顶盖)}else{// 次涡旋区域(靠近底部中心)startX = (float)(Width * (0.3 + 0.4 * rand.NextDouble()));  // X: 30%-70%startY = (float)(Height * (0.1 + 0.2 * rand.NextDouble()));  // Y: 10%-30%(靠近底部)}} while (IsSolidBoundary((int)startX, (int)startY));  // 确保起点不在固体边界// 添加起点到流线streamlines[i].Add(new PointF(startX, startY));// 沿速度场追踪后续点(最多500步,防止无限循环)int maxSteps = 600;int currentStep = 0;double currentX = startX;double currentY = startY;while (currentStep < maxSteps){// 获取当前网格点的速度(双线性插值提高精度)(double uxVal, double uyVal) = GetInterpolatedVelocity(currentX, currentY);// 计算移动步长(速度越大,步长越小,避免跳过细节)float stepSize = (float)Math.Max(0.1f, 1.0f / (Math.Sqrt(uxVal * uxVal + uyVal * uyVal) + 1e-3f));// 计算下一步坐标double nextX = currentX + uxVal * stepSize;double nextY = currentY + uyVal * stepSize;// 检查是否超出计算域边界if (nextX < 0 || nextX > Width || nextY < 0 || nextY > Height)break;// 添加新点到流线streamlines[i].Add(new PointF((float)nextX, (float)nextY));// 更新当前坐标currentX = nextX;currentY = nextY;currentStep++;}}}#endregion// 辅助方法:判断是否为固体边界(顶盖、左右、底边界)private bool IsSolidBoundary(int x, int y){// 顶盖驱动流中,边界是固体壁面return x == 0 || x == Width || y == 0 || y == Height;}private bool IsBoundary(int x, int y){return x == 0 || x == Width || y == 0 || y == Height;}private Color HsvToRgb(double h, double s, double v){h = Math.Max(0, Math.Min(1, h - Math.Floor(h)));s = Math.Max(0, Math.Min(1, s));v = Math.Max(0, Math.Min(1, v));int hi = (int)(Math.Floor(h * 6)) % 6;double f = h * 6 - Math.Floor(h * 6);double p = v * (1 - s);double q = v * (1 - f * s);double t = v * (1 - (1 - f) * s);double r, g, b;switch (hi){case 0: r = v; g = t; b = p; break;case 1: r = q; g = v; b = p; break;case 2: r = p; g = v; b = t; break;case 3: r = p; g = q; b = v; break;case 4: r = t; g = p; b = v; break;default: r = v; g = p; b = q; break;}return Color.FromArgb((int)(r * 255),(int)(g * 255),(int)(b * 255));}// 更健壮的HSV转RGB函数private static Color ColorFromHSV(float hue, float saturation, float value){// 输入验证if (float.IsNaN(hue) || float.IsInfinity(hue)) hue = 0;if (float.IsNaN(saturation) || float.IsInfinity(saturation)) saturation = 0;if (float.IsNaN(value) || float.IsInfinity(value)) value = 0;// 参数边界限制hue = Math.Clamp(hue % 360, 0, 360);if (hue < 0) hue += 360; // 处理负值saturation = Math.Clamp(saturation, 0, 1);value = Math.Clamp(value, 0, 1);// 核心计算(保证中间值范围安全)float c = Math.Clamp(value * saturation, 0, 1);float x = Math.Clamp(c * (1 - Math.Abs((hue / 60) % 2 - 1)), 0, 1);float m = Math.Clamp(value - c, 0, 1);float r = 0, g = 0, b = 0;// 角度范围处理float sector = hue / 60;if (sector < 1){r = c; g = x; b = 0;}else if (sector < 2){r = x; g = c; b = 0;}else if (sector < 3){r = 0; g = c; b = x;}else if (sector < 4){r = 0; g = x; b = c;}else if (sector < 5){r = x; g = 0; b = c;}else // 5 <= sector < 6{r = c; g = 0; b = x;}// 安全转换到RGB字节值int red = Math.Clamp((int)((r + m) * 255), 0, 255);int green = Math.Clamp((int)((g + m) * 255), 0, 255);int blue = Math.Clamp((int)((b + m) * 255), 0, 255);// 最后防线:确保颜色有效if (red < 0 || green < 0 || blue < 0 || red > 255 || green > 255 || blue > 255){// 记录错误但不中断流程//Debug.WriteLine($"Invalid HSV conversion: h={hue}, s={saturation}, v={value} => r={r}, g={g}, b={b}");return Color.Magenta; // 视觉标记错误}return Color.FromArgb(red, green, blue);}#endregionpublic void Dispose(){f = null;F = null;rho = null;ux = null;uy = null;ux0 = null;uy0 = null;GC.Collect();}public void Start() => IsRunning = true;public void Pause() => IsRunning = false;public void Reset() { Pause(); Initialize(); }public void SetGridSize(int width, int height){Width = Math.Clamp(width, 16, 512);Height = Math.Clamp(height, 16, 512);Initialize();}public void SetVisualizationMode(VisualizationMode mode){VisualizationMode = mode;UpdateVisualization();}}

结尾:以上代码仅供学习参考;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/91910.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/91910.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/91910.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

仓库管理系统-17-前端之物品类型管理

文章目录 1 表设计(goodstype) 2 后端代码 2.1 Goodstype.java 2.2 GoodstypeMapper.java 2.3 GoodstypeService.java 2.4 GoodstypeServiceImpl.java 2.5 GoodstypeController.java 3 前端代码 3.1 goodstype/GoodstypeManage.vue 3.2 添加菜单 3.3 页面显示 1、goodstype表设…

共识算法深度解析:PoS/DPoS/PBFT对比与Python实现

目录 共识算法深度解析:PoS/DPoS/PBFT对比与Python实现 1. 引言:区块链共识的核心挑战 2. 共识算法基础 2.1 核心设计维度 2.2 关键评估指标 3. PoS(权益证明)原理与实现 3.1 核心机制 3.2 Python实现 4. DPoS(委托权益证明)原理与实现 4.1 核心机制 4.2 Python实现 5. P…

3.JVM,JRE和JDK的关系是什么

3.JVM&#xff0c;JRE和JDK的关系是什么 1.JDK&#xff08;Java Development Kit&#xff09;&#xff0c;是功能齐全的Java SDK&#xff0c;包含JRE和一些开发工具&#xff08;比如java.exe,运行工具javac.exe编译工具&#xff0c;生成.class文件&#xff0c;javaw.exe,大多用…

深度学习技术发展思考笔记 || 一项新技术的出现,往往是为了解决先前范式中所暴露出的特定局限

深度学习领域的技术演进&#xff0c;遵循着一个以问题为导向的迭代规律。一项新技术的出现&#xff0c;往往是为了解决先前范式中所暴露出的特定局限。若将这些新技术看作是针对某个问题的“解决方案”&#xff0c;便能勾勒出一条清晰的技术发展脉络。 例如&#xff0c;传统的前…

Promise的reject处理: then的第二个回调 与 catch回调 笔记250804

Promise的reject处理: then的第二个回调 与 catch回调 笔记250804 Promise 错误处理深度解析&#xff1a;then 的第二个回调 vs catch 在 JavaScript 的 Promise 链式调用中&#xff0c;错误处理有两种主要方式&#xff1a;.then() 的第二个回调函数和 .catch() 方法。这两种方…

Maven模块化开发与设计笔记

1. 模块化开发模块化开发是将大型应用程序拆分成多个小模块的过程&#xff0c;每个模块负责不同的功能。这有助于降低系统复杂性&#xff0c;提高代码的可维护性和可扩展性。2. 聚合模块聚合模块&#xff08;父模块&#xff09;用于组织和管理多个子模块。它定义了项目的全局配…

sqli-labs:Less-21关卡详细解析

1. 思路&#x1f680; 本关的SQL语句为&#xff1a; $sql"SELECT * FROM users WHERE username($cookee) LIMIT 0,1";注入类型&#xff1a;字符串型&#xff08;单引号、括号包裹&#xff09;、GET操作提示&#xff1a;参数需以)闭合关键参数&#xff1a;cookee p…

大模型+垂直场景:技术纵深、场景适配与合规治理全景图

大模型垂直场景&#xff1a;技术纵深、场景适配与合规治理全景图​​核心结论​&#xff1a;2025年大模型落地已进入“深水区”&#xff0c;技术价值需通过 ​领域纵深&#xff08;Domain-Deep&#xff09;​、数据闭环&#xff08;Data-Driven&#xff09;​、部署友好&#x…

Kotlin Daemon 简介

Kotlin Daemon 是 Kotlin 编译器的一个后台进程&#xff0c;旨在提高编译性能。它通过保持编译环境的状态来减少每次编译所需的启动时间&#xff0c;从而加快增量编译的速度。 Kotlin Daemon 的主要功能增量编译&#xff1a; 只编译自上次编译以来发生更改的文件&#xff0c;节…

鸿蒙南向开发 编写一个简单子系统

文章目录 前言给设备&#xff0c;编写一个简单子系统总结 一、前言 对于应用层的开发&#xff0c;搞了十几年&#xff0c;其实已经有点开发腻的感觉了&#xff0c;翻来覆去&#xff0c;就是调用api&#xff0c;页面实现&#xff0c;最多就再加个性能优化&#xff0c;但对底层…

超详细:2026年博士申请时间线

博士申请是一场持久战&#xff0c;需要提前规划。那么&#xff0c;如何科学安排2026年博士申请时间线&#xff1f;SCI论文发表的最佳时间节点是什么&#xff1f;今天给所有打算申博的同学们&#xff0c;详细解析下&#xff0c;每个时间节点的重点内容。2025年4月&#xff1a;是…

Python爬虫实战:研究tproxy代理工具,构建电商数据采集系统

1. 引言 1.1 研究背景 在大数据与人工智能技术快速发展的背景下,网络数据已成为企业决策、学术研究、舆情监控的核心资源。据 Statista 统计,2024 年全球互联网数据总量突破 180ZB,其中 80% 为非结构化数据,需通过爬虫技术提取与转化。Python 凭借其简洁语法与丰富的爬虫…

HighgoDB查询慢SQL和阻塞SQL

文章目录环境文档用途详细信息环境 系统平台&#xff1a;N/A 版本&#xff1a;6.0,5.6.5,5.6.4,5.6.3,5.6.1,4.5.2,4.5,4.3.4.9,4.3.4.8,4.3.4.7,4.3.4.6,4.3.4.5,4.3.4.4,4.3.4.3,4.3.4.2,4.3.4,4.7.8,4.7.7,4.7.6,4.7.5,4.3.2 文档用途 本文介绍了如何对数据库日志进行分析…

day15 SPI

1串行外设接口概述1.1基本概念SPI&#xff08;串行外设接口&#xff09;是一种高速、全双工、同步的串行通信协议。串行外设接口一般是需要4根线来进行通信&#xff08;NSS、MISO、MOSI、SCK&#xff09;&#xff0c;但是如果打算实现单向通信&#xff08;最少3根线&#xff09…

阿里云微服务引擎 MSE 及 API 网关 2025 年 7 月产品动态

点击此处&#xff0c;了解微服务引擎 MSE 产品详情。

RFID技术在汽车倍速链中的应用:驱动智能制造的隐形引擎

RFID技术在汽车倍速链中的应用&#xff1a;驱动智能制造的隐形引擎某汽车汽车倍速链现场存在问题&#xff1a;&#xff08;1&#xff09;条形码需人工扫描&#xff0c;数据采集延迟率高达15%&#xff0c;导致生产调度与实际工况脱节&#xff1b;&#xff08;2&#xff09;涂装车…

ES集群调优策略

Index(写)调优 副本数置0 如果是集群首次灌入数据,可以将副本数设置为0&#xff0c;写入完毕再调整回去&#xff0c;这样副本分片只需要拷贝&#xff0c;节省了索引过 程。 PUT /my_temp_index/_settings { "number_of_replicas": 0 } 自动生成doc ID  通过Elast…

mysql的安装步骤

安装步骤1.下载软件包&#xff0c;安装依赖环境2.安装mysql服务器3.基础安装路径&#xff0c;创用户&#xff0c;与权限4.初始化5.添加环境变量&#xff0c;把服务放到开机自启里面6.启动mysql服务器7.获取初始密码并且修改一&#xff0c;下载软件&#xff08;yum&#xff0c;源…

Ant Design Vue notification自定义

<script setup> import { onMounted, h, ref, watch, getCurrentInstance } from vue; import { notification, Button } from ant-design-vue; onMounted(() > { const list [{id: 11,warnLevel: 严重,...},...]showMessage(list); }); function showMessage(){ for…

蓝桥杯算法之搜索章 - 2

大家好&#xff0c;接下来&#xff0c;我将带来对于搜索篇的新内容&#xff0c;这部分我将打算围绕DFS深度优先搜索去讲解。 温馨提示&#xff1a;由于这篇文章是接着上一篇文章的&#xff0c;如果新读者没有看过前一篇的话&#xff0c;推荐去看一下&#xff0c;不然有些地方可…