【问题标题】:How to draw a continuous curved line from 3 given points at a time如何一次从 3 个给定点绘制连续曲线
【发布时间】:2011-05-10 06:58:42
【问题描述】:

我正在尝试在 Flash 中绘制一条连续的曲线。有很多方法,但到目前为止我发现没有一个完全符合我的要求。首先,我想使用flash图形api的curveTo()方法。 我不想simulate a curve with hundreds of calls to lineTo() per curved line segment这是我的经验和理解线段是处理器繁重的。 Flash 的二次贝塞尔曲线应该占用更少的 CPU 功率。如果你认为我错了,请挑战这个假设。

我也不想使用将整行作为参数的预制方法(例如 mx.charts.chartClasses.GraphicsUtilities.drawPolyline())。 原因是我最终需要修改逻辑以向我正在绘制的线条添加装饰,所以我需要一些我理解的最低级别的东西。

我目前创建了一个方法,可以在给定 3 个点 using the mid-point method found here 的情况下绘制一条曲线。

这是一张图片:

问题在于这些线实际上并没有通过线的“真实”点(灰色圆圈)弯曲。有没有一种方法可以利用数学的力量来调整控制点,使曲线实际上通过“真实”点?仅将当前点及其上一个/下一个点作为参数?复制上图的代码如下。如果我能修改它以满足这个要求,那就太好了(注意第一点和最后一点的例外)。

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);
  }
}

稍后我将在 draw 方法中添加箭头和东西。

【问题讨论】:

  • 好的,为了获得想要的效果,我开始看起来在任何给定时间我的绘制方法至少需要 4 个点。如果我错了,请纠正我。到目前为止,我只是使用中点计算;还没有触发。
  • flex有样条绘图包:degrafa.org/docs
  • +1 清晰的写作风格!

标签: apache-flex actionscript-3 bezier continuous spline


【解决方案1】:

我认为您正在寻找 Catmull-Rom 样条。我已经为您搜索了一个 AS3 实现,但尚未尝试过,因此请自行决定使用:

http://actionsnippet.com/?p=1031

【讨论】:

  • 这是我的博客文章 - 所以我可以确认该页面上的方法应该可以工作 +1 您只需将像素绘图代码替换为图形类 lineTo/moveTo 就可以了
  • 非常好。但是,如果我理解正确,它就会不符合我的条件之一。那就是我没有绘制多条直线段来模拟曲线。如果我遇到性能问题,唯一的解决方案是降低曲线分辨率,使其更像一系列直线。这就是为什么我需要使用 flash 的 curveTo() 方法绘制曲线。
  • PS,别以为我忘恩负义。我真的很喜欢这种方法。但对于我目前的问题,我怀疑它是否合适。
【解决方案2】:

好的,Catmull-Rom 样条建议是一个很好的建议,但不是完全我正在寻找的。 提供的链接中的示例是一个很好的起点,但有点不灵活。我已经使用它并修改了我的原始源代码以使用它。我将其发布为答案,因为我认为它比 Zevan 的博客文章更模块化且更易于理解(没有冒犯 Zevan!)。以下代码将显示如下图像:

代码如下:

    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() 确实完成了我正在尝试做的事情——问题在于它不灵活,我无法解析代码(更重要的是,它没有t 似乎正确地绘制了锐角——如果我错了,请纠正我)。如果有人可以提供任何见解,请发布。目前,以上是我的答案。

【讨论】:

  • 注意:对 Hermite 函数值使用 Vector 而不是 Array 可能会提高性能——对我来说非常重要。
【解决方案3】:

我对此进行了编码,我认为它可能会有所帮助:

SWF:http://dl.dropbox.com/u/2283327/stackoverflow/SplineTest.swf

代码:http://dl.dropbox.com/u/2283327/stackoverflow/SplineTest.as

我在代码上留下了很多 cmets。希望对您有所帮助!

以下是代码背后的理论:

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:对不起我的英语不好

【讨论】:

  • 谢谢,但这并不能模拟多个任意点上的样条曲线。请参阅 GraphicsUtilities.drawPolyLine() 以了解我正在尝试完成的示例。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-09-09
  • 2017-10-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-21
相关资源
最近更新 更多