On-Line Библиотека www.XServer.ru - учебники, книги, статьи, документация, нормативная литература.
       Главная         В избранное         Контакты        Карта сайта   
    Навигация XServer.ru








 

Программное рисование во Flash MX. Управление кривыми.(Часть I)

Автор: © Aib

Программное рисование во Flash MX.
Управление кривыми.
(Часть I)

Вместо вступления.

Наконец-то!!! Теперь во Flash MX мы можем рисовать по средствам программного кода: создавать и удалять клипы, делать различные градиентные заливки, свободно управлять размером, местоположением и прочими характеристиками текста и, конечно же, рисовать прямые и кривые линии.

Но вслед за восторгом от новшеств вскоре приходят некоторые проблемы. Кривая, заданая тремя точками (начальная является концом предыдущей линии или может быть задана с помощью метода moveTo), ведёт себя совершенно непонятно - через точку (ControlX, ControlY) она даже не проходит, и может возникнуть проблема с рисованием даже простой окружности.

Не спешите "выбрасывать на свалку" инструмент curveTo, отчаявшись научиться им управлять. В этой статье будет рассказано о том, что же рисует curveTo, а также о том, как можно облегчить себе жизнь, создав несколько собственных инструментов рисования.

Что рисует curveTo().

По сути данная кривая всего-навсего CV NURBS из трёх точек, каждая с весом 1, кривая степени 2. Для тех, кто изучал 3D-Studio MAX R2 или выше или какую-либо другую программу создания 3D-графики и анимации этого достаточно (Poser и Bryce3D в расчёт не беруться, т.к. Я их не видел и наличие там NURBS гарантировать не могу). Для всех остальных потребуются пояснения. Control Vertex Non-Uniform Rational Basic Splines (сокращённо CV NURBS) - это линии, состоящие из частей кривых различного порядка (т.е. заданых математическими уравнениями некоторой степени), форма которых определяется направляющими векторами, концы которых находятся в задаваемых пользователем точках. В Flash MX эти составляющие имеют степень 2, т.е. это могут быть параболы, гиперболы и эллипсы. Но так как вес точки (коэффициэнт, влияющий на то, как близко к этой точке подходит кривая) постоянен и равен единице, то линия, создаваемая curveTo() является частью параболы. Из этого следует, что просто нарисовать обычную окружность или эллипс нельзя, а можно лишь нарисовать кривую, которая будет на них похожа. Ввиду ограниченности кол-ва задаваемых точек, сложную кривую нужно создавать по частям, рассчитывая точки так, чтобы не было изломов. Однако есть и свои плюсы. Направляющие векторы в нашем случае - это касательные к параболе в двух точках её графика, а точка (ControlX, ControlY) - пересечение этих касательных.

Это позволяет получить условие построения более сложной кривой без изломов. Для этого нужно, чтобы контрольная (сontrol) и якорная (anchor) точки первой кривой и контрольная точка второй кривой находились на одной прямой.

Однако хотелось бы, чтобы Flash сам рисовал кривые кривые любой сложности, а нам лишь нужно было задавать контрольные точки. Поэтому переходим к следующему пункту...

Метод рисование сложной кривой multicurveTo().

Данный метод позволит нам рисовать почти полноценный CV NURBS. Почти, потому что мы не сможем менять вес точки (всегда равный единице) и устанавливать степень кривой больше 2. Будет ещё одна оговорка, но о ней чуть позднее. Итак, программный код метода:

function Multicurve(Xargs, Yargs, closed){
  var Xmid = Xargs.slice(0);
  var Ymid = Yargs.slice(0);
  if ((Xmid.length != Ymid.length) || (Xmid.length < 2)){
    trace('Wrong Arguments');
    return this
  };
  if (Xmid.length == 2){
    this.moveTo(Xmid[0], Ymid[0]);
    this.lineTo(Xmid[1], Ymid[1]);
    delete Xmid;
    delete Ymid;
    return this;
  };
  var Xpoint = new Array();
  var Ypoint = new Array();
  for (var i = 1; i < Xmid.length-2; i++){
    Xpoint[i] = 0.5*(Xmid[i+1]+Xmid[i]);
    Ypoint[i] = 0.5*(Ymid[i+1]+Ymid[i]);
  };
  if (closed){
    Xpoint[0] = 0.5*(Xmid[1]+Xmid[0]);
    Ypoint[0] = 0.5*(Ymid[1]+Ymid[0]);
    Xpoint[i] = 0.5*(Xmid[i+1]+Xmid[i]);
    Ypoint[i] = 0.5*(Ymid[i+1]+Ymid[i]);
    Xpoint[i+1] = 0.5*(Xmid[i+1]+Xmid[0]);
    Ypoint[i+1] = 0.5*(Ymid[i+1]+Ymid[0]);
    Xmid[i+2] = Xmid[0];
    Ymid[i+2] = Ymid[0];
    Xpoint[i+2] = Xpoint[0];
    Ypoint[i+2] = Ypoint[0];
  } else {
    Xpoint[0] = Xmid[0];
    Ypoint[0] = Ymid[0];
    Xpoint[i] = Xmid[i+1];
    Ypoint[i] = Ymid[i+1];
    Xmid.pop();
    Ymid.pop();
  };
  this.moveTo (Xpoint[0], Ypoint[0]);
  for (var i = 1; i < Xmid.length; i++){
    this.curveTo(Xmid[i], Ymid[i], Xpoint[i], Ypoint[i]);
  };
  delete Xmid;
  delete Ymid;
  delete Xpoint;
  delete Ypoint;
  return this;
}
Object.prototype.multicurveTo = Multicurve;

