// UTF-8 /** * I. INTRODUCTION * * There are two methods, that are used in ActionScript drawing: lineTo() and curveTo() * and realize sengment and Bezier second-order curve drawing correspondingly. * There are tools in Flash IDE to draw curves with Bezier third-order curves, * but during the compilation process they are approximated with second-order curves. * As the result, all shapes in compiled SWF-file become segments or Bezier second-order curves. * That's why we encounter a group of tasks, that need mathematical engine * to work with segments and Bezier second-order curves. * * * II. BASE TASKS * * 1. Controlling: * - using control points; * - placing the particular point in particular coondinates; * - rotating around any point; * - offseting. * 2. Geometric properties: * - finding actual point on curve or line by time-iterator; * - parents (line for the segment and parabola for the Bezier curve) * - length of the given segment * - bounding rectangle * - square (for the Bezier curve) * - tangants * 3. Getting points on curve: * - by distance from the beginning; * - nearest to the given point; * - inretsections with other segments and curves. * * Most of other practical tasks can be solved based on these solutions. * * Actually, these tasks are realizaed in this class package. * Samples for other practical tasks are located in howtodo package. * * * III. CONCEPTIONS * * 1. Bezier and Line classes are realized in a similar way and most of * their methods have similar or the same syntax, defined by IParametric interface. * Of course, there are differencies: for example, Line doesn't have * area and control properties. Bezier * in it's turn doesn't have angle, that is presented in Line. * * 2. Geometric figures (lines), defined in Line and Bezier classes, * are controlled in parametric form, every point of the figure is defined by it's time-iterator. * In the beginning it can seem inconvenient that computation of the point on curve returns * a time-iterator that is Number instead of Point class instance. * However, this realization helps us to avoid redundant convertations in future calculations. * To convert point on figure to the Point, use the getPoint() method. * To find time-iterator for the two-dimensional point, use the getClosest() method. * * 3. Bezier and Line instances can be infinite or limited with start * and end points. Limited nature can be set using isSegment property (default value * is true). * If you use isSegment false, then class methods results will include points that lie * outside of start-end segment. In other case, methods results will contain only points, that belong to * the segment, defined by start and end properties. * * @lang eng * @translator Maxim Kachurovskiy http://www.riapriority.com * **/ /* * * I. ВС ТУПЛЕНИЕ * * Для программного рисования во Flash используется два метода: lineTo() и curveTo(), * реализующие соответственно отрисовку отрезка и кривой Безье второго порядка. * В редакторе Flash имеется возможность отрисовывать кривые с помощью кривых Безье * третьего порядка, однако, на этапе компиляции, они аппроксимируются кривыми * Безье второго порядка. * В итоге, все векторные фигуры в скомпилированном swf файле реализованы с помощью * отрезков или кривых Безье второго порядка. * В результате возникает целый спектр задач, для решения которых требуется математический * аппарат работы с отрезками и кривыми Безье второго порядка. * * * II. БАЗОВЫЕ ЗАДАЧИ * * 1. управление: * - с помощью контрольных точек; * - установка заданной точки в произвольно заданые координаты; * - поворот относительно произвольно заданной точки; * - смещение на заданное расстояние. * 2. геометрические свойства: * - получение точки на плоскости по известному time-итератору; * - родители (прямая для отрезка и парабола для кривой Безье) * - длина заданного сегмента * - габаритный прямоугольник * - площадь (для кривой Безье) * - касательные * 3. получение точек на кривой: * - по дистанции от начала; * - ближайшей до произвольно заданной; * - пересечения с другими кривыми и отрезками. * * Подавляющее большинство остальных практических задач могут быть решены * на основе решений этих базовых задач. * * Собственно, перечисленные базовые задачи и реализованы в этом пакете классов. * Примеры решения других практических задач вынесены в пакет howtodo. * * * III. КОНЦЕПЦИИ * * 1. Классы Bezier и Line реализованы схожим образом и подавляющее большинство * их методов имеют либо схожий, либо аналогичный синтаксис, определенный интерфейсом IParametric. * Разумеется, есть и отличия: к примеру, у Line не может быть свойства area, * и отсутствует управляющая точка control; у Bezier, в свою очередь нет свойства * angle, присутствующего в Line. * * 2. Геометрические фигуры(линии), реализованные в классах Line и Bezier, задаются в * параметрической форме, и каждая точка фигуры характеризуется time-итератором. * Возможно, что поначалу покажется неудобным, что при вычислении точки на кривой * возвращается не привычный всем объект класса Point, а time-итератор, * являющийся Number. Однако такая реализация позволяет избежать избыточных * конвертаций при последующих расчетах. * При необходимости перевести точку фигуры в объект Point, используйте метод getPoint(). * При необходимости найти time-итератор двумерной точки, используйте метод getClosest(). * * 3. Объекты Bezier и Line могут быть бесконечны, либо ограничены конечными точками start и end. * Ограниченность может быть установлена свойством isSegment (по умолчанию true). * Если задать isSegment=false, то возвращаемые методами значения будут содержать точки, в том числе, * лежащие за пределами сегмента start-end. В противном случае, возвращаемые методами значения * будут содержать только точки, принадлежащие сегменту лежащему между start и end. * * TODO: [Dembicki] Дописать **/ package flash.geom { import flash.math.Equations; /* * *

* Класс Bezier представляет кривую Безье второго порядка в параметрическом представлении, * задаваемую точками на плоскости start, control и end * и реализован в поддержку встроенного метода curveTo(). * В классе реализованы свойства и методы, предоставляющие доступ к основным геометрическим свойствам этой кривой. *

*
*

Краткие сведения о кривой Безье второго порядка.

* Любая точка Pt на кривой Безье второго порядка вычисляется по формуле:
*

Pt = S*(1-t)2 + 2*C*(1-t)*t + E*t2    (1)


* где:
* t (time) — time-итератор точки
* S (start) — начальная опорная (узловая) точка (t=0) (anchor point)
* С (control) — управляющая точка (direction point)
* E (end) — конечная опорная (узловая) точка (t=1) (anchor point)
*
* Построение производится итерационным вычислением множества точек кривой, c изменением значения итератора t в пределах от нуля до единицы.
* Точка кривой Безье характеризуется time-итератором. * Две точки кривой, имеющие одинаковый time-итератор совпадут. * В общем случае две точки кривой Безье второго порядка с различным time-итератором не совпадут.
* *
* * * * * * * * *
*
*

Интерактивная демонстрация
* Используйте клавиши "влево" "вправо" для управления итератором.
* Мышью перемещайте контрольные точки кривой.


*
*

Свойства кривой Безье второго порядка

* *

Кривая Безье и парабола

