En

Введение в Quil

Данный пост является введением в Quil. Quil - это библиотека для создания интерактивной анимации в Clojure. Попросту говоря она позволяет рисовать на экране всё, что душе угодно. Quil предоставляет множество полезных функций для рисования в 2D и 3D. В это посте я покажу, как создавать и запускать эскизы (скетчи). Начнём с чего-нибудь простого, например с тригонометрии... Все её любят: синусы, косинусы, тангенсы, что может быть лучше? Наш первый скетч будет просто рисовать спираль используя функции sin и cos.

project.clj

(defproject quil-intro "0.1.0-SNAPSHOT"
  :dependencies [[org.clojure/clojure "1.8.0"]
                 [quil "2.5.0"]])

И собственно сам код quil_intro.clj:

(ns quil-intro
  (:require [quil.core :as q]))

; определяем функцию, которая рисует спираль
(defn draw []
  ; делаем фон белым
  (q/background 255)

  ; перемещаем начало координат в центр экрана
  ; по умолчанию оно находится в левом верхнем углоу
  (q/with-translation [(/ (q/width) 2) (/ (q/height) 2)]
    ; параметр t пробегает по значениям от 0 до 100 с шагом 0.01
    (doseq [t (range 0 100 0.01)]
      ; рисуем точку с координатами x=t*sin(t) и y=t*cos(t)
      (q/point (* t (q/sin t))
               (* t (q/cos t))))))

; запускаем скетч
(q/defsketch trigonometry
  :size [300 300]
  :draw draw)

Для базового скетча требуется задать draw функцию, которая будет что-нибудь рисовать. Затем вызвать макрос defsketch и передать ему draw. Вот что рисует наш скетч:

Plot of spiral

Теперь давайте немного порефакторим draw, чтобы сделать построение графиков функций чуть проще. Для этого мы зададим функцию draw-plot, которая принимает параметрическую функцию f(t) = (x, y) и границы параметра t на которых нужно построить график. Вот какой получился код:

; задаём f
(defn f [t]
  [(* t (q/sin t))
   (* t (q/cos t))])

(defn draw-plot [f from to step]
  (doseq [two-points (->> (range from to step)
                          (map f)
                          (partition 2 1))]
    ; мы могли бы использовать функцию point для того, чтобы нарисовать точку
    ; но будет лучше, если мы нарисуем линию, соединяющую соседние точки графика
    (apply q/line two-points)))

(defn draw []
  (q/background 255)
  (q/with-translation [(/ (q/width) 2) (/ (q/height) 2)]
    (draw-plot f 0 100 0.01)))

Отлично, теперь можно экспериментировать с функцией f. И здесь проявляется великолепие Quil и Clojure: перезагрузка на лету.

Перезагрузка на лету

В большинстве языков, после изменения кода нам бы понадобилось закрыть текущий скетч, скомпилировать изменения и запустить скетч заново. В Quil мы можем изменить все функции на лету и увидеть изменения немедленно. Вообще, можно запрограммировать весь скетч, от начала до конца, ни разу его не закрыв, а постепенно наращивая его функционал. Конечно, не всё можно изменить на лету, например, невозможно зарегистрировать обработчики событий мыши и клавиатуры. Но это не мешает изменить существующие, т.е. можно изначально зарегистрировать пустые обработчики, а, потом в процессе творчества, добавить в них логику. Теперь давайте вернёмся обратно к коду и изменим функцию f:

; можно получить кучу интересных графиков пробуя
; произвольные комбинации тригонометрических функций,
; например f, представленная, ниже рисует цветок
(defn f [t]
  (let [r (* 200 (q/sin t) (q/cos t))]
    [(* r (q/sin (* t 0.2)))
     (* r (q/cos (* t 0.2)))]))

Теперь нужно перегрузить изменённую функцию f. Для этого используются стандартные для Clojure приёмы:

  • Emacs: C-x C-e для перегрузки f.
  • LightTable: Ctrl+Enter для перегрузки f.
  • REPL: заново определить функцию f.

Ниже изображение цветка (и ещё пары других графиков случайных функций):

Plot of spiral Plot of water drop
Plot of leaf Plot of crazy lines

Анимация

Теперь рассмотрим ещё одну фичу Quil. До этого момента мы рисовали только статичные изображения, которые не изменялись с течением времени. На самом деле функция draw вызывается периодически с короткими интервалами, что позволяет рисовать движущиеся объекты и настоящую анимацию! Сейчас мы изменим draw так, чтобы на каждой итерации рисовалась только небольшая часть графика: линия от f(t) до f(t+1). Единственная проблема - то, что на каждой итерации t должно меняться. Для этого мы воспользуемся функцией frame-count, которая возвращает номер текущей итерации. Этот номер и будет служить числом t. Теперь cобственно реализация:

(defn draw []
  (q/with-translation [(/ (q/width) 2) (/ (q/height) 2)]
    ; заметьте, что мы не используем draw-plot здесь,
    ; т.к. нам нужно отрисовывать только небольшую часть
    ; графика на каждой итерации
    (let [t (/ (q/frame-count) 10)]
      (q/line (f t)
              (f (+ t 0.1))))))

; 'setup' - это брат функции 'draw'
; setup инициализирует скетч и вызывается только один раз,
; перед первым вызовом draw
(defn setup []
  ; draw будет вызываться 60 раз в секунду
  (q/frame-rate 60)
  ; сделаем фон белым только в setup
  ; если мы будем это вызывать в draw, то на каждой итерации
  ; скетч будет очищаться
  (q/background 255))

(q/defsketch trigonometry
  :size [300 300]
  :setup setup
  :draw draw)

Время для анимации!

Animation of leaf plot

До сих пор все наши скетчи были чёрно-белыми. Было бы неплохо добавить побольше цветов. Я не буду разбирать, как это сделать в этом посте - это будет упражнение читателю, или, если вы слишком ленивый - можно посмотреть реализации в репо на GitHub в конце этого поста. Вот что у меня получилось:

Colourful animation of flower plot

На сегодня всё. Пару финальных замечаний: Quil основан на языке Processing, который сам по себе является замечательным языком/программой для создания изображений и анимаций, но Quil улучшает его при помощи перезагрузки на лету (в принципе тоже самое можно сказать и про сам кложур по отношению к программированию в целом). Это очень классно, иметь возможность перегружать части скетча на лету и немедленно видеть эффект. Такая возможность ускоряет скорость разработки и экспериментирования, так что я всем советую поиграться с ним. Несколько полезных ссылок:

Любые коментарии приветствуются

Опубликовано 29 May 2014

comments powered by Disqus