首页 文章

基于细胞的液体模拟:局部压力模型?

提问于
浏览
11

我正试图在我的基于瓷砖的2D平台上添加半真实的水 . 水必须有点栩栩如生,压力模型完全局部运行 . (IE . 只能使用靠近它的单元格中的数据)由于我的游戏性质,你不能确定你需要的数据不在内存不在的区域内,因此需要这个模型 .

到目前为止,我已尝试过一种方法,但我无法对其进行精炼以适应我的约束 .

对于该模型,每个细胞将是轻微可压缩的,这取决于上述细胞中的水量 . 当细胞的含水量大于正常容量时,细胞会尝试向上扩展 . 这创造了一个相当不错的模拟,速度慢(不滞后;水中的变化需要一段时间来传播 . ),有时 . 当我试图将它实现到我的引擎中时,我发现我的局限性缺乏其工作所需的精度 . 如果您愿意,我可以提供更深入的解释或原始概念的链接 .

我的约束:

  • 水位仅有256个离散值 . (没有浮点变量:() - 编辑 . 浮点数很好 .

  • 固定网格大小 .

  • 仅限2D .

  • U-Bend配置必须正常工作 .

我使用的语言是C#,但我可以使用其他语言并将其翻译为C# .

问题是,有人能给我一个水压力模型,尽可能地遵循我的约束吗?

3 回答

  • 3

    尝试将每个连续的水域区域作为单个区域(如洪水填充)处理,并跟踪1)水可以逃逸的最低单元格和2)水可以来自的最高单元格,然后将水从从上到下 . 这不是本地的,但我认为您可以将要影响的区域的边缘视为未连接,并处理您想要的任何子集 . 重新评估每个帧上哪些区域是连续的(在每个帧上重新泛洪),以便当blob收敛时,它们可以开始被视为一个 .

    这是我的代码来自Windows Forms演示的想法 . 它可能需要一些微调,但在我的测试中似乎工作得很好:

    public partial class Form1 : Form
    {
      byte[,] tiles;
      const int rows = 50;
      const int cols = 50;
      public Form1()
      {
         SetStyle(ControlStyles.ResizeRedraw, true);
         InitializeComponent();
         tiles = new byte[cols, rows];
         for (int i = 0; i < 10; i++)
         {
            tiles[20, i+20] = 1;
            tiles[23, i+20] = 1;
            tiles[32, i+20] = 1;
            tiles[35, i+20] = 1;
            tiles[i + 23, 30] = 1;
            tiles[i + 23, 32] = 1;
            tiles[21, i + 15] = 2;
            tiles[21, i + 4] = 2;
            if (i % 2 == 0) tiles[22, i] = 2;
         }
         tiles[20, 30] = 1;
         tiles[20, 31] = 1;
         tiles[20, 32] = 1;
         tiles[21, 32] = 1;
         tiles[22, 32] = 1;
         tiles[33, 32] = 1;
         tiles[34, 32] = 1;
         tiles[35, 32] = 1;
         tiles[35, 31] = 1;
         tiles[35, 30] = 1;
      }
    
      protected override void OnPaint(PaintEventArgs e)
      {
         base.OnPaint(e);
         using (SolidBrush b = new SolidBrush(Color.White))
         {
            for (int y = 0; y < rows; y++)
            {
               for (int x = 0; x < cols; x++)
               {
                  switch (tiles[x, y])
                  {
                     case 0:
                        b.Color = Color.White;
                        break;
                     case 1:
                        b.Color = Color.Black;
                        break;
                     default:
                        b.Color = Color.Blue;
                        break;
                  }
                  e.Graphics.FillRectangle(b, x * ClientSize.Width / cols, y * ClientSize.Height / rows,
                     ClientSize.Width / cols + 1, ClientSize.Height / rows + 1);
               }
            }
         }
      }
    
      private bool IsLiquid(int x, int y)
      {
         return tiles[x, y] > 1;
      }
    
      private bool IsSolid(int x, int y)
      {
         return tiles[x, y] == 1;
      }
    
      private bool IsEmpty(int x, int y)
      {
         return IsEmpty(tiles, x, y);
      }
    
      public static bool IsEmpty(byte[,] tiles, int x, int y)
      {
         return tiles[x, y] == 0;
      }
    
      private void ProcessTiles()
      {
         byte processedValue = 0xFF;
         byte unprocessedValue = 0xFF;
    
         for (int y = 0; y < rows; y ++)
            for (int x = 0; x < cols; x++)
            {
               if (IsLiquid(x, y))
               {
                  if (processedValue == 0xff)
                  {
                     unprocessedValue = tiles[x, y];
                     processedValue = (byte)(5 - tiles[x, y]);
                  }
                  if (tiles[x, y] == unprocessedValue)
                  {
                     BlobInfo blob = GetWaterAt(new Point(x, y), unprocessedValue, processedValue, new Rectangle(0, 0, 50, 50));
                     blob.ProcessMovement(tiles);
                  }
               }
            }
      }
    
      class BlobInfo
      {
         private int minY;
         private int maxEscapeY;
         private List<int> TopXes = new List<int>();
         private List<int> BottomEscapeXes = new List<int>();
         public BlobInfo(int x, int y)
         {
            minY = y;
            maxEscapeY = -1;
            TopXes.Add(x);
         }
         public void NoteEscapePoint(int x, int y)
         {
            if (maxEscapeY < 0)
            {
               maxEscapeY = y;
               BottomEscapeXes.Clear();
            }
            else if (y < maxEscapeY)
               return;
            else if (y > maxEscapeY)
            {
               maxEscapeY = y;
               BottomEscapeXes.Clear();
            }
            BottomEscapeXes.Add(x);
         }
         public void NoteLiquidPoint(int x, int y)
         {
            if (y < minY)
            {
               minY = y;
               TopXes.Clear();
            }
            else if (y > minY)
               return;
            TopXes.Add(x);
         }
         public void ProcessMovement(byte[,] tiles)
         {
            int min = TopXes.Count < BottomEscapeXes.Count ? TopXes.Count : BottomEscapeXes.Count;
            for (int i = 0; i < min; i++)
            {
               if (IsEmpty(tiles, BottomEscapeXes[i], maxEscapeY) && (maxEscapeY > minY))
               {
                  tiles[BottomEscapeXes[i], maxEscapeY] = tiles[TopXes[i], minY];
                  tiles[TopXes[i], minY] = 0;
               }
            }
         }
      }
    
      private BlobInfo GetWaterAt(Point start, byte unprocessedValue, byte processedValue, Rectangle bounds)
      {
         Stack<Point> toFill = new Stack<Point>();
         BlobInfo result = new BlobInfo(start.X, start.Y);
         toFill.Push(start);
         do
         {
            Point cur = toFill.Pop();
            while ((cur.X > bounds.X) && (tiles[cur.X - 1, cur.Y] == unprocessedValue))
               cur.X--;
            if ((cur.X > bounds.X) && IsEmpty(cur.X - 1, cur.Y))
               result.NoteEscapePoint(cur.X - 1, cur.Y);
            bool pushedAbove = false;
            bool pushedBelow = false;
            for (; ((cur.X < bounds.X + bounds.Width) && tiles[cur.X, cur.Y] == unprocessedValue); cur.X++)
            {
               result.NoteLiquidPoint(cur.X, cur.Y);
               tiles[cur.X, cur.Y] = processedValue;
               if (cur.Y > bounds.Y)
               {
                  if (IsEmpty(cur.X, cur.Y - 1))
                  {
                     result.NoteEscapePoint(cur.X, cur.Y - 1);
                  }
                  if ((tiles[cur.X, cur.Y - 1] == unprocessedValue) && !pushedAbove)
                  {
                     pushedAbove = true;
                     toFill.Push(new Point(cur.X, cur.Y - 1));
                  }
                  if (tiles[cur.X, cur.Y - 1] != unprocessedValue)
                     pushedAbove = false;
               }
               if (cur.Y < bounds.Y + bounds.Height - 1)
               {
                  if (IsEmpty(cur.X, cur.Y + 1))
                  {
                     result.NoteEscapePoint(cur.X, cur.Y + 1);
                  }
                  if ((tiles[cur.X, cur.Y + 1] == unprocessedValue) && !pushedBelow)
                  {
                     pushedBelow = true;
                     toFill.Push(new Point(cur.X, cur.Y + 1));
                  }
                  if (tiles[cur.X, cur.Y + 1] != unprocessedValue)
                     pushedBelow = false;
               }
            }
            if ((cur.X < bounds.X + bounds.Width) && (IsEmpty(cur.X, cur.Y)))
            {
               result.NoteEscapePoint(cur.X, cur.Y);
            }
         } while (toFill.Count > 0);
         return result;
      }
    
      private void timer1_Tick(object sender, EventArgs e)
      {
         ProcessTiles();
         Invalidate();
      }
    
      private void Form1_MouseMove(object sender, MouseEventArgs e)
      {
         if (e.Button == MouseButtons.Left)
         {
            int x = e.X * cols / ClientSize.Width;
            int y = e.Y * rows / ClientSize.Height;
            if ((x >= 0) && (x < cols) && (y >= 0) && (y < rows))
               tiles[x, y] = 2;
         }
      }
    }
    
  • 1

    不同的方法怎么样?

    忘掉浮标,从长远来看,这就要求出现问题 . 相反,一个单位的水怎么样?

    每个细胞含有一定数量的水 . 每次迭代,你将细胞与它的4个邻居进行比较,然后移动说10%(改变它以改变传播速度)水的单位数的差异 . 映射功能将水的单位转换为水位 .

    为避免计算顺序问题,请使用两个值,一个用于旧单元,一个用于新单元 . 计算所有内容,然后将更新的值复制回来 . 2个int =每个单元8个字节 . 如果你有一百万个细胞仍然只有8mb .

    如果你真的想模拟波浪,你还需要存储流量 - 4个值,16 mb . 为了使波浪对流动施加一些惯性 - 在计算出所需的流量之后,然后将前一流量移动到所需值的10% .

  • 2

    从流体动力学的观点来看,一种相当流行的基于格的算法族是所谓的Lattice Boltzmann方法 . 一个简单的实现,忽略了让学术界开心的所有精细细节,应该相对简单快速,并且还能获得合理正确的动态 .

相关问题