* Кривая Безье второго порядка является сегментом параболы. * Кривая, построенная по формуле 1, и итератором t изменяющимся в бесконечных пределах является параболой. * Если кривая Безье лежит на параболе, то такая парабола по отношению к ней является родительской. * * @langversion 3.0 * @playerversion Flash 9.0 * * @see Line * @see Intersection * **/ /** *

* Class Bezier represents a Bezier second-order curve in parametric view, * and is given by start, control and end points on the plane. * It exists to support the curveTo() method. Methods and properties of this class * give access to the geometric properties of the curve. *

*
*

Brief information about the Bezier second-order curve.

* Any point Pt on the Bezier second-order curve is computed using formula:
*

Pt = S*(1-t)2 *  + 2*C*(1-t)*t + E*t2  *    (1)


* where:
* t(time) — time-iterator of the point
* S(start) — initial control (anchor) point (t=0)
* С(control) — direction point
* E(end) — final control (anchor) point (t=1)
*
* The curve is built by iterative computing of the curve points * with time-iterator modification from 0 to 1
* Bezier curve point is characterized by it's time-iterator. * Two curve points with the same time-iterator coincide. * Generally, two curve points with different time-iterator do not coincide.
* *
* * * * * * * * *
*
*

Interactive demo
* You can use "Left" and "Right" keys to control the iterator.
* Curve control points are dragable.


*
*

Bezier second-order curve properties

* *

Bezier curve and parabola

* Bezier second-order curve is a parabola segment. * Curve built from formula 1 with iterator * t changing in infinite limits is a parabola. * If Bezier curve lies on parabola then this parabola is considered to be the parent for it.
* * This property also applies to the Bezier curves with other orders. * For example, segment can be considered as Bezier first-order curve * and it's parent will be the line that contains it. * Class Line interprets segment this way to simplify * it's usage together with Bezier class.
* Bezier third-order curve on the plane is a segment of projection of * cubic parabola in a three-dimensional space on the plane. * General case: Bezier N-order curve is a segment of projection * of the N-order curve built in N-dimensional space. *
* * @see Line * @see Intersection * * @langversion 3.0 * @playerversion Flash 9.0 * * @lang eng * @translator Maxim Kachurovskiy http://www.riapriority.com * **/ public class Bezier extends Object implements IParametric { protected static const PRECISION : Number = 1e-10; protected var startPoint : Point; protected var controlPoint : Point; protected var endPoint : Point; protected var __isSegment : Boolean = true; //************************************************** // CONSTRUCTOR //************************************************** /* * * * Создает новый объект Bezier. * Если параметры не переданы, то все опорные точки создаются в координатах 0,0 * * @param start:Point начальная точка кривой Безье * * @param control:Point контрольная точка кривой Безье * * @param end:Point конечная точка кривой Безье * * @param isSegment:Boolean режим обработки. * * @example В этом примере создается кривая Безье в случайных координатах. * * import flash.geom.Bezier; * import flash.geom.Point; * * function randomPoint():Point { * return new Point(Math.random()*stage.stageWidth, Math.random()*stage.stageHeight); * } * function randomBezier():Bezier { * return new Bezier(randomPoint(), randomPoint(), randomPoint()); * } * * const bezier:Bezier = randomBezier(); * trace("random bezier: "+bezier); * * * @langversion 3.0 * @playerversion Flash 9.0 * * @lang rus **/ /** * Create new Bezier object. * If parameters are not passed, all control points are created in coordinates 0,0 * * @param start:Point initial point of Bezier curve * * @param control:Point control point of Bezier curve * * @param end:Point end point of Bezier curve * * @param isSegment:Boolean operating mode * * @example In this example created Bezier curve with random coordinates. * * import flash.geom.Bezier; * import flash.geom.Point; * * function randomPoint():Point { * return new Point(Math.random()*stage.stageWidth, Math.random()*stage.stageHeight); * } * function randomBezier():Bezier { * return new Bezier(randomPoint(), randomPoint(), randomPoint()); * } * * const bezier:Bezier = randomBezier(); * trace("random bezier: "+bezier); * * * @langversion 3.0 * @playerversion Flash 9.0 * * @lang eng * @translator Ilya Segeda http://www.digitaldesign.com.ua **/ public function Bezier(start : Point = undefined, control : Point = undefined, end : Point = undefined, isSegment : Boolean = true) { initInstance(start, control, end, isSegment); } // protected function initInstance(start : Point = undefined, control : Point = undefined, end : Point = undefined, isSegment : Boolean = true) : void { startPoint = (start as Point) || new Point(); controlPoint = (control as Point) || new Point(); endPoint = (end as Point) || new Point(); __isSegment = Boolean(isSegment); } // Поскольку публичные переменные нельзя нельзя переопределять в дочерних классах, // start, control, end и isSegment реализованы как get-set методы, а не как публичные переменные. /* * * Начальная опорная (anchor) точка кривой Безье. Итератор time равен нулю. * * @langversion 3.0 * @playerversion Flash 9.0 * * @lang rus * **/ // As public variables cannot be redefined in affiliated classes, start, control, end and isSegment // are realized as get-set methods, instead of as public variables. /** * Initial anchor point of Bezier curve. Iterator time is equal to zero. * * @langversion 3.0 * @playerversion Flash 9.0 * * @lang eng * @translator Ilya Segeda http://www.digitaldesign.com.ua * **/ public function get start() : Point { return startPoint; } public function set start(value : Point) : void { startPoint = value; } /* * * Управляющая (direction) точка кривой Безье. * * @langversion 3.0 * @playerversion Flash 9.0 * * @lang rus **/ /** * Direction point of Bezier curve. * * @langversion 3.0 * @playerversion Flash 9.0 * * @lang eng * @translator Ilya Segeda http://www.digitaldesign.com.ua **/ public function get control() : Point { return controlPoint; } public function set control(value : Point) : void { controlPoint = value; } /* * * Конечная опорная (anchor) точка кривой Безье. Итератор time равен единице. * * @langversion 3.0 * @playerversion Flash 9.0 **/ /** * End anchor point of Bezier curve. Iterator time is equal to one * * @langversion 3.0 * @playerversion Flash 9.0 * * @lang eng * @translator Ilya Segeda http://www.digitaldesign.com.ua **/ public function get end() : Point { return endPoint; } public function set end(value : Point) : void { endPoint = value; } /* * * Определяет является ли кривая Безье бесконечной в обе стороны * или ограничена в пределах значения итераторов от 0 до 1.
*
* Безье строится с использованием итератора в пределах от 0 до 1, однако, * может быть построена в произвольных пределах.
* Кривая Безье, построеная от минус бесконечности до плюс * бесконечности является параболой.
*
* Текущее значение isSegment влияет на результаты методов:
* intersectionBezier
* intersectionLine
* getClosest
* Line.intersectionBezier
* * @see #intersectionBezier * @see #intersectionLine * @see #getClosest * * @default true * * @langversion 3.0 * @playerversion Flash 9.0 * * @lang rus **/ /** * Defines is the Bezier curve is infinite in both directions or is limited within * the bounds of value of iterators from 0 up to 1.
*
* The Bezier curve is constructed with using iterator within the bounds from 0 up to 1, * however, it can be constructed in any bounds.
* The Bezier curve constructed from a minus of infinity up to plus of infinity is a parabola.
*
* Current value isSegment influence on the results of methods:
* intersectionBezier
* intersectionLine
* getClosest
* Line.intersectionBezier
* * @see #intersectionBezier * @see #intersectionLine * @see #getClosest * * @default true * * @langversion 3.0 * @playerversion Flash 9.0 * * @lang eng * @translator Ilya Segeda http://www.digitaldesign.com.ua **/ public function get isSegment() : Boolean { return __isSegment; } public function set isSegment(value : Boolean) : void { __isSegment = Boolean(value); } /* * * Создает и возвращает копию текущего объекта Bezier. * * @return Bezier. * * @example В этом примере создается случайная кривая Безье и ее копия. * * import flash.geom.Bezier; * import flash.geom.Point; * * function randomPoint():Point { * return new Point(Math.random()*stage.stageWidth, Math.random()*stage.stageHeight); * } * function randomBezier():Bezier { * return new Bezier(randomPoint(), randomPoint(), randomPoint()); * } * * const bezier:Bezier = randomBezier(); * const clone:Bezier = bezier.clone(); * trace("bezier: "+bezier); * trace("clone: "+clone); * trace(bezier == clone); * * * * @langversion 3.0 * @playerversion Flash 9.0 * * @lang rus **/ /** * Creates and returns a copy of current object Bezier. * * @return Bezier. * * @example In this example creates random Bezier curve and its copy. * * import flash.geom.Bezier; * import flash.geom.Point; * * function randomPoint():Point { * return new Point(Math.random()*stage.stageWidth, Math.random()*stage.stageHeight); * } * function randomBezier():Bezier { * return new Bezier(randomPoint(), randomPoint(), randomPoint()); * } * * const bezier:Bezier = randomBezier(); * const clone:Bezier = bezier.clone(); * trace("bezier: "+bezier); * trace("clone: "+clone); * trace(bezier == clone); * * * * @langversion 3.0 * @playerversion Flash 9.0 **/ public function clone() : Bezier { return new Bezier(startPoint.clone(), controlPoint.clone(), endPoint.clone(), __isSegment); } //************************************************** // GEOM PROPERTIES //************************************************** /* * * Вычисляет длину киривой Безье * * @return Number длина кривой Безье. * * @example В этом примере создается случайная кривая Безье и выводится ее длина. * * * import flash.geom.Bezier; * import flash.geom.Point; * * function randomPoint():Point { * return new Point(Math.random()*stage.stageWidth, Math.random()*stage.stageHeight); * } * function randomBezier():Bezier { * return new Bezier(randomPoint(), randomPoint(), randomPoint()); * } * * const bezier:Bezier = randomBezier(); * * trace("bezier length: "+bezier.length); * * * * * @see #getSegmentLength * * @langversion 3.0 * @playerversion Flash 9.0 * * @lang rus * **/ /** * Calculates length of the Bezier curve * * @return Number length of the Bezier curve. * * @example In this example creates random Bezier curve and traces its length. * * * import flash.geom.Bezier; * import flash.geom.Point; * * function randomPoint():Point { * return new Point(Math.random()*stage.stageWidth, Math.random()*stage.stageHeight); * } * function randomBezier():Bezier { * return new Bezier(randomPoint(), randomPoint(), randomPoint()); * } * * const bezier:Bezier = randomBezier(); * * trace("bezier length: "+bezier.length); * * * * * @see #getSegmentLength * * @langversion 3.0 * @playerversion Flash 9.0 * * @lang eng * @translator Ilya Segeda http://www.digitaldesign.com.ua * **/ public function get length() : Number { return getSegmentLength(1); } /* * * Вычисляет длину сегмента кривой Безье от стартовой точки до * точки на кривой, заданной параметром time. * * @param time:Number параметр time конечной точки сегмента. * @return Number length of arc. * * @example В этом примере создается случайная кривая Безье, * вычисляется time-итератор точки середины кривой, а затем * выводятся значения половины длины кривой и длина сегмента * кривой до средней точки - они должны быть равны. * * * import flash.geom.Bezier; * import flash.geom.Point; * * function randomPoint():Point { * return new Point(Math.random()*stage.stageWidth, Math.random()*stage.stageHeight); * } * function randomBezier():Bezier { * return new Bezier(randomPoint(), randomPoint(), randomPoint()); * } * * const bezier:Bezier = randomBezier(); * * const middleDistance:Number = bezier.length/2; * const middleTime:Number = bezier.getTimeByDistance(middleDistance); * const segmentLength:Number = bezier.getSegmentLength(middleTime); * * trace(middleDistance); * trace(segmentLength); * * * * @langversion 3.0 * @playerversion Flash 9.0 * * @see #length * * @lang rus **/ /** * Calculates length of a segment of Bezier curve from a starting point * up to a point on a curve which passed in parameter time. * * @param time:Number parameter time of the end point of a segment. * @return Number length of arc. * * @example In this example creates random Bezier curve, calculates time-iterator * of the middle of a curve, and then traces values of half of length of a curve * and length of a segment of a curve up to an middle point - they should be equal. * * * import flash.geom.Bezier; * import flash.geom.Point; * * function randomPoint():Point { * return new Point(Math.random()*stage.stageWidth, Math.random()*stage.stageHeight); * } * function randomBezier():Bezier { * return new Bezier(randomPoint(), randomPoint(), randomPoint()); * } * * const bezier:Bezier = randomBezier(); * * const middleDistance:Number = bezier.length/2; * const middleTime:Number = bezier.getTimeByDistance(middleDistance); * const segmentLength:Number = bezier.getSegmentLength(middleTime); * * trace(middleDistance); * trace(segmentLength); * * * * * @see #length * * @langversion 3.0 * @playerversion Flash 9.0 * * @lang eng * @translator Ilya Segeda http://www.digitaldesign.com.ua * **/ public function getSegmentLength(time : Number) : Number { const csX : Number = controlPoint.x - startPoint.x; const csY : Number = controlPoint.y - startPoint.y; const nvX : Number = endPoint.x - controlPoint.x - csX; const nvY : Number = endPoint.y - controlPoint.y - csY; // vectors: c0 = 4*(cs,cs), с1 = 8*(cs, ec-cs), c2 = 4*(ec-cs,ec-cs) const c0 : Number = 4 * (csX * csX + csY * csY); const c1 : Number = 8 * (csX * nvX + csY * nvY); const c2 : Number = 4 * (nvX * nvX + nvY * nvY); var ft : Number; var f0 : Number; if (c2 == 0) { if (c1 == 0) { ft = Math.sqrt(c0) * time; return ft; } else { ft = (2 / 3) * (c1 * time + c0) * Math.sqrt(c1 * time + c0) / c1; f0 = (2 / 3) * c0 * Math.sqrt(c0) / c1; return (ft - f0); } } else { const sqrt_0 : Number = Math.sqrt(c2 * time * time + c1 * time + c0); const sqrt_c0 : Number = Math.sqrt(c0); const sqrt_c2 : Number = Math.sqrt(c2); const exp1 : Number = (0.5 * c1 + c2 * time) / sqrt_c2 + sqrt_0; if (exp1 < PRECISION) { ft = 0.25 * (2 * c2 * time + c1) * sqrt_0 / c2; } else { ft = 0.25 * (2 * c2 * time + c1) * sqrt_0 / c2 + 0.5 * Math.log((0.5 * c1 + c2 * time) / sqrt_c2 + sqrt_0) / sqrt_c2 * (c0 - 0.25 * c1 * c1 / c2); } const exp2 : Number = (0.5 * c1) / sqrt_c2 + sqrt_c0; if (exp2 < PRECISION) { f0 = 0.25 * (c1) * sqrt_c0 / c2; } else { f0 = 0.25 * (c1) * sqrt_c0 / c2 + 0.5 * Math.log((0.5 * c1) / sqrt_c2 + sqrt_c0) / sqrt_c2 * (c0 - 0.25 * c1 * c1 / c2); } return ft - f0; } } /* * * Вычисляет и возвращает площадь фигуры, ограниченой кривой Безье * и отрезком SE. * Площадь этой фигуры составляет 2/3 площади треугольника ∆SCE, * образуемого контрольными точками start, control, end.
* Соответственно, оставшаяся часть треугольника составляет 1/3 его площади. * * @return Number * * @example * * import flash.geom.Bezier; * import flash.geom.Point; * * function randomPoint():Point { * return new Point(Math.random()*stage.stageWidth, Math.random()*stage.stageHeight); * } * function randomBezier():Bezier { * return new Bezier(randomPoint(), randomPoint(), randomPoint()); * } * * const randomBezier:Bezier = randomBezier(); * * trace("bezier area: "+randomBezier.area); * * * * @langversion 3.0 * @playerversion Flash 9.0 * * @see #triangleArea * * @lang rus **/ /** * Calculates and returns the area of the figure limited by Bezier curve and * line SE. * The area of this figure makes 2/3 areas of a triangle ∆SCE, which is formed of * control points start, control, end.
* Accordingly, the rest of a triangle makes 1/3 its areas. * * @return Number * * @example * * import flash.geom.Bezier; * import flash.geom.Point; * * function randomPoint():Point { * return new Point(Math.random()*stage.stageWidth, Math.random()*stage.stageHeight); * } * function randomBezier():Bezier { * return new Bezier(randomPoint(), randomPoint(), randomPoint()); * } * * const randomBezier:Bezier = randomBezier(); * * trace("bezier area: "+randomBezier.area); * * * * @see #triangleArea * * @langversion 3.0 * @playerversion Flash 9.0 * * @lang eng * @translator Ilya Segeda http://www.digitaldesign.com.ua * **/ public function get area() : Number { return triangleArea * (2 / 3); } /* * * Вычисляет и возвращает площадь треугольника ∆SCE, * образуемого контрольными точками start, control, end. * * @return Number * * @see #area * * @lang rus **/ /** * Calculates and returns the area of a triangle ∆SCE, which is formed of * control points start, control, end. * * @return Number * * @see #area * * @lang eng * @translator Ilya Segeda http://www.digitaldesign.com.ua **/ public function get triangleArea() : Number { // heron's formula const distanceStartControl : Number = Point.distance(startPoint, controlPoint); const distanceEndControl : Number = Point.distance(endPoint, controlPoint); const distanceStartEnd : Number = Point.distance(startPoint, endPoint); const halfPerimeter : Number = (distanceStartControl + distanceEndControl + distanceStartEnd) / 2; const area : Number = Math.sqrt(halfPerimeter * (halfPerimeter - distanceStartControl) * (halfPerimeter - distanceEndControl) * (halfPerimeter - distanceStartEnd)); return area; } /** * Gravity center of the figure limited by Bezier curve and line SE. * * @return Point **/ public function get internalCentroid() : Point { const x : Number = (startPoint.x + endPoint.x) * .4 + controlPoint.x * .2; const y : Number = (startPoint.y + endPoint.y) * .4 + controlPoint.y * .2; return new Point(x, y); // return Point.interpolate(controlPoint, Point.interpolate(startPoint, endPoint, 0.5), 0.2); } /** * Gravity center of the figure limited by Bezier curve and lines SC and CE. * * @return Point **/ public function get externalCentroid() : Point { const x : Number = (startPoint.x + endPoint.x) * .2 + controlPoint.x * .6 ; const y : Number = (startPoint.y + endPoint.y) * .2 + controlPoint.y * .6; return new Point(x, y); // return Point.interpolate(controlPoint, Point.interpolate(startPoint, endPoint, 0.5), 0.6); } /** * Gravity center of triangle SCE. * * @return Point **/ public function get triangleCentroid() : Point { const x : Number = (startPoint.x + endPoint.x + controlPoint.x) / 3 ; const y : Number = (startPoint.y + endPoint.y + controlPoint.y) / 3; return new Point(x, y); // return Point.interpolate(controlPoint, Point.interpolate(startPoint, endPoint, 0.5), 1/3); } /* * * Вычисляет и возвращает габаритный прямоугольник сегмента кривой Безье.
* Установка свойству isSegment=false не изменяет результат вычислений. * * @return Rectangle габаритный прямоугольник. * * @langversion 3.0 * @playerversion Flash 9.0 * * @lang rus */ /** * Calculates and returns a bounds rectangular of a segment of Bezier curve.
* Property isSegment=false does not change result of calculations. * * @return Rectangle bounds rectangular. */ public function get bounds() : Rectangle { var xMin : Number; var xMax : Number; var yMin : Number; var yMax : Number; const x : Number = startPoint.x - 2 * controlPoint.x + endPoint.x; const extremumTimeX : Number = ((startPoint.x - controlPoint.x) / x) || 0; const extemumPointX : Point = getPoint(extremumTimeX); if (isNaN(extemumPointX.x) || extremumTimeX <= 0 || extremumTimeX >= 1) { xMin = Math.min(startPoint.x, endPoint.x); xMax = Math.max(startPoint.x, endPoint.x); } else { xMin = Math.min(extemumPointX.x, Math.min(startPoint.x, endPoint.x)); xMax = Math.max(extemumPointX.x, Math.max(startPoint.x, endPoint.x)); } const y : Number = startPoint.y - 2 * controlPoint.y + endPoint.y; const extremumTimeY : Number = ((startPoint.y - controlPoint.y) / y) || 0; const extemumPointY : Point = getPoint(extremumTimeY); if (isNaN(extemumPointY.y) || extremumTimeY <= 0 || extremumTimeY >= 1) { yMin = Math.min(startPoint.y, endPoint.y); yMax = Math.max(startPoint.y, endPoint.y); } else { yMin = Math.min(extemumPointY.y, Math.min(startPoint.y, endPoint.y)); yMax = Math.max(extemumPointY.y, Math.max(startPoint.y, endPoint.y)); } const width : Number = xMax - xMin; const height : Number = yMax - yMin; return new Rectangle(xMin, yMin, width, height); } //************************************************** // PARENT PARABOLA //************************************************** /* * * Вычисляет и возвращает time-итератор вершины параболы. * * @return Number; * * @example * import flash.geom.Bezier; * import flash.geom.Point; * * function randomPoint():Point { * return new Point(Math.random()*stage.stageWidth, Math.random()*stage.stageHeight); * } * function randomBezier():Bezier { * return new Bezier(randomPoint(), randomPoint(), randomPoint()); * } * * const randomBezier:Bezier = randomBezier(); * * trace("parabola vertex time: "+randomBezier.parabolaVertex); * * * * @see #parabolaFocus * */ /** * Calculates and returns time-iterator of top of the parabola. * * @return Number; * * @example * import flash.geom.Bezier; * import flash.geom.Point; * * function randomPoint():Point { * return new Point(Math.random()*stage.stageWidth, Math.random()*stage.stageHeight); * } * function randomBezier():Bezier { * return new Bezier(randomPoint(), randomPoint(), randomPoint()); * } * * const randomBezier:Bezier = randomBezier(); * * trace("parabola vertex time: "+randomBezier.parabolaVertex); * * * * @see #parabolaFocus * * @langversion 3.0 * @playerversion Flash 9.0 * * @lang eng * @translator Ilya Segeda http://www.digitaldesign.com.ua * **/ public function get parabolaVertex() : Number { const x : Number = startPoint.x - 2 * controlPoint.x + endPoint.x; const y : Number = startPoint.y - 2 * controlPoint.y + endPoint.y; const summ : Number = x * x + y * y; const dx : Number = startPoint.x - controlPoint.x; const dy : Number = startPoint.y - controlPoint.y; const vertexTime : Number = (x * dx + y * dy) / summ; if (isNaN(vertexTime)) { return 1 / 2; } return vertexTime; } /* * * @return Point - фокус родительской параболы; * * @langversion 3.0 * @playerversion Flash 9.0 * * @see #parabolaVertex */ /** * @return Point - focus of a parental parabola; * * @see #parabolaVertex * * @langversion 3.0 * @playerversion Flash 9.0 * * @lang eng * @translator Ilya Segeda http://www.digitaldesign.com.ua * **/ public function get parabolaFocusPoint() : Point { const startX : Number = startPoint.x; const startY : Number = startPoint.y; const controlX : Number = controlPoint.x; const controlY : Number = controlPoint.y; const endX : Number = endPoint.x; const endY : Number = endPoint.y; const x : Number = startX - 2 * controlX + endX; const y : Number = startY - 2 * controlY + endY; const summ : Number = x * x + y * y; if (summ == 0) { return controlPoint.clone(); } const dx : Number = controlX - startX; const dy : Number = controlY - startY; const minT : Number = -(x * dx + y * dy) / summ; const minF : Number = 1 - minT; const minT2 : Number = minT * minT; const minF2 : Number = minF * minF; const psx : Number = 2 * dx + 2 * minT * x; const psy : Number = 2 * dy + 2 * minT * y; const vertexX : Number = startX * minF2 + 2 * minT * minF * controlX + minT2 * endX; const vertexY : Number = startY * minF2 + 2 * minT * minF * controlY + minT2 * endY; var fx : Number = vertexX - psy / (4 * Math.SQRT2); var fy : Number = vertexY + psx / (4 * Math.SQRT2); const side : Number = (psy * (startX - vertexX) - psx * (startY - vertexY)) * (psy * (fx - vertexX) - psx * (fy - vertexY)); if (side < 0) { fx = vertexX + psy / (4 * Math.SQRT2); fy = vertexY - psx / (4 * Math.SQRT2); } return new Point(fx, fy); } //************************************************** // CURVE POINTS //************************************************** /* * * Реализация формулы 1
* Вычисляет и возвращает объект Point представляющий точку на кривой Безье, заданную параметром time. * * @param time:Number итератор точки кривой * * @return Point точка на кривой Безье;
* * Если передан параметр time равный 1 или 0, то будут возвращены объекты Point * эквивалентные start и end, но не сами объекты start и end * * * @example * * import flash.geom.Bezier; * import flash.geom.Point; * * function randomPoint():Point { * return new Point(Math.random()*stage.stageWidth, Math.random()*stage.stageHeight); * } * function randomBezier():Bezier { * return new Bezier(randomPoint(), randomPoint(), randomPoint()); * } * * const bezier:Bezier = randomBezier(); * * const time:Number = Math.random(); * const point:Point = bezier.getPoint(time); * * trace(point); * * * @langversion 3.0 * @playerversion Flash 9.0 * */ /** * Realization of formula 1
* Calculates and returns object Point which representing a point on a Bezier curve, * specified by the parameter time. * * @param time:Number iterator of the point on curve * * @return Point point on the Bezier curve;
* * If passed the parameter time equal to 1 or 0, object Point equivalent to start * or end will be returned, but not objects exact start or end. * * * @example * * import flash.geom.Bezier; * import flash.geom.Point; * * function randomPoint():Point { * return new Point(Math.random()*stage.stageWidth, Math.random()*stage.stageHeight); * } * function randomBezier():Bezier { * return new Bezier(randomPoint(), randomPoint(), randomPoint()); * } * * const bezier:Bezier = randomBezier(); * * const time:Number = Math.random(); * const point:Point = bezier.getPoint(time); * * trace(point); * * * @langversion 3.0 * @playerversion Flash 9.0 * * @lang eng * @translator Ilya Segeda http://www.digitaldesign.com.ua * **/ public function getPoint(time : Number, point : Point = null) : Point { if (isNaN(time)) { return undefined; } point = (point as Point) || new Point(); const f : Number = 1 - time; point.x = startPoint.x * f * f + controlPoint.x * 2 * time * f + endPoint.x * time * time; point.y = startPoint.y * f * f + controlPoint.y * 2 * time * f + endPoint.y * time * time; return point; } /** * Вычисляет time-итератор точки, находящейся на заданной дистанции * по кривой от точки start
* Для вычисления равноуделенных последовательностей точек, * например для рисования пунктиром, используйте метод getTimesSequence * * @param distance:Number дистанция по кривой до искомой точки. * * @return Number time iterator of bezier point; * * @example * * import flash.geom.Bezier; * import flash.geom.Point; * * function randomPoint():Point { * return new Point(Math.random()*stage.stageWidth, Math.random()*stage.stageHeight); * } * function randomBezier():Bezier { * return new Bezier(randomPoint(), randomPoint(), randomPoint()); * } * * const bezier:Bezier = randomBezier(); * * trace(bezier.getTimeByDistance(-10); // negative value * trace(bezier.getTimeByDistance(bezier.length/2); // value between 0 and 1 * * * @see #getPoint * * @langversion 3.0 * @playerversion Flash 9.0 * */ public function getTimeByDistance(distance : Number) : Number { if (isNaN(distance)) { return 0; } var arcLength : Number; var diffArcLength : Number; const curveLength : Number = length; var time : Number = distance / curveLength; if (__isSegment) { if (distance <= 0) { return 0; } if (distance >= curveLength) { return 1; } } const csX : Number = controlPoint.x - startPoint.x; const csY : Number = controlPoint.y - startPoint.y; const ecX : Number = endPoint.x - controlPoint.x; const ecY : Number = endPoint.y - controlPoint.y; const nvX : Number = ecX - csX; const nvY : Number = ecY - csY; // vectors: c0 = 4*(cs,cs), с1 = 8*(cs, ec-cs), c2 = 4*(ec-cs,ec-cs) const c0 : Number = 4 * (csX * csX + csY * csY); const c1 : Number = 8 * (csX * nvX + csY * nvY); const c2 : Number = 4 * (nvX * nvX + nvY * nvY); const c025 : Number = c0 - 0.25 * c1 * c1 / c2; const f0Base : Number = 0.25 * c1 * Math.sqrt(c0) / c2; const exp2 : Number = 0.5 * c1 / Math.sqrt(c2) + Math.sqrt(c0); const c00sqrt : Number = Math.sqrt(c0); const c20sqrt : Number = Math.sqrt(c2); var c22sqrt : Number; var exp1 : Number; var ft : Number; var ftBase : Number; var f0 : Number; const maxIterations : Number = 100; if (c2 == 0) { if (c1 == 0) { do { arcLength = c00sqrt * time; diffArcLength = Math.sqrt(Math.abs((c2 * time * time + c1 * time + c0))) || PRECISION; time = time - (arcLength - distance) / diffArcLength; } while (Math.abs(arcLength - distance) > PRECISION && maxIterations--); } else { do { arcLength = (2 / 3) * (c1 * time + c0) * Math.sqrt(c1 * time + c0) / c1 - (2 / 3) * c0 * c00sqrt / c1; diffArcLength = Math.sqrt(Math.abs((c2 * time * time + c1 * time + c0))) || PRECISION; time = time - (arcLength - distance) / diffArcLength; } while (Math.abs(arcLength - distance) > PRECISION && maxIterations--); } } else { do { c22sqrt = Math.sqrt(Math.abs(c2 * time * time + c1 * time + c0)); exp1 = (0.5 * c1 + c2 * time) / c20sqrt + c22sqrt; ftBase = 0.25 * (2 * c2 * time + c1) * c22sqrt / c2; if (exp1 < PRECISION) { ft = ftBase; } else { ft = ftBase + 0.5 * Math.log((0.5 * c1 + c2 * time) / c20sqrt + c22sqrt) / c20sqrt * c025; } if (exp2 < PRECISION) { f0 = f0Base; } else { f0 = f0Base + 0.5 * Math.log((0.5 * c1) / c20sqrt + c00sqrt) / c20sqrt * c025; } arcLength = ft - f0; diffArcLength = c22sqrt || PRECISION; time = time - (arcLength - distance) / diffArcLength; } while (Math.abs(arcLength - distance) > PRECISION && maxIterations--); } return time; } /** * Вычисляет и возвращает массив time-итераторов точек, * находящихся друг от друга на дистанции по кривой, * заданной параметром step.
* Если задан параметр startShift, то расчет производится * не от точки start, а от точки на кривой, находящейся на * заданнй этим параметром дистанции.
* Значение startShift конвертируется в остаток от деления на step.
* * * @param step:Number шаг, дистанция по кривой между точками. * @param startShift:Number дистанция по кривой, задающая смещение первой * точки последовательности относительно точки start * * @return Array sequence of points on bezier curve; * * @example * * import flash.geom.Bezier; * import flash.geom.Point; * * function randomPoint():Point { * return new Point(Math.random()*stage.stageWidth, Math.random()*stage.stageHeight); * } * function randomBezier():Bezier { * return new Bezier(randomPoint(), randomPoint(), randomPoint()); * } * * const bezier:Bezier = randomBezier(); * * // TODO: [Dembicki] example * * * * @langversion 3.0 * @playerversion Flash 9.0 * */ public function getTimesSequence(step : Number, startShift : Number = 0) : Array { step = Math.abs(step); var distance : Number = startShift; const times : Array = new Array(); const curveLength : Number = length; if (distance > curveLength) { return times; } if (distance < 0) { distance = distance % step + step; } else { distance = distance % step; } const csX : Number = controlPoint.x - startPoint.x; const csY : Number = controlPoint.y - startPoint.y; const ecX : Number = endPoint.x - controlPoint.x; const ecY : Number = endPoint.y - controlPoint.y; const nvX : Number = ecX - csX; const nvY : Number = ecY - csY; // vectors: c0 = 4*(cs,cs), с1 = 8*(cs, ec-cs), c2 = 4*(ec-cs,ec-cs) const c0 : Number = 4 * (csX * csX + csY * csY); const c1 : Number = 8 * (csX * nvX + csY * nvY); const c2 : Number = 4 * (nvX * nvX + nvY * nvY); var arcLength : Number; var diffArcLength : Number; var time : Number = distance / curveLength; const c025 : Number = c0 - 0.25 * c1 * c1 / c2; const f0Base : Number = 0.25 * c1 * Math.sqrt(c0) / c2; const exp2 : Number = 0.5 * c1 / Math.sqrt(c2) + Math.sqrt(c0); const c00sqrt : Number = Math.sqrt(c0); const c20sqrt : Number = Math.sqrt(c2); // const c21sqrt:Number = c20sqrt*(c025); var c22sqrt : Number; var exp1 : Number; var ft : Number; var ftBase : Number; var f0 : Number; while (distance <= curveLength) { var limiter : Number = 20; if (c2 == 0) { if (c1 == 0) { do { arcLength = c00sqrt * time; diffArcLength = Math.sqrt(Math.abs(c2 * time * time + c1 * time + c0)) || PRECISION; time = time - (arcLength - distance) / diffArcLength; } while (Math.abs(arcLength - distance) > PRECISION && limiter--); } else { do { arcLength = (2 / 3) * ((c1 * time + c0) * Math.sqrt(Math.abs(c1 * time + c0)) - c0 * c00sqrt) / c1; diffArcLength = Math.sqrt(Math.abs(c2 * time * time + c1 * time + c0)) || PRECISION; time = time - (arcLength - distance) / diffArcLength; } while (Math.abs(arcLength - distance) > PRECISION && limiter--); } } else { do { c22sqrt = Math.sqrt(Math.abs(c2 * time * time + c1 * time + c0)); exp1 = (0.5 * c1 + c2 * time) / c20sqrt + c22sqrt; ftBase = 0.25 * (2 * c2 * time + c1) * c22sqrt / c2; if (exp1 < PRECISION) { ft = ftBase; } else { ft = ftBase + 0.5 * Math.log((0.5 * c1 + c2 * time) / c20sqrt + c22sqrt) / c20sqrt * c025; } if (exp2 < PRECISION) { f0 = f0Base; } else { f0 = f0Base + 0.5 * Math.log((0.5 * c1) / c20sqrt + c00sqrt) / c20sqrt * c025; } arcLength = ft - f0; diffArcLength = c22sqrt || PRECISION; time = time - (arcLength - distance) / diffArcLength; } while (Math.abs(arcLength - distance) > PRECISION && limiter--); } times[times.length] = time; distance += step; } return times; } //************************************************** // CHANGE BEZIER //************************************************** /** * Изменяет кривую таким образом, что заданная параметром time точка кривой Pt, * будет находиться в заданных параметрами x и y координатах.
* Если один из параметров x или y не задан, * то точка Pt не изменит значение соответствующей координаты. * * @param time:Number time-итератор точки кривой. * @param x:Number новое значение позиции точки по оси X. * @param y:Number новое значение позиции точки по оси Y. * * @example * * * import flash.geom.Bezier; * import flash.geom.Point; * * function randomPoint():Point { * return new Point(Math.random()*stage.stageWidth, Math.random()*stage.stageHeight); * } * function randomBezier():Bezier { * return new Bezier(randomPoint(), randomPoint(), randomPoint()); * } * * const bezier:Bezier = randomBezier(); * trace(bezier); * * bezier.setPoint(0, 0, 0); * bezier.setPoint(0.5, 100, 100); * bezier.setPoint(1, 200, 0); * * trace(bezier); // (start:(x=0, y=0), control:(x=100, y=200), end:(x=200, y=0)) * * * @langversion 3.0 * @playerversion Flash 9.0 * */ public function setPoint(time : Number, x : Number = undefined, y : Number = undefined) : void { if ((isNaN(x) && isNaN(y))) { return; } const f : Number = 1 - time; const tSquared : Number = time * time; const fSquared : Number = f * f; const tf : Number = 2 * time * f; if (isNaN(x)) { x = startPoint.x * fSquared + controlPoint.x * 2 * tf + endPoint.x * tSquared; } if (isNaN(y)) { y = startPoint.y * fSquared + controlPoint.y * 2 * tf + endPoint.y * tSquared; } switch (time) { case 0: startPoint.x = x; startPoint.y = y; break; case 1: endPoint.x = x; endPoint.y = y; break; default: controlPoint.x = (x - endPoint.x * tSquared - startPoint.x * fSquared) / tf; controlPoint.y = (y - endPoint.y * tSquared - startPoint.y * fSquared) / tf; } } /* * * Поворачивает кривую относительно точки fulcrum на заданный угол. * Если точка fulcrum не задана, используется (0,0) * * @param value:Number угол вращения * * @param fulcrum:Point центр вращения. * * * @langversion 3.0 * @playerversion Flash 9.0 * */ /** * Rotate a curve concerning to a point fulcrum on the value angle * If point fulcrum is not set, used (0,0) * * @param value:Number rotation angle * * @param fulcrum:Point center of rotation. * * @langversion 3.0 * @playerversion Flash 9.0 * * @lang eng * @translator Ilya Segeda http://www.digitaldesign.com.ua * **/ public function angleOffset(value : Number, fulcrum : Point = null) : void { fulcrum = fulcrum || new Point(); const startLine : Line = new Line(fulcrum, startPoint); startLine.angle += value; const controlLine : Line = new Line(fulcrum, controlPoint); controlLine.angle += value; const endLine : Line = new Line(fulcrum, endPoint); endLine.angle += value; } /* * * Смещает кривую на заданное расстояние по осям X и Y. * * @param dx:Number величина смещения по оси X * @param dy:Number величина смещения по оси Y * * @langversion 3.0 * @playerversion Flash 9.0 * * @lang rus */ /** * Moves a curve on the prescribed distance on axes X and Y. * * @param dx:Number offset by X * @param dy:Number offset by Y * * @langversion 3.0 * @playerversion Flash 9.0 * * @lang eng * @translator Ilya Segeda http://www.digitaldesign.com.ua * */ public function offset(dX : Number = 0, dY : Number = 0) : void { startPoint.offset(dX, dY); controlPoint.offset(dX, dY); endPoint.offset(dX, dY); } //************************************************** // BEZIER AND EXTERNAL POINTS //************************************************** /** *

Вычисляет и возвращает time-итератор точки на кривой, * ближайшей к точке fromPoint.
* В зависимости от значения свойства isSegment * возвращает либо значение в пределах от 0 до 1, либо от минус * бесконечности до плюс бесконечности.

* * @param fromPoint:Point произвольная точка на плоскости. * * @return Number time-итератор точки на кривой. * * @example * * import flash.geom.Bezier; * import flash.geom.Point; * * function randomPoint():Point { * return new Point(Math.random()*stage.stageWidth, Math.random()*stage.stageHeight); * } * function randomBezier():Bezier { * return new Bezier(randomPoint(), randomPoint(), randomPoint()); * } * * const bezier:Bezier = randomBezier(); * * TODO: [Dembicki] example * * * * @see #isSegment * * @langversion 3.0 * @playerversion Flash 9.0 * **/ public function getClosest(fromPoint : Point) : Number { if (!(fromPoint as Point)) { return NaN; } const sx : Number = startPoint.x; const sy : Number = startPoint.y; const cx : Number = controlPoint.x; const cy : Number = controlPoint.y; const ex : Number = endPoint.x; const ey : Number = endPoint.y; const lpx : Number = sx - fromPoint.x; const lpy : Number = sy - fromPoint.y; const kpx : Number = sx - 2 * cx + ex; const kpy : Number = sy - 2 * cy + ey; const npx : Number = -2 * sx + 2 * cx; const npy : Number = -2 * sy + 2 * cy; const delimiter : Number = 2 * (kpx * kpx + kpy * kpy); var A : Number; var B : Number; var C : Number; var extremumTimes : Array; if(delimiter) { A = 3 * (npx * kpx + npy * kpy) / delimiter; B = ((npx * npx + npy * npy) + 2 * (lpx * kpx + lpy * kpy)) / delimiter; C = (npx * lpx + npy * lpy) / delimiter; extremumTimes = Equations.solveCubicEquation(1, A, B, C); } else { B = (npx * npx + npy * npy) + 2 * (lpx * kpx + lpy * kpy); C = npx * lpx + npy * lpy; extremumTimes = Equations.solveLinearEquation(B, C); } if (__isSegment) { extremumTimes.push(0); extremumTimes.push(1); } var extremumTime : Number; var extremumPoint : Point; var extremumDistance : Number; var closestPointTime : Number; var closestDistance : Number; var isInside : Boolean; const len : uint = extremumTimes.length; for (var i : uint = 0;i < len; i++) { extremumTime = extremumTimes[i]; extremumPoint = getPoint(extremumTime); extremumDistance = Point.distance(fromPoint, extremumPoint); isInside = (extremumTime >= 0) && (extremumTime <= 1); if (isNaN(closestPointTime)) { if (!__isSegment || isInside) { closestPointTime = extremumTime; closestDistance = extremumDistance; } continue; } if (extremumDistance < closestDistance) { if (!__isSegment || isInside) { closestPointTime = extremumTime; closestDistance = extremumDistance; } } } return closestPointTime; } //************************************************** // WORKING WITH SEGMENTS //************************************************** /** * Вычисляет и возвращает сегмент кривой Безье. * * @param fromTime:Number time-итератор начальной точки сегмента * @param toTime:Number time-итератор конечной точки сегмента кривой * * @return Bezier; * * @example * В данном примере на основе случайной кривой Безье создаются еще две. * Первая из них - segment1 * * import flash.geom.Bezier; * import flash.geom.Point; * * function randomPoint():Point { * return new Point(Math.random()*stage.stageWidth, Math.random()*stage.stageHeight); * } * function randomBezier():Bezier { * return new Bezier(randomPoint(), randomPoint(), randomPoint()); * } * * const bezier:Bezier = randomBezier(); * const segment1:Bezier = bezier.getSegment(1/3, 2/3); * const segment2:Bezier = bezier.getSegment(-1, 2); * * * * @langversion 3.0 * @playerversion Flash 9.0 * */ public function getSegment(fromTime : Number = 0, toTime : Number = 1) : Bezier { const segmentStart : Point = getPoint(fromTime); const segmentEnd : Point = getPoint(toTime); const segmentVertex : Point = getPoint((fromTime + toTime) / 2); const baseMiddle : Point = Point.interpolate(segmentStart, segmentEnd, 1 / 2); const segmentControl : Point = Point.interpolate(segmentVertex, baseMiddle, 2); return new Bezier(segmentStart, segmentControl, segmentEnd, true); } //************************************************** // TANGENT OF BEZIER POINT //************************************************** /** * Tangent is line that touches but does not intersect with bezier. * Computes and returns the angle of tangent line in radians. * The return value is between positive pi and negative pi. * * @param t:Number time of bezier point * @return Number angle in radians; * * @example * * import flash.geom.Bezier; * import flash.geom.Point; * * function randomPoint():Point { * return new Point(Math.random()*stage.stageWidth, Math.random()*stage.stageHeight); * } * function randomBezier():Bezier { * return new Bezier(randomPoint(), randomPoint(), randomPoint()); * } * * const bezier:Bezier = randomBezier(); * * * @langversion 3.0 * @playerversion Flash 9.0 * */ public function getTangentAngle(time : Number = 0) : Number { const t0X : Number = startPoint.x + (controlPoint.x - startPoint.x) * time; const t0Y : Number = startPoint.y + (controlPoint.y - startPoint.y) * time; const t1X : Number = controlPoint.x + (endPoint.x - controlPoint.x) * time; const t1Y : Number = controlPoint.y + (endPoint.y - controlPoint.y) * time; const distanceX : Number = t1X - t0X; const distanceY : Number = t1Y - t0Y; return Math.atan2(distanceY, distanceX); } //************************************************** // INTERSECTIONS //************************************************** /** * Результат вычисления пересечения кривой Безье с линией может дать следующие результаты:
* - если пересечение отсутствует, возвращается null;
* - если пересечение произошло в одной или двух точках, будет возвращен объект Intersection, * и time-итераторы точек пересечения на кривой Безье будут находиться в массиве currentTimes. * time-итераторы точек пересечения target будут находиться в массиве targetTimes;
* - если кривая Безье вырождена, то может произойти совпадение. * В этом случае результатом будет являться отрезок - объект Line (isSegment=true), * который будет доступен как свойство coincidenceLine в возвращаемом объекте Intersection;
*
* На результаты вычисления пересечений оказывает влияние свойство isSegment как текущего объекта, * так и значение isSegment объекта target. * * @param target:Line * @return Intersection * * @example * import flash.geom.Bezier; * import flash.geom.Point; * * function randomPoint():Point { * return new Point(Math.random()*stage.stageWidth, Math.random()*stage.stageHeight); * } * function randomBezier():Bezier { * return new Bezier(randomPoint(), randomPoint(), randomPoint()); * } * * const bezier:Bezier = randomBezier(); * * @langversion 3.0 * @playerversion Flash 9.0 * * @see Intersection * @see Line * */ public function intersectionLine(target : Line) : Intersection { // быстрая проверка. // очевидно, что порабола никогда не выходит за пределы сектора SCE // если если кривая бесконечна смторим пересечения секторов // если конечна, пересечение треугольников SCE if(!intersectionRayLine(controlPoint, endPoint, target.start, target.end, isSegment, target.isSegment)) if(!intersectionRayLine(controlPoint, startPoint, target.start, target.end, isSegment, target.isSegment)) { var p1 : Point = new Point(startPoint.x - controlPoint.x, startPoint.y - controlPoint.y), p2 : Point = new Point(endPoint.x - controlPoint.x, endPoint.y - controlPoint.y), p3 : Point = new Point(target.start.x - controlPoint.x, target.start.y - controlPoint.y), angle : Point = new Point((p1.x * p2.x + p1.y * p2.y) / p1.length / p2.length, (p1.x * p2.y - p2.x * p1.y) / p1.length / p2.length), angle2 : Point = new Point((p3.x * p2.x + p3.y * p2.y) / p3.length / p2.length, (p3.x * p2.y - p2.x * p3.y) / p3.length / p2.length); if(angle.y * angle2.y < 0 || angle2.x < angle.x) return null; } var intersection : Intersection = new Intersection(); const sX : Number = startPoint.x; const sY : Number = startPoint.y; const cX : Number = controlPoint.x; const cY : Number = controlPoint.y; const eX : Number = endPoint.x; const eY : Number = endPoint.y; const oX : Number = target.start.x; const oY : Number = target.start.y; const lineAngle : Number = target.angle; var cosa : Number = Math.cos(lineAngle); var sina : Number = Math.sin(lineAngle); // var time0 : Number; var time1 : Number; var lineTime0 : Number; var lineTime1 : Number; var intersectionPoint0 : Point; var intersectionPoint1 : Point; const distanceX : Number = target.end.x - target.start.x; const distanceY : Number = target.end.y - target.start.y; const checkByX : Boolean = Math.abs(distanceX) > Math.abs(distanceY); if (Math.abs(cosa) < 1e-6) { cosa = 0; } if (Math.abs(sina) < 1e-6) { sina = 0; } // кривая лежит на прямой if((sX + eX - 2 * cX) * (cY - sY) == (sY + eY - 2 * cY) * (cX - sX)) { const al : Number = -distanceY, bl : Number = distanceX, cl : Number = -al * oX - bl * oY; if(!(al * sX + bl * sY + cl) && !(al * eX + bl * eY + cl) && !(al * cX + bl * cY + cl)) { intersection.isCoincidence = true; // прямые заданы параметрически, нет нужды делать лишние проверки // результат var reT : Number; var rsT : Number; // видимый отрезок var beT : Number; var bsT : Number; // точки кривой безье var sT : Number; var cT : Number; var eT : Number; // заданная прямая const leT : Number = 1; const lsT : Number = 0; if(bl) { sT = (sX - oX) / bl; cT = (cX - oX) / bl; eT = (eX - oX) / bl; } else { // прямые строго вертикальны sT = (oY - sY) / al; cT = (oY - cY) / al; eT = (oY - eY) / al; } // случай, когда управляющая точка лежит вне опорных if((cT - sT) * (cT - eT) > 0) { const p : Point = bl ? getPoint(-(cX - sX) / (sX + eX - 2 * cX)) : getPoint(-(cY - sY) / (sY + eY - 2 * cY)); bsT = bl ? (p.x - oX) / bl : (oY - p.y) / al; if(Math.abs(bsT - sT) > Math.abs(bsT - eT)) beT = sT; else beT = eT; } else { beT = eT; bsT = sT; } // считаем пересечение отрезков if ((lsT - bsT) * (lsT - beT) <= 0 && (leT - bsT) * (leT - beT) <= 0) { reT = leT; rsT = lsT; } else if ((bsT - lsT) * (bsT - leT) <= 0 && (beT - lsT) * (beT - leT) <= 0) { reT = beT; rsT = bsT; } else if ((bsT - lsT) * (bsT - leT) <= 0 && (beT - lsT) * (beT - leT) >= 0) { rsT = bsT; reT = (lsT - bsT) * (lsT - beT) <= 0 ? lsT : leT; } else if ((bsT - lsT) * (bsT - leT) >= 0 && (beT - lsT) * (beT - leT) <= 0) { rsT = beT; reT = (lsT - bsT) * (lsT - beT) <= 0 ? lsT : leT; } intersection.targetTimes.push(rsT); intersection.targetTimes.push(reT); intersection.coincidenceLine = new Line(new Point(rsT * bl + oX, -rsT * al + oY), new Point(reT * bl + oX, -reT * al + oY)); return intersection; } } var divider : Number = -2 * sina * cX + sina * eX + sina * sX + 2 * cosa * cY - cosa * eY - cosa * sY; if (Math.abs(divider) < 1e-6) { divider = 0; } // единственное решение. Иначе говоря отрезок параллелен параболе if (divider == 0) { const divider2 : Number = (-2 * sX + 2 * cX) * sina - (-2 * sY + 2 * cY) * cosa; if (divider2 == 0) { // Это вообще что? intersection.currentTimes[0] = 0; intersection.currentTimes[1] = 1; intersectionPoint0 = getPoint(0); intersectionPoint1 = getPoint(1); if (checkByX) { lineTime0 = (intersectionPoint0.x - target.start.x) / distanceX; lineTime1 = (intersectionPoint1.x - target.start.x) / distanceX; } else { lineTime0 = (intersectionPoint0.y - target.start.y) / distanceY; lineTime1 = (intersectionPoint1.y - target.start.y) / distanceY; } return intersection; } time0 = -((sX - oX) * sina - (sY - oY) * cosa) / divider2; intersectionPoint0 = getPoint(time0); var intersection_is_in_segment : Number; if(checkByX > 0) { intersection_is_in_segment = (intersectionPoint0.x - target.start.x) * (intersectionPoint0.x - target.end.x); } else { intersection_is_in_segment = (intersectionPoint0.y - target.start.y) * (intersectionPoint0.y - target.end.y); } if(isSegment && target.isSegment) { if(time0 < 0 || time0 > 1) return null; if (intersection_is_in_segment > 0) return null; } if(!isSegment && target.isSegment) { if (intersection_is_in_segment > 0) return null; } if(isSegment && !target.isSegment) { if(time0 < 0 || time0 > 1) return null; } if (checkByX) { lineTime0 = (intersectionPoint0.x - target.start.x) / distanceX; } else { lineTime0 = (intersectionPoint0.y - target.start.y) / distanceY; } intersection.currentTimes.push(time0); intersection.targetTimes.push(lineTime0); return intersection; } const discriminant : Number = +cosa * cosa * (sY * oY + cY * cY - eY * sY - 2 * cY * oY + eY * oY) + sina * cosa * (-sY * oX - eY * oX - 2 * cY * cX + eX * sY - sX * oY + 2 * cY * oX + 2 * cX * oY + eY * sX - eX * oY) + sina * sina * (eX * oX + sX * oX - 2 * cX * oX + cX * cX - eX * sX); if (discriminant < 0) { return null; } const a : Number = -2 * cosa * sY + 2 * sina * sX + 2 * cosa * cY - 2 * sina * cX; const c : Number = 2 * divider; var outsideBezier0 : Boolean; var outsideLine0 : Boolean; var outsideBezier1 : Boolean; var outsideLine1 : Boolean; // касательная if (discriminant == 0) { time0 = a / c; outsideBezier0 = time0 < 0 || time0 > 1; if (isSegment && outsideBezier0) { return null; } intersectionPoint0 = getPoint(time0); if (checkByX) { lineTime0 = (intersectionPoint0.x - target.start.x) / distanceX; } else { lineTime0 = (intersectionPoint0.y - target.start.y) / distanceY; } outsideLine0 = lineTime0 < 0 || lineTime0 > 1; if (target.isSegment && outsideLine0) { return null; } intersection.currentTimes[0] = time0; intersection.targetTimes[0] = lineTime0; return intersection; } // if discriminant > 0 // пересечение по двум точкам const b : Number = 2 * Math.sqrt(discriminant); time0 = (a - b) / c; time1 = (a + b) / c; outsideBezier0 = time0 < 0 || time0 > 1; outsideBezier1 = time1 < 0 || time1 > 1; if (isSegment && outsideBezier0 && outsideBezier1) { return null; } intersectionPoint0 = getPoint(time0); intersectionPoint1 = getPoint(time1); if (distanceX) { lineTime0 = (intersectionPoint0.x - target.start.x) / distanceX; lineTime1 = (intersectionPoint1.x - target.start.x) / distanceX; } else { lineTime0 = (intersectionPoint0.y - target.start.y) / distanceY; lineTime1 = (intersectionPoint1.y - target.start.y) / distanceY; } outsideLine0 = lineTime0 < 0 || lineTime0 > 1; outsideLine1 = lineTime1 < 0 || lineTime1 > 1; if (target.isSegment && outsideLine0 && outsideLine1) { return null; } if (isSegment) { if (target.isSegment) { if (!outsideBezier0 && !outsideLine0) { intersection.currentTimes.push(time0); intersection.targetTimes.push(lineTime0); } if (!outsideBezier1 && !outsideLine1) { intersection.currentTimes.push(time1); intersection.targetTimes.push(lineTime1); } } else { if (!outsideBezier0) { intersection.currentTimes.push(time0); intersection.targetTimes.push(lineTime0); } if (!outsideBezier1) { intersection.currentTimes.push(time1); intersection.targetTimes.push(lineTime1); } } if (!intersection.currentTimes.length) { return null; } return intersection; } // if !this.isSegment if (target.isSegment) { if (!outsideLine0) { intersection.currentTimes.push(time0); intersection.targetTimes.push(lineTime0); } if (!outsideLine1) { intersection.currentTimes.push(time1); intersection.targetTimes.push(lineTime1); } if (!intersection.currentTimes.length) { return null; } return intersection; } // if !this.isSegment && !target.isSegment intersection.currentTimes.push(time0); intersection.targetTimes.push(lineTime0); intersection.currentTimes.push(time1); intersection.targetTimes.push(lineTime1); return intersection; } /** * Результат вычисления пересечения кривой Безье с другой кривой Безье может дать следующие результаты:
* - если пересечение отсутствует, возвращается null;
* - если пересечение произошло в точках (от одной до четырех точек), будет возвращен объект Intersection, * и time-итераторы точек пересечения на данной кривой Безье будут находиться в массиве currentTimes. * time-итераторы точек пересечения target будут находиться в массиве targetTimes;
* - также может произойти совпадение кривых. В этом случае результатом будет являться кривая - объект Bezier (isSegment=true), * которая будет доступна как свойство coincidenceBezier в возвращаемом объекте Intersection;
*
* На результаты вычисления пересечений оказывает влияние свойство isSegment как текущего объекта, * так и значение isSegment объекта target. * * @param target:Bezier * @return Intersection * * @example * import flash.geom.Bezier; * import flash.geom.Point; * * function randomPoint():Point { * return new Point(Math.random()*stage.stageWidth, Math.random()*stage.stageHeight); * } * function randomBezier():Bezier { * return new Bezier(randomPoint(), randomPoint(), randomPoint()); * } * * const bezier:Bezier = randomBezier(); * * * @langversion 3.0 * @playerversion Flash 9.0 * */ // TODO: [Sergeev] метод не закончен. // 4. Требуется расчета совпадений public function intersectionBezier(target : Bezier) : Intersection { // быстрая проверка. // очевидно, что порабола никогда не выходит за пределы сектора SCE // если если кривая бесконечна смторим пересечения секторов // если конечна, пересечение треугольников SCE if(!intersectionRay(controlPoint, endPoint, target.controlPoint, target.endPoint, isSegment, target.isSegment)) if(!intersectionRay(controlPoint, startPoint, target.controlPoint, target.endPoint, isSegment, target.isSegment)) if(!intersectionRay(controlPoint, endPoint, target.controlPoint, target.startPoint, isSegment, target.isSegment)) if(!intersectionRay(controlPoint, startPoint, target.controlPoint, target.startPoint, isSegment, target.isSegment)) { // лучи не пересекаются // но один сектор может поглотить другой var litmus : Number = 0; var p1 : Point = new Point(startPoint.x - controlPoint.x, startPoint.y - controlPoint.y), p2 : Point = new Point(endPoint.x - controlPoint.x, endPoint.y - controlPoint.y), p3 : Point = new Point(target.controlPoint.x - controlPoint.x, target.controlPoint.y - controlPoint.y), angle : Point = new Point((p1.x * p2.x + p1.y * p2.y) / p1.length / p2.length, (p1.x * p2.y - p2.x * p1.y) / p1.length / p2.length), angle2 : Point = new Point((p3.x * p2.x + p3.y * p2.y) / p3.length / p2.length, (p3.x * p2.y - p2.x * p3.y) / p3.length / p2.length); // второй внутри первого if(angle.y * angle2.y < 0 || angle2.x < angle.x) { litmus++; } p1 = new Point(target.startPoint.x - target.controlPoint.x, target.startPoint.y - target.controlPoint.y); p2 = new Point(target.endPoint.x - target.controlPoint.x, target.endPoint.y - target.controlPoint.y); p3 = new Point(controlPoint.x - target.controlPoint.x, controlPoint.y - target.controlPoint.y); angle = new Point((p1.x * p2.x + p1.y * p2.y) / p1.length / p2.length, (p1.x * p2.y - p2.x * p1.y) / p1.length / p2.length); angle2 = new Point((p3.x * p2.x + p3.y * p2.y) / p3.length / p2.length, (p3.x * p2.y - p2.x * p3.y) / p3.length / p2.length); // первый внутри второго if(angle.y * angle2.y < 0 || angle2.x < angle.x) { litmus++; } if(litmus == 2) return null; } const ax1 : Number = startPoint.x + endPoint.x - 2 * controlPoint.x, bx1 : Number = 2 * controlPoint.x - 2 * startPoint.x, cx1 : Number = startPoint.x, ax2 : Number = target.startPoint.x + target.endPoint.x - 2 * target.controlPoint.x, bx2 : Number = 2 * target.controlPoint.x - 2 * target.startPoint.x, cx2 : Number = target.startPoint.x, ay1 : Number = startPoint.y + endPoint.y - 2 * controlPoint.y, by1 : Number = 2 * controlPoint.y - 2 * startPoint.y, cy1 : Number = startPoint.y, ay2 : Number = target.startPoint.y + target.endPoint.y - 2 * target.controlPoint.y, by2 : Number = 2 * target.controlPoint.y - 2 * target.startPoint.y, cy2 : Number = target.startPoint.y, cy : Number = cy1 - cy2, cx : Number = cx1 - cx2; var intersection : Intersection; // обе кривые - прямые линии. Возможно пересечение в виде прямой if(ax2 * by2 == ay2 * bx2 && ax1 * by1 == ay1 * bx1) { // TODO поправки на C intersection = intersectionLine(new Line(target.startPoint,target.endPoint)); return intersection; } // решение «в лоб» const A : Number = -(ax2 * ay1 - ax1 * ay2) * (ax2 * ay1 - ax1 * ay2), B : Number = -2 * (ax2 * ay1 - ax1 * ay2) * (-ay2 * bx1 + ax2 * by1), C : Number = -ay2 * ay2 * (bx1 * bx1 + 2 * ax1 * cx) - ax2 * (by2 * (ay1 * bx2 - ax1 * by2) + ax2 * (by1 * by1 + 2 * ay1 * cy)) + ay2 * (-ax1 * bx2 * by2 + ay1 * (bx2 * bx2 + 2 * ax2 * cx) + 2 * ax2 * (bx1 * by1 + ax1 * cy)), D : Number = -2 * ay2 * ay2 * bx1 * cx + ay2 * (bx2 * bx2 * by1 - bx1 * bx2 * by2 + 2 * ax2 * (by1 * cx + bx1 * cy)) + ax2 * (-bx2 * by1 * by2 + bx1 * by2 * by2 - 2 * ax2 * by1 * cy), E : Number = -ay2 * ay2 * cx * cx + ay2 * (-bx2 * by2 * cx + bx2 * bx2 * cy + 2 * ax2 * cx * cy) - ax2 * (-by2 * by2 * cx + bx2 * by2 * cy + ax2 * cy * cy); const solves : Array = Equations.solveEquation(A, B, C, D, E); intersection = new Intersection(); //поворачиваем кривую в вертикальное положение. Решение будет одно и только одно. const tga : Number = ay2 ? -ax2 / ay2 : null, sina : Number = ay2 ? tga / Math.sqrt(1 + tga * tga) : sign(-ax2), cosa : Number = ay2 ? 1 / Math.sqrt(1 + tga * tga) : 0; const bxn : Number = bx2 * cosa + by2 * sina, cxn : Number = cx2 * cosa + cy2 * sina; var pSolve : Point; // бесконечное множество решений. То есть есть совпадение кривых if(!A && !B && !C && !D && !E) { var t1 : Number, t2 : Number, rt1 : Number, rt2 : Number; pSolve = getPoint(0); t1 = (pSolve.x * cosa + pSolve.y * sina - cxn) / bxn; pSolve = getPoint(1); t2 = (pSolve.x * cosa + pSolve.y * sina - cxn) / bxn; if (t1 * (t1 - 1) <= 0 && t2 * (t2 - 1) <= 0) { rt2 = t2; rt1 = t1; } else if ( t1 * t2 <= 0 && (1 - t1) * (1 - t2) <= 0) { rt2 = 1; rt1 = 0; } else if ( t1 * t2 <= 0 && (1 - t1) * (1 - t2) >= 0) { rt1 = 0; rt2 = t1 * (t1 - 1) <= 0 ? t1 : t2; } else if (t1 * t2 >= 0 && (1 - t1) * (1 - t2) <= 0) { rt1 = 1; rt2 = t1 * (t1 - 1) <= 0 ? t1 : t2; } else { // нет пересечений return null; } intersection.isCoincidence = true; intersection.coincidenceBezier = target.getSegment(rt1, rt2); return intersection; } for(var i : Number = 0 ;i < solves.length; i++) { pSolve = getPoint(solves[i]); var ox : Number = pSolve.x * cosa + pSolve.y * sina; //var oy : Number = pSolve.y * cosa - pSolve.x * sina; // TODO: если кривая — прямая линия? var ot : Number = bxn ? (ox - cxn) / bxn : 0.5; if(isSegment && (solves[i] >= 1 || solves[i] <= 0)) continue; if(target.isSegment && (ot >= 1 || ot <= 0)) continue; intersection.targetTimes.push(ot); intersection.currentTimes.push(solves[i]); } return intersection; } //************************************************** // UTILS //************************************************** /** * * @return String * */ public function toString() : String { return "(start:" + startPoint + ", control:" + controlPoint + ", end:" + endPoint + ")"; } //************************************************** // PRIVATE //************************************************** private function sign(vol : Number) : Number { if(vol > 0) return 1; if(vol < 0) return -1; return 0; } private function intersectionRay(p1s : Point, p1e : Point, p2s : Point, p2e : Point, seg1 : Boolean, seg2 : Boolean) : Boolean { const dx1 : Number = p1e.x - p1s.x, dy1 : Number = p1e.y - p1s.y, dx2 : Number = p2e.x - p2s.x, dy2 : Number = p2e.y - p2s.y, div : Number = dx1 * dy2 - dy1 * dx2; var t1 : Number, t2 : Number; // параллельны if(!div) { // совпадение точек if(p1e.x == p2e.x && p1e.y == p2e.y) return true; } else { t1 = (dx2 * (p1s.y - p2s.y) - dy2 * (p1s.x - p2s.x)) / div; t2 = (dx1 * (p1s.y - p2s.y) - dy1 * (p1s.x - p2s.x)) / div; if(t1 >= 1 && seg1) return false; if(t2 >= 1 && seg2) return false; if(t2 > 0 && t1 > 0) return true; } return false; } private function intersectionRayLine(p1s : Point, p1e : Point, p2s : Point, p2e : Point, seg1 : Boolean, seg2 : Boolean) : Boolean { const dx1 : Number = p1e.x - p1s.x, dy1 : Number = p1e.y - p1s.y, dx2 : Number = p2e.x - p2s.x, dy2 : Number = p2e.y - p2s.y, div : Number = dx1 * dy2 - dy1 * dx2; var t1 : Number, t2 : Number; // параллельны if(!div) { // совпадение точек if(p1e.x == p2e.x && p1e.y == p2e.y) return true; } else { t1 = (dx2 * (p1s.y - p2s.y) - dy2 * (p1s.x - p2s.x)) / div; t2 = (dx1 * (p1s.y - p2s.y) - dy1 * (p1s.x - p2s.x)) / div; if(t1 >= 1 && seg1) return false; if(t2 >= 1 && seg2) return false; if(t1 > 0) return true; } return false; } } }