// 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 and 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 getPoint() method.
* To find time-iterator for the two-dimensional point, use the getClosest() method.
*
* 3. 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 = 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)| * * |
Интерактивная демонстрация
* Используйте клавиши "влево" "вправо" для управления итератором.
* Мышью перемещайте контрольные точки кривой.
S и E всегда принадлежат кривой Безье и ограничивают ее.t=0.5 лежит на середине отрезка, соединяюшем С и середину отрезка SE.C в общем случае не принадлежит кривой и лежит на пересечении касательных к кривой в точках S и EС лежит на прямой SE, то такая кривая является вырожденнойSE равняется 2/3 t изменяющимся в бесконечных пределах является параболой.
* Если кривая Безье лежит на параболе, то такая парабола по отношению к ней является родительской.
*
* 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.
*
Pt on the Bezier second-order curve is computed using formula:Pt = S*(1-t)2
* + 2*C*(1-t)*t + E*t2
* (1)t(time) — time-iterator of the pointS(start) — initial control (anchor) point (t=0)С(control) — direction pointE(end) — final control (anchor) point (t=1)| * * |
Interactive demo
* You can use "Left" and "Right" keys to control the iterator.
* Curve control points are dragable.
S and E always
* lie on the Bezier curve and limit itt = 0.5 -
* lies in the middle of the segment, that connects С and the
* middle of the SE segmentC generally does not belongs to the curve
* and lies on the intersection of the tangents to the curve from points
* S and EС belongs to SE then the curve is singularSE equals 2/3 of the ∆SCE triangle squaret changing in infinite limits is a parabola.
* If Bezier curve lies on parabola then this parabola is considered to be the parent for it.Line interprets segment this way to simplify
* it's usage together with Bezier class.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.SE.
* Площадь этой фигуры составляет 2/3 площади треугольника ∆SCE,
* образуемого контрольными точками start, control, end.SE.
* The area of this figure makes 2/3 areas of a triangle ∆SCE, which is formed of
* control points start, control, end.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);
}
/* *
* Вычисляет и возвращает габаритный прямоугольник сегмента кривой Безье.time.
*
* @param time:Number итератор точки кривой
*
* @return Point точка на кривой Безье;start и end, но не сами объекты start и end
*
*
* @example time.
*
* @param time:Number iterator of the point on curve
*
* @return Point point on the Bezier curve;start
* or end will be returned, but not objects exact start or end.
*
*
* @example startstep.startShift, то расчет производится
* не от точки start, а от точки на кривой, находящейся на
* заданнй этим параметром дистанции.start
*
* @return Array sequence of points on bezier curve;
*
* @example Pt,
* будет находиться в заданных параметрами x и y координатах.x или y не задан,
* то точка Pt не изменит значение соответствующей координаты.
*
* @param time:Number time-итератор точки кривой.
* @param x:Number новое значение позиции точки по оси X.
* @param y:Number новое значение позиции точки по оси Y.
*
* @example
* 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, либо от минус
* бесконечности до плюс бесконечности.
segment1
* target будут находиться в массиве targetTimes;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;
}
}
}