Как нарисовать непрерывную кривую линию из 3-х заданных точек одновременно
Я пытаюсь нарисовать непрерывную кривую линию во вспышке. Есть много методов, но ни один из тех, которые я нашел, пока не соответствует моим требованиям. Прежде всего, я хочу использовать метод curveTo() флэш-графики api. Я не хочу имитировать кривую с сотнями обращений к lineTo() на сегмент изогнутой линии. По моему опыту и пониманию, линейные сегменты сильно загружены процессором. Квадратичная кривая Безье Flash должна потреблять меньше ресурсов процессора. Пожалуйста, бросьте вызов этому предположению, если вы думаете, что я неправ.
Я также не хочу использовать готовый метод, который принимает всю строку в качестве аргумента (например, mx.charts.chartClasses.GraphicsUtilities.drawPolyline()). Причина в том, что мне нужно будет в конечном итоге изменить логику, чтобы добавить украшения к линии, которую я рисую, поэтому мне нужно что-то, что я понимаю на самом низком уровне.
В настоящее время я создал метод, который будет рисовать кривую с учетом 3 точек, используя метод средней точки, найденный здесь.
Вот картинка:
Проблема в том, что линии на самом деле не изгибаются через "реальные" точки линии (серые кружки). Есть ли способ использовать силу математики, чтобы я мог настроить контрольную точку так, чтобы кривая действительно проходила через "реальную" точку? Учитывая только текущую точку и ее предыдущую / следующую точку в качестве аргументов? Код для дублирования приведенного выше рисунка следует. Было бы здорово, если бы я мог изменить его, чтобы он соответствовал этому требованию (обратите внимание на исключение для первого и последнего пункта).
package {
import flash.display.Shape;
import flash.display.Sprite;
import flash.display.Stage;
import flash.geom.Point;
[SWF(width="200",height="200")]
public class TestCurves extends Sprite {
public function TestCurves() {
stage.scaleMode = "noScale";
var points:Array = [
new Point(10, 10),
new Point(80, 80),
new Point(80, 160),
new Point(20, 160),
new Point(20, 200),
new Point(200, 100)
];
graphics.lineStyle(2, 0xFF0000);
var point:Point = points[0];
var nextPoint:Point = points[1];
SplineMethod.drawSpline(graphics, point, null, nextPoint);
var prevPoint:Point = point;
var n:int = points.length;
var i:int;
for (i = 2; i < n + 1; i++) {
point = nextPoint;
nextPoint = points[i]; //will eval to null when i == n
SplineMethod.drawSpline(graphics, point, prevPoint, nextPoint);
prevPoint = point;
}
//straight lines and vertices for comparison
graphics.lineStyle(2, 0xC0C0C0, 0.5);
graphics.drawCircle(points[0].x, points[0].y, 4);
for (i = 1; i < n; i++) {
graphics.moveTo(points[i - 1].x, points[i - 1].y);
graphics.lineTo(points[i].x, points[i].y);
graphics.drawCircle(points[i].x, points[i].y, 4);
}
}
}
}
import flash.display.Graphics;
import flash.geom.Point;
internal class SplineMethod {
public static function drawSpline(target:Graphics, p:Point, prev:Point=null, next:Point=null):void {
if (!prev && !next) {
return; //cannot draw a 1-dimensional line, ie a line requires at least two points
}
var mPrev:Point; //mid-point of the previous point and the target point
var mNext:Point; //mid-point of the next point and the target point
if (prev) {
mPrev = new Point((p.x + prev.x) / 2, (p.y + prev.y) / 2);
}
if (next) {
mNext = new Point((p.x + next.x) / 2, (p.y + next.y) / 2);
if (!prev) {
//This is the first line point, only draw to the next point's mid-point
target.moveTo(p.x, p.y);
target.lineTo(mNext.x, mNext.y);
return;
}
} else {
//This is the last line point, finish drawing from the previous mid-point
target.moveTo(mPrev.x, mPrev.y);
target.lineTo(p.x, p.y);
return;
}
//draw from mid-point to mid-point with the target point being the control point.
//Note, the line will unfortunately not pass through the actual vertex... I want to solve this
target.moveTo(mPrev.x, mPrev.y);
target.curveTo(p.x, p.y, mNext.x, mNext.y);
}
}
Позже я буду добавлять стрелки и другие вещи в метод рисования.
3 ответа
Хорошо, предложение сплайна Катмулла-Рома - хорошее, но не совсем то , что я ищу. Пример по предоставленной ссылке был хорошей отправной точкой, но немного негибким. Я взял его и изменил свой исходный код, чтобы использовать его. Я публикую это как ответ, потому что считаю, что он более модульный и легче для понимания, чем пост в блоге Зевана (без обид Зеван!). Следующий код отобразит следующее изображение:
Вот код:
package {
import flash.display.Shape;
import flash.display.Sprite;
import flash.display.Stage;
import flash.geom.Point;
[SWF(width="300",height="300")]
public class TestCurves extends Sprite {
public function TestCurves() {
stage.scaleMode = "noScale";
//draw a helpful grid
graphics.lineStyle(1, 0xC0C0C0, 0.5);
for (var x:int = 0; x <= 300; x += 10) {
graphics.moveTo(x, 0);
graphics.lineTo(x, 300);
graphics.moveTo(0, x);
graphics.lineTo(300, x);
}
var points:Array = [
new Point(40, 20),
new Point(120, 80),
new Point(120, 160),
new Point(60, 160),
new Point(60, 200),
new Point(240, 150),
new Point(230, 220),
new Point(230, 280)
];
SplineMethod.setResolution(5);
graphics.lineStyle(2, 0xF00000);
graphics.moveTo(points[0].x, points[0].y);
var n:int = points.length;
var i:int;
for (i = 0; i < n - 1; i++) {
SplineMethod.drawSpline(
graphics,
points[i], //segment start
points[i + 1], //segment end
points[i - 1], //previous point (may be null)
points[i + 2] //next point (may be null)
);
}
//straight lines and vertices for comparison
graphics.lineStyle(2, 0x808080, 0.5);
graphics.drawCircle(points[0].x, points[0].y, 4);
for (i = 1; i < n; i++) {
graphics.moveTo(points[i - 1].x, points[i - 1].y);
graphics.lineTo(points[i].x, points[i].y);
graphics.drawCircle(points[i].x, points[i].y, 4);
}
}
}
}
import flash.display.Graphics;
import flash.geom.Point;
internal class SplineMethod {
//default setting will just draw a straight line
private static var hermiteValues:Array = [0, 0, 1, 0];
public static function setResolution(value:int):void {
var resolution:Number = 1 / value;
hermiteValues = [];
for (var t:Number = resolution; t <= 1; t += resolution) {
var h00:Number = (1 + 2 * t) * (1 - t) * (1 - t);
var h10:Number = t * (1 - t) * (1 - t);
var h01:Number = t * t * (3 - 2 * t);
var h11:Number = t * t * (t - 1);
hermiteValues.push(h00, h10, h01, h11);
}
}
public static function drawSpline(target:Graphics, segmentStart:Point, segmentEnd:Point, prevSegmentEnd:Point=null, nextSegmentStart:Point=null):void {
if (!prevSegmentEnd) {
prevSegmentEnd = segmentStart;
}
if (!nextSegmentStart) {
nextSegmentStart = segmentEnd;
}
var m1:Point = new Point((segmentEnd.x - prevSegmentEnd.x) / 2, (segmentEnd.y - prevSegmentEnd.y) / 2);
var m2:Point = new Point((nextSegmentStart.x - segmentStart.x) / 2, (nextSegmentStart.y - segmentStart.y) / 2);
var n:int = hermiteValues.length;
for (var i:int = 0; i < n; i += 4) {
var h00:Number = hermiteValues[i];
var h10:Number = hermiteValues[i + 1];
var h01:Number = hermiteValues[i + 2];
var h11:Number = hermiteValues[i + 3];
var px:Number = h00 * segmentStart.x + h10 * m1.x + h01 * segmentEnd.x + h11 * m2.x;
var py:Number = h00 * segmentStart.y + h10 * m1.y + h01 * segmentEnd.y + h11 * m2.y;
target.lineTo(px, py);
}
}
}
Это не идеальное решение. Но, к сожалению, я не могу собрать воедино, как выполнить то, что я хочу, используя curveTo(). Обратите внимание, что GraphicsUtilities.drawPolyLine() действительно выполняет то, что я пытаюсь сделать - проблема в том, что он негибкий, и я не могу разобрать код (что более важно, он, кажется, неправильно рисует острые углы - поправьте меня, если Я не прав). Если кто-то может предоставить какие-либо идеи, пожалуйста, напишите. Пока выше, мой ответ.
Я думаю, что вы ищете сплайн Catmull-Rom. Я нашел для вас реализацию AS3, но не пробовал ее, поэтому используйте по своему усмотрению:
Я кодирую это, я думаю, что это может помочь:
SWF: http://dl.dropbox.com/u/2283327/stackru/SplineTest.swf
Код: http://dl.dropbox.com/u/2283327/stackru/SplineTest.as
Я оставил много комментариев к коду. Я желаю, чтобы это помогло!
Вот теория, лежащая в основе кода:
A и C - первая и последняя точка, B - "контрольная точка" в AS3, вы можете нарисовать кривую так:
graphics.moveTo(A.x, A.y);
graphics.curveTo(B.x, B.y, C.x, C.y);
Теперь D - середина вектора AC. А середина DB - это середина кривой. Теперь в коде я переместил B точно на D+DB*2, поэтому, если вы нарисуете кривую, используя эту точку в качестве контрольной точки, средняя точка кривой будет B.
PS: извините за мой бедный английский