Теперь разберём всё по порядку:

  function Multicurve(Xargs, Yargs, closed){

В качестве аргументов передаём два массива - координаты контрольных точек кривой по х и по у, а также параметр closed, который показывает, какую мы хотим получить кривую. Если кривая замкнута, то линия не имеет начала и конца, все заданные точки используются как контрольные (значение closed равно true), в противном случае нулевая (исходя из индекса в массиве) и последняя заданные точки являются началом и концом кривой(значение closed равно false). Можно было бы сделать проверку на замкнутость автоматической, но иногда бывают случаи, когда нужно, чтобы линия имела совпадающие начало и конец.

  var Xmid = Xargs.slice(0);
  var Ymid = Yargs.slice(0);

Возможно, Вам в дальнейшем потребуются массивы, которые вы задали как параметры функции. Если производить с аргументами Xargs и Yargs какие-нибудь преобразования, то изменятся и сами передаваемые массивы. Поэтому создаются их локальные копии (на локальность указывает слово var). Если Вы уверены, что передаваемые массивы нигде больше использоваться не будут, можно удалить эти две строчки, а первую строку записать как function Multicurve(Xmid, Ymid, closed){.

  if ((Xmid.length != Ymid.length) || (Xmid.length < 2)){
    trace('Wrong Arguments');
    return this
  };

Проверка на отсутствие ошибок. Если задано меньше двух точек, или количество координат по X не равно количеству координат по Y, то метод заканчивается с передачей ссылки на себя и выводит в режиме теста соответствующее сообщение. Если проверка наличия хотя-бы двух точек - простая формальность, то проверка различного числа координат может быть весьма полезной. При желании строчку trace('Wrong Arguments'); можно заменить на что-нибудь вроде
  trace('Wrong Arguments: '+(Xmid.length-Ymid.length)), чтобы знать, сколько и каких координат не достаёт.

  if (Xmid.length == 2){
    this.moveTo(Xmid[0], Ymid[0]);
    this.lineTo(Xmid[1], Ymid[1]);
    delete Xmid;
    delete Ymid;
    return this;
  };

Ещё одна проверка. Если задано всего две точки, то мы просто рисуем соединяющий их отрезок и заканчиваем выполнение метода. Данная проверка обязательна, т.к. иначе используемый далее метод curveTo() просто не получит достаточное количество параметров. Слово this используется для того, чтобы данный метод, применённый к любому клипу перенимал все его свойства и действовал в его пределах. Обращаю Ваше внимание на следующие строки:

  delete Xmid;
  delete Ymid;

В них удаляются из памяти локальные массивы. В ActionScript эти строчки не обязательны, т.к. "сборщик мусора" сам с ними разберётся.

Теперь вся подготовительная работа выполнена и можно начинать создание массивов данных для построения кривой:

  var Xpoint = new Array();
  var Ypoint = new Array();
  for (var i = 1; i < Xmid.length-2; i++){
    Xpoint[i] = 0.5*(Xmid[i+1]+Xmid[i]);
    Ypoint[i] = 0.5*(Ymid[i+1]+Ymid[i]);
  };

Cоздаём ещё два локальных массива, Xpoint и Ypoint, которые будут содержать координаты всех якорных точек. Далее в цикле происходит рассчёт координат этих якорных точек. Здесь и появляется та самая оговорка. В оригинале в CV NURBS координаты таких точек зависят от того, насколько далеко от концов кривой они находятся. Но подобный способ получения координат во Flash себя не оправдывает, и вот почему. В нашем методе мы создаём якорные точки на середине отрезка, соединяющего соседние контрольные точки. Максимальное отклонение "реального" местоположения якорных точек от середины отрезка меньше 0,58 % длины самого отрезка. Так что этим вполне можно пренебречь. Отмечу, что рассчёт координат происходит для точек с первой по предпоследнюю, так как координаты первой и последней рассчитываются в зависимости от того, замкнута кривая или нет.

  if (closed){
    Xpoint[0] = 0.5*(Xmid[1]+Xmid[0]);
    Ypoint[0] = 0.5*(Ymid[1]+Ymid[0]);
    Xpoint[i] = 0.5*(Xmid[i+1]+Xmid[i]);
    Ypoint[i] = 0.5*(Ymid[i+1]+Ymid[i]);
    Xpoint[i+1] = 0.5*(Xmid[i+1]+Xmid[0]);
    Ypoint[i+1] = 0.5*(Ymid[i+1]+Ymid[0]);
    Xmid[i+2] = Xmid[0];
    Ymid[i+2] = Ymid[0];
    Xpoint[i+2] = Xpoint[0];
    Ypoint[i+2] = Ypoint[0];
  }

Если кривая замкнута, то мы создаём нулевую якорную точку на середине отрезка между нулевой и первой контрольными точками, последнюю - между предпоследней и последней. И нам потребуются координаты ещё нескольких якорных точек: точки, находящейся на середине отрезка, соединяющего последнюю контрольную точку с первой, и копии координат нулевых якорной и контрольной точек. Это делается для того, чтобы объединить в один код рисование замкнутой и не замкнутой кривых. Используемая в индексах переменная i после окончания цикла имела значение Xmid.length-2, поэтому, следуя нашей организации массивов, Xpoint[i], Ypoint[i] - это координаты якорной точки, находящейся между предпоследней и последней контрольными (если здесь вообще уместно говорить, какая точка первая, а какая - последняя), Xpoint[i+1], Ypoint[i+1] - между последней и первой, Xpoint[i+2], Ypoint[i+2] - копии координат.

  } else {
    Xpoint[0] = Xmid[0];
    Ypoint[0] = Ymid[0];
    Xpoint[i] = Xmid[i+1];
    Ypoint[i] = Ymid[i+1];
    Xmid.pop();
    Ymid.pop();
  };

Если же кривая не замкнута, то мы просто делаем нулевую и последнюю контрольные точки нулевой и последней якорной. Снова используем переменную i: Xmid[i+1], Ymid[i+1] являются поледними элементами массивов, т.к. i+1 = Xmid.length-1. Далее мы удаляем последний элемент в массивах координат контрольных точек, но оставляем первый затем, чтобы каждой паре якорных точек [i-1], [i] соответствовала [i]-ая контрольная точка, как и в случае замкнутой кривой.

Нахождение координат точек завершено и начинается процесс рисования кривой:

  this.moveTo (Xpoint[0], Ypoint[0]);
  for (var i = 1; i < Xmid.length; i++){
    this.curveTo(Xmid[i], Ymid[i], Xpoint[i], Ypoint[i]);
  };

Сначала указываем начальную точку.
Затем начинаем рисовать кривые, где для каждой i-ой кривой начальная точка имеет координаты (Xpoint[i-1], Ypoint[i-1]), контрольная - (Xmid[i], Ymid[i]), а конечная - (Xpoint[i], Ypoint[i]). Искомая линия построена!!!

    delete Xmid;
    delete Ymid;
    delete Xpoint;
    delete Ypoint;
    return this;
  }

