Симуляция жидкости на основе ячеек: модель локального давления? - PullRequest
11 голосов
/ 24 мая 2011

Я пытаюсь добавить полуреалистичную воду в мой 2D-платформер на основе плитки.Вода должна действовать как живая, с моделью давления, которая работает исключительно локально.(IE. Может использовать данные только из ячеек рядом с ним). Эта модель нужна из-за характера моей игры, когда вы не можете быть уверены, что нужные вам данные не находятся внутри области, которой нет в памяти.

До сих пор я пробовал один метод, но я не мог уточнить его настолько, чтобы работать с моими ограничениями.

Для этой модели каждая ячейка будет слегка сжимаемой, в зависимости от количества воды внад клеткой.Когда содержание воды в клетке превышало нормальную емкость, клетка пыталась расширяться вверх.Это создало довольно приятную симуляцию, пусть и медленную (не задержку; изменения в воде требовали времени для распространения.), Время от времени.Когда я попытался внедрить это в свой движок, я обнаружил, что моим ограничениям не хватало точности, необходимой для его работы.Я могу предоставить более подробное объяснение или ссылку на исходную концепцию, если хотите.

Мои ограничения:

  • Только 256 дискретных значений для уровня воды.(Без переменных с плавающей точкой :() - РЕДАКТИРОВАТЬ. С плавающей точкой все в порядке.
  • Фиксированный размер сетки.
  • Только 2D.
  • Конфигурации U-Bend должны работать.

Я использую язык C #, но я, вероятно, могу взять другие языки и перевести его на C #.

Вопрос в том, может ли кто-нибудь дать мне модель давления для воды,следовать моим ограничениям как можно ближе?

Ответы [ 3 ]

3 голосов
/ 28 мая 2011

Как насчет другого подхода?

Забудьте о поплавках, это требует проблем округления в долгосрочной перспективе. Вместо этого, как насчет единицы воды?

Каждая клетка содержит определенное количество единиц воды. Каждую итерацию вы сравниваете ячейку с ее 4 соседями и перемещаете, скажем, 10% (измените это, чтобы изменить скорость распространения) разницы в количестве единиц воды. Функция картирования переводит единицы воды в уровень воды.

Чтобы избежать проблем порядка расчета, используйте два значения: одно для старых единиц, другое для новых. Рассчитайте все, а затем скопируйте обновленные значения обратно. 2 дюйма = 8 байтов на ячейку. Если у вас есть миллион ячеек, это все равно только 8 МБ.

Если вы на самом деле пытаетесь смоделировать волны, вам также необходимо сохранить поток - 4 значения, 16 МБ. Чтобы заставить волну придать потоку некоторую инерцию - после того, как вы вычислите желаемый поток, затем передвиньте предыдущий поток, скажем, на 10% до желаемого значения.

2 голосов
/ 01 июня 2011

Попробуйте обработать каждую смежную область воды как отдельную область (например, заполнение паводком) и проследите, чтобы 1) самая низкая ячейка (и), куда может сбежать вода, и 2) самая высокая ячейка (и), из которой может поступать вода, затем двигайтесь вода сверху донизу. Это не локально, но я думаю, что вы можете рассматривать края области, на которую хотите повлиять, как не связанные, и обрабатывать любое подмножество, которое хотите. Переоцените, какие области являются смежными в каждом кадре (повторно затопите в каждом кадре), чтобы, когда сгустки сходились, они могли начать рассматриваться как одна.

Вот мой код из демонстрации идеи 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 голос
/ 01 июня 2011

С точки зрения гидродинамики, достаточно популярным семейством алгоритмов на основе решеток является так называемый метод Lattice Boltzmann . Простая реализация, игнорирующая все мелкие детали, которые радуют ученых, должна быть относительно простой и быстрой, а также иметь разумно правильную динамику.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...