Основы LibCanvas — практика



    Это продолжение статьи про основы LibCanvas. Если первая часть затрагивала теоретические засады, то в этой части мы перейдём к практике и постараемся реализовать совсем базовые и простые вещи. Цель статьи — осилить самые основы LibCanvas, мы напишем очень простые скрипты, малопригодные для использования в реальной жизни, но вы их можете развить во что-то великое.



    HTML


    Структура html-файла очень простая:
    <!DOCTYPE html>
    <html>
       <head>
          <meta charset="utf-8" />
          <script src="atom.js"></script>
          <script src="libcanvas.js"></script>
          <script src="application.js"></script>
       </head>
       <body>
          <canvas></canvas>
       </body>
    </html>
    


    Необходимо подключить последние AtomJS и LibCanvas. Новичкам лучше использовать *-full-compiled версии с репозитория.

    Далее мы будем рассматривать содержимое application.js

    Простая отрисовка в холст


    Мы всё так же можем отрисовывать в холст при помощи контекста. Необходимо дождаться полной загрузки DOM, получить элемент canvas, его контекст и отрисовать две фигуры. Я сразу же глобализовал всё содержимое LibCanvas. В дальнейшем, когда я буду показывать примеры — будет подразумеваться только содержимое onready-функции.

    Сейчас мы просто нарисуем зелёный прямоугольник и красный круг на светло-кофейный холст:
    LibCanvas.extract();
    
    atom.dom(function() {
       var canvas  = atom.dom('canvas').first;
       var context = canvas.getContext('2d-libcanvas');
    
       context
          .fillAll( '#efebe7' )
          .fill( new Rectangle( 75, 75, 30, 30 ), 'green' )
          .fill( new Circle   ( 50, 50, 20 )    , '#c00'  );
    });
    



    Анимация


    Но такой подход позволяет отрисовывать только какие-нибудь статичные картинки. Для чего-нибудь интерактивного необходимо создать объект LibCanvas. Сделаем очень простое приложение — чёрный холст заполняется случайными прямоугольниками зелёного цвета.
    Обратите внимание на два важных момента:
    1. По-умолчанию холст очищается каждый кадр, потому необходимо выключить это поведение по-умолчанию при помощи { clear: null }
    2. Функция, переданная в start относится к этапу рендера, потому она не будет вызываться без обновления холста, чего мы добились благодаря .addFunc( libcanvas.update ). На самом деле это плохое решение, но в данном случае оно — подходит.

    var libcanvas = new LibCanvas('canvas', { clear: null });
    
    libcanvas.ctx.fillAll('black');
    
    libcanvas
       .start(function () {
          this.ctx.fill(
             new Rectangle({
                from: this.ctx.rectangle.getRandomPoint(),
                size: [ 2, 2 ]
             }), 'green'
          );
       })
       .addFunc( libcanvas.update );
    



    Объекты


    Теперь добавим объект. Пускай это будет отрезок чёрного цвета, который будет вращаться вокруг одной из своих точек. В этот раз нам необходимо очищать холст перед отрисовкой следующего кадра, потому мы не будет отменять clear. Смотрим код и читаем комментарии:

    var Item = atom.Class({
       Implements: [ Drawable ],
       // В конструкторе мы принимаем два аргумента -
       // Point - центр вращения и скорость вращения в радианах за секунду
       initialize: function (center, speed) {
          this.line = new Line( center,
             // Мы клонируем и смещаем точку вправо так мы получаем
             // ещё одну точку в двадцати пикселях от текущей
             center.clone().move([ 20, 0 ])
          );
          // время передаётся в милисекундах, а скорость указана в миллисекундах
          // потому необходимо привести скорость к формату "радиан/миллисекунду"
          this.speed = speed / 1000;
       },
       update: function (time) {
          // мы вращаем вторую точку прямой вокруг первой
          this.line.to.rotate(
             // для того, чтобы скорость вращения не зависела от fps мы
             // умножаем скорость на прошедшее время. Если fps низкий, то
             // обновляться будет реже, но изменения будут на больший угол
             this.speed * time,
             this.line.from
          );
          // Необходимо сообщить libcanvas, что изменился
          // внешний вид холста и надо бы его перерисовать
          this.libcanvas.update();
       },
       draw: function () {
          // просто рисуем прямую
          this.libcanvas.ctx
             .stroke( this.line, 'black' );
       }
    });
    
    var libcanvas = new LibCanvas('canvas');
    
    // Мы конструируем новый объект
    // Обратите внимание на запись угла. Человеку обычно привычнее считать угол в градусах
    // В программировании принято хранить угол в радианах.
    // Такая запись позволяет легко получить радианы из градусов
    var item = new Item( new Point(50, 50), (180).degree() );
    
    
    libcanvas.addElement( item )
       // Очень важно не забыть "запустить" LibCanvas, он ждёт вашей команды "start"
       .start()
       // обратите внимание, что если мы просто передадим "item.update", то он будет вызван
       // с неверным контекстом, потому привязываем его при помощи bind к объекту:
       .addFunc( item.update.bind(item) );
    


    У нас получилась крутящаяся стрелочка.

    Реакция на мышь


    Давайте возьмём нашу стрелочку в красный круг, который можно таскать. Это достаточно просто, я покажу, что изменилось, как видите, всего несколько строчек. Обратите внимание, что я создал свойство shape, а не circle. Это необходимо для Draggable.

    var Item = atom.Class({
       Implements: [ Drawable, Draggable /* Таскаемый */ ],
       [...]
       initialize: function (center, speed) {
          [...]
          // создаём круг
          this.shape = new Circle( center, 25 );
       },
       [...]
       draw: function () {
          [...]
             .stroke( this.shape, '#c00' );
       }
    });
    
    [...]
    libcanvas.listenMouse();
    item.draggable()
    


    Мы видим, что практически всё заработало, но есть ошибка, когда мы тягаем круг — стрелка меняет свою длину. Секрет в том, что, когда мы перемещаем круг, также перемещается точка center, которая является началом отрезка. Конец отрезка остаётся на месте и начинает вертеться по новой траектории. Его необходимо смещать вместе с началом. Это очень легко сделать, подписавшись на событие move у точки:

    initialize: function (center, speed) {
       [...]
       // Необходимо, чтобы вторая точка двигалась вместе с первой
       center.addEvent('move', function (diff) {
          this.line.to.move(diff);
       }.bind(this))
    },
    


    Теперь корректно

    Секундомер


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

    var StopWatch = atom.Class({
       Implements: [ Drawable, Draggable ],
       initialize: function (center) {
          this.center   = center;
          this.millisec = this.line();
          this.seconds  = this.line();
          this.minutes  = this.line();
          this.shape    = new Circle( center, 25 );
    
          center.addEvent('move', function (diff) {
             // мы вызываем метод "move" с параметром "diff" у всех точек
             [this.millisec.to, this.seconds.to, this.minutes.to].invoke('move', diff);
          }.bind(this));
       },
       // Метод для удобного создания линии
       line: function () {
          return new Line( this.center, this.center.clone().move([0, -20]) );
       },
       update: function (time) {
          var full = (360).degree();
    
          // Методы toSeconds, toMinutes и toHours взяты из LibCanvas.Utils.Math
          // Миллисекундная стрелка должна сделать полный оборот за секунду
          this.millisec.to.rotate( full * time.toSeconds(), this.center );
          // Секундная - за минуту
          this.seconds .to.rotate( full * time.toMinutes(), this.center );
          // Минутная - за час
          this.minutes .to.rotate( full * time.toHours()  , this.center );
          this.libcanvas.update();
       },
       draw: function () {
          this.libcanvas.ctx
             .stroke( this.shape   , '#c00' )
             .stroke( this.millisec, '#999' )
             .stroke( this.seconds , '#000' )
             .stroke( this.minutes , '#090' );
       }
    });
    


    Вот, получилось!

    Заключение


    Да, порог вхождения высок. Но взамен вы получаете высокооптимизированное приложение, расширяемость, хорошую архитектуру и мощный инструментарий. Надеюсь статьи пролили свет на LibCanvas.

    Если у вас всё-ещё есть вопросы — можете смело писать в комментарии, на емейл shocksilien@gmail.com или в джаббер shock@jabber.com.ua
    Поделиться публикацией

    Похожие публикации

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 21

      +6
      Да, порог вхождения высок.

      Откуда вообще такие мысли?
      По-моему высокого порога вхождения в IT не существует. Это в квантовой физике он высокий, а тут все сложности решает интерес и упорство. :)
        +3
        Ну я сужу по тому, что интереса и упорства у многих не хватает. Жалуются, что LibCanvas — страшный и слишком профессиональный.
          +2
          По сути, путь к вершинам квантовой физики тот же — интерес и упорство. Конечно, без знания основ не стоит начинать разбираться в эффекте Холла, но и в программировании так же — если не знать, зачем нужен оператор «new», то порог вхождения будет очень высоким )
            0
            осторожно — злая ступенька!
            +2
            Спасибо, с утра почитал обе части — довольно-таки интересно. Вполне возможно, что нашлось занятие на выходные =)
              –5
              выглядит паршиво. есть поддержка антиалиасинга?
                +3
                Что паршиво? Поддержка антиалиасинга встроена во все браузеры, фреймворк только использует их api.
                  –2
                  лесенка у линий. и в зависимости от угла у линий разная толщина. видимо фиговая поддержка…
                    +3
                    Это как раз сглаженные линии. Судя по всему используется достаточно быстрый алгоритм Ву для рисования сглаженных линий (а не алгоритм Брезенхэма для несглаженных).

                    Алгоритм Ву (используется сейчас):


                    Алгоритм Брезенхэма:


                    Да, возможно сглаженность не очень симпатично смотрится при определённых углах, но это компромисс скорости и внешнего вида
                      –1
                      интересно, как бы в этом случае показал себя dda алгоритм

                      впрочем, есть подозрение, что косичка вместо линии — следствие использования неправильной гамма-коррекции.
                        0
                        DDA — это, вроде, тот же Брезенхэм, но неоптимизированный (работает с float-ами, а не int-ами)

                        Ну в Гимпе результат похожий (хоть и получше). Видимо, таки дело в том, что немного пожертвовали красотой в угоду скорости. При активном использовании эта косичка не раздражает.


                        В 3dmax вон вообще линии без сглаживания — и ничего.
                          –1
                          есть и оптимизированные целочисленные версии.

                          лучше бы они не занимались жертвоприношениями, а дали возможность выбора — сделать красиво, либо абы как.

                            0
                            Вообще я согласен с этой идеей — почему бы не сделать три режима — «Быстрый но ужасный», «Идеально средний»(как сейчас) и «Медленный но офигенный»? Было бы классно.
                            0
                            Уже с сглаживанием :)
                  +2
                  Расскажете о спрайтовой анимации?
                    +2
                    Хорошо) Это будет одним из следующих топиков)
                    +2
                    Хотелось бы пример посложнее. Ведь цель таких библиотек в том, что они позволяют легко решать задачи, которые без них решаются трудно, а квадрат с кругом, как на примере, легко нарисовать и без всяких либ.
                      +2
                      Просто меня просили примеров «полегче», чтобы было легко ввойти и с чего начать экспериментировать:
                      Я хотел разобраться в LibCanvas — не получилось. Примеры — да, работают. Объяснения — вроде понятны. Но нет объяснения структуры и от этого не ясно как совмещать различные элементы.

                      Я недавно описывал Ping-Pong — это более сложный пример. Плюс есть примеры, где можно порыться в исходниках. Ну и да — планирую описывать что-то более интересное.
                      0
                      А существует ли более удобочитаемое описание API с примерами для библиотечки?
                      0
                      Примеры: libcanvas.github.com/
                      Api Docs: github.com/theshock/libcanvas/tree/master/Docs/Ru

                      Но тут покрыто приблизительно 50% возможностей библиотеки. Стараюсь потихоньку наверстывать.
                      Небольшая проблема в том, что добавляя новые фичи я их сначала хорошенько тестирую и обкатываю и только потом добавляю в примеры и апи. Например, сцены, хотя я на них и переписал "Пятнашки".

                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                      Самое читаемое