Удаление массивов и окончание метода. Без комментариев.

В последней строчке Object.prototype.multicurveTo = Multicurve; мы добавляем наш метод к набору имеющихся методов класса Object.

В завершении...

Метод описан, можете смело его использовать. Для простоты рекомендую поместить весь программный код в текстовый файл с расширением .as и в каждом новом клипе, где этот метод может пригодиться, в корне (_root) написать строку

#include path

где path - это абсолютный или относительный путь к файлу с кодом. Заметьте, что после #include точка с запятой НЕ ставится. Лично у меня это выглядит так:

#include "D:/Flash/aibdraw.as"

Если для написания программного кода вы используете встроеный во Flash редактор, то, возможно, Вам пригодиться следущее:
-- Заходите в корневой каталог Flash, далее \First Run\ActionsPanel
-- Открываете файл ActionsPanel.xml, находите
        <ifdef mode="FEATURE_DRAWING_API">
-- Перед </folder> прописываете подобную строчку:
         <string name="multicurveTo" tiptext="Draws a multipoint curve from the first to the last point" object="MovieClip" text=".multicurveTo(% [pointsX], [pointsY], Closed? %)" type="procedure" version="6" />
После этого в левом столбике редактора ActionScript и всплывающих подсказках появится multicurveTo().



Литература по FLASH