Статьи

Динамика жидкости в реальном времени в Swift для iOS

 

Глядя на удивительный успех, достигнутый Джозефом Лордом в ускорении работы клеточных автоматов на базе процессора , я подумал, что будет интересным проектом перенести некоторый старый код динамической обработки ActionScript 3 на Swift. Оригинальный AS3 порт был на Oaxoa и на основе Джоз Стам в гидродинамике в реальном времени для игр.   Я использовал его много раз в проектах AS3 на протяжении многих лет, но никогда не настраивал его.

Моя итерация — почти прямая копия исходного кода AS3, но я потратил некоторое время на его оптимизацию.

CFD решатель использует несколько массивов для горизонтального и вертикального направления ( U и об ) и плотности ( г ). В некоторых случаях исходный код будет вызывать одну и ту же функцию, например diffuse () и advect () , последовательно ориентируясь на массивы u и v по отдельности. Таким образом, за счет повторного использования кода я создал почти дубликаты копий тех функций, которые нацелены на  u  и  v  одновременно. 

В исходном коде широко использовалась небольшая функция для получения индекса одномерного массива на основе параметров x и y . По сути, я много раз использовал это и следил за дублирующими вычислениями — создавая константы для повторно используемых значений.

Как и в других моих недавних проектах Swift, как решение, так и рендеринг выполняются в отдельных потоках. Я использую библиотеку Tobias Due Munk Async, которая абстрагирует Grand Central Dispatch для обработки потоков и кода на основе кода генерации растровых данных Джозефа  для рендеринга.

Пользовательский интерфейс довольно прост — есть один UIImageView для хранения визуализированных плотностей жидкости и кнопка сброса для запуска «взрыва». 

Внутри ViewController я переопределил метод touchesMoved () . Это позволяет мне регистрировать любые события касания в UIImageView следующим образом:

 override func touchesMoved(touches: NSSet!, withEvent event: UIEvent!)
{
    let touch = event.allTouches().anyObject().locationInView(uiImageView);
        
    let touchX = Int(touch.x / 3);

    let touchY = Int(touch.y / 3);
    [...]
}

Я не совсем уверен, почему мне нужно умножить значения на 3 — я подозреваю, что это связано с слуховым разрешением новых устройств Apple iOS — но touchX и touchY установлены на координаты в массивах CFD, поэтому я могу легко увеличить плотность, где пользователь коснулся. 

Я также храню записи о предыдущих координатах касания, чтобы использовать дельту для обновления  массивов u  и v, чтобы события касания изменяли направление потока жидкости. Поскольку предыдущие позиции касания являются необязательными, я использовал необязательную привязку внутри переопределенных функций touchesMoved () :

 [...]
if let ptx = previousTouchX
{
    if let pty = previousTouchY
    {
        u[targetIndex] = u[targetIndex] + Double((touchX - ptx) / 2)
        v[targetIndex] = v[targetIndex] + Double((touchY - pty) / 2)
    }

}
[...]

… и когда жест касания закончен, я могу обнулить предыдущие значения:

 override func touchesEnded(touches: NSSet!, withEvent event: UIEvent!)
{
    previousTouchX = nil;
    previousTouchY = nil;

}

Есть некоторая смесь между использованием и не использованием точек с запятой. Моя работа заключается в кодировании ActionScript в течение дня, и добавление их является второй натурой, поэтому, пожалуйста, простите непоследовательность. 

Код написан в XCode 6 Beta 6 и в симуляторе iOS на моем iMac, каждый шаг занимает около 0,04 секунды для сетки 200 x 200. Предстоит еще больше оптимизации, но если у вас есть возможность запустить ее на недавнем iPad на базе A7, я бы хотел услышать результаты.

Весь исходный код доступен здесь, в моем репозитории GitHub.