As3用于在样条曲线或Hermite曲线上外推点的函数,类似于关键帧插值?

时间:2011-08-26 14:02:52

标签: flash actionscript-3 math geometry

我希望有人可以帮我制作一些高级数据重新格式化。我希望的是一个函数,我可以输入一个值以及一系列关键位置,如下所示:

function remap(percentage:Number, keypoints:Array) { ...

数组以最小值开始,以最大点结束,沿途有嵌套关键点。例如,我会输入类似remap(0.25, [0:0,80:50,100:100] )的函数,函数将“想象”来自(0,0) - (100,100)的样条曲线图,其关键点为(80,50),然后返回y值该图表中有25%。

希望这很清楚......有什么想法吗?

1 个答案:

答案 0 :(得分:14)

Hermite曲线的等式是:

Hermite Curve Equation (通过Wikipedia

其中 p(t)是t处曲线上的点(百分比0.0到1.0)

  1. p0是第一个控制点
  2. m0是第一个锚点
  3. p1是第二个控制点
  4. m1是第二个控制点
  5. 因此,动作中的等式将是这样的:

            /*
             * Computes x,y values for a given traversal of a Hermite Curve
             * @param t:Number - a normalized value (0.0 to 1.0) describing path traversal
             * @param points:Array - an array contining the 4 points describing the curve (P0,T0,P1,T1 - always in this order)
             * Anchor points are relative to they're control points
             */
            private function hermite(t:Number,points:Array):Point{
                var result:Point = new Point();
                result.x = (2 * Math.pow(t,3) - 3 * t * t + 1) * points[0].x+
                            (Math.pow(t,3) - 2 * t * t + t) * points[1].x + 
                            (- 2 * Math.pow(t,3) + 3*t*t) * points[2].x +
                            ( Math.pow(t,3) - t*t) * points[3].x;
                result.y = (2 * Math.pow(t,3) - 3 * t * t + 1) * points[0].y+
                            (Math.pow(t,3) - 2 * t * t + t) * points[1].y + 
                            (- 2 * Math.pow(t,3) + 3*t*t) * points[2].y +
                            ( Math.pow(t,3) - t*t) * points[3].y;
                return result;
            }
    

    您可以看到基本的演示here Hermite Curve Basic Demo

    不过,我有点担心,因为你的例子提到3分(2个控制点和1个锚点)。

    立方曲线(Hermite / Catmull-Rom /等)有2个控制点和2个锚点(幂次方程为3 - 立方)

    如果您只需要一个控制点,则需要使用Quadratic CurveQuadratic vs Cubic

    (来自Adobe Actionscript 3文档的图片)

    立方曲线: Cubic

    二次曲线: Quadratic

    (来自维基百科的动画)

    二次方程是这样的: Quadratic Equation

    哪个会转换为:

    private function quad(t:Number,p:Array):Point{
                var result:Point = new Point();
                var oneMinusTSq:Number = (1-t) * (1-t);
                var TSq:Number = t*t;
                result.x = oneMinusTSq*p[0].x+2*(1-t)*t*p[1].x+TSq*p[2].x;
                result.y = oneMinusTSq*p[0].y+2*(1-t)*t*p[1].y+TSq*p[2].y;
                return result;
            }
    

    还有一些测试代码:

    package {
        import flash.display.Sprite;
        import flash.events.Event;
        import flash.events.MouseEvent;
        import flash.geom.Point;
    
        /**
         * @author george
         */
        public class BasicQuad extends Sprite {
            private var p0:Point = new Point(0,0);
            private var p1:Point = new Point(80,50);
            private var p2:Point = new Point(100,100);
            private var pts:Array = [p0,p1,p2];
            private var t:Number = 0;
            private var pt : Point;
    
            public function BasicQuad() {
                init();
            }
            private function init():void{
                stage.doubleClickEnabled = true;
                stage.addEventListener(MouseEvent.DOUBLE_CLICK, reset);
                reset();
            }
            private function reset(event : MouseEvent = null) : void {
                graphics.clear();
                graphics.lineStyle(3,0x009900,.5);
                t = 0;
                this.addEventListener(Event.ENTER_FRAME, draw);
            }
            private function draw(event : Event) : void {
                trace(t,pt);
                pt = quad(t, pts);
                if(t == 0) graphics.moveTo(pt.x,pt.y);//draw
                graphics.lineTo(pt.x,pt.y);
                t+= 0.015;
                if(t >= 1) removeEventListener(Event.ENTER_FRAME, draw);//done
            }
            private function quad(t:Number,p:Array):Point{
                var result:Point = new Point();
                var oneMinusTSq:Number = (1-t) * (1-t);
                var TSq:Number = t*t;
                result.x = oneMinusTSq*p[0].x+2*(1-t)*t*p[1].x+TSq*p[2].x;
                result.y = oneMinusTSq*p[0].y+2*(1-t)*t*p[1].y+TSq*p[2].y;
                return result;
            }
        }
    }
    

    另外,我不清楚你的意思

      

    高级数据重新格式化

    代码片段是作为代码编写的公式,但还有其他方法来计算它。

      

    二次Bézier曲线是函数B(t)追踪的路径,   给定点P0,P1和P2,

    我们需要从P0到P1,从P1到P2。 由于您正在寻找Flash / ActionScript解决方案,我们可以利用Point的interpolate()方法。 所以我们在P0和P1之间进行插值,让我们说P01 然后从P1到P2得到P12,通过所有3个点进行插值 P01和P12之间的插值:

    function quadLerp(t:Number,p:Array):Point {
                var p1:Point = Point.interpolate(p[1], p[0], t);
                var p2:Point = Point.interpolate(p[2], p[1], t);
                return Point.interpolate(p2, p1, t);
            }
    

    由于动作脚本插值的实现方式,代码看起来与我上面写的有点相反:“两点之间的插值水平。指示新点的位置,沿着pt1和pt2之间的线。 f = 1,返回pt1;如果f = 0,则返回pt2。“

    <强>更新

    进一步混淆:

      

    参考你的问题:我的例子实际上提到了3个控制   点数和0个锚点

    你试图简单地沿着一系列线(多个点,0个锚点......直线)得到当前x的y值吗?

      

    记住我不需要图表本身 - 我只需要一点   只需走一条直线图/锯齿线(无曲线)?

    如果,那么,你可以这样做:

    1. 遍历路径的所有点并找到当前x值的行(这将是起始x位置小于给定x的行,并且行的结束x位置大于给定x位置)
    2. 计算从行首开始到给定x的x距离与整行(end.x-start.x)之间的比率
    3. 使用此比率划分当前行'height'(end.y和start.y之间的差异)并将其偏移start.y,利用similar triangles, according to Thales' Theorem
    4. 这是一个快速草图来说明这个想法: Thales 想象一个直角三角形,其中当前行是连字符(ABC)。现在想象一下你的鼠标光标的垂直线将该三角形分成两个相似的三角形(OO')。小三角形与大角形具有相同的角度,并且它的边是成比例的。使用AO和AB之间的比率来划分AC,并获得OO'的长度(该x的线上的y位置)。

      这是功能:

      private function getYforX(x:Number,pts:Vector.<Point>):Number{
                  var numPts:int = pts.length;
                  for (var i : int = 1; i < numPts; i++) {
                      if(x > pts[i-1].x && x < pts[i].x) {//find the line on which the cursor lies
                          t = (x-pts[i-1].x)/(pts[i].x-pts[i-1].x);//ratio between the x distance from the start of the line to mouseX and the whole line (end.x-start.x)
                          return pts[i-1].y + ((pts[i].y-pts[i-1].y) * t);//Thales similar triangles version, cheaper version of Point.interpolate(pts[i], pts[i-1], t).y; 
                      }
                  }
                  return -1;
              }
      

      快速演示:

      package {
          import flash.events.*;
          import flash.display.*;
          import flash.geom.Point;
      
          public class LerpPoints extends Sprite {
      
              private var path:Shape = new Shape();
              private var cursor:Shape = new Shape();
              private var numPts:int = 11;
              private var pts:Vector.<Point> = new Vector.<Point>(numPts,true);
              private var t:Number = 0;
      
              public function LerpPoints() {
                  init();
              }
              private function init():void{
                  cursor.graphics.lineStyle(10,0x009900);
                  cursor.graphics.drawCircle(-3, -3, 3);
                  cursor.graphics.lineStyle(1,0x000099);
                  cursor.graphics.moveTo(0, -stage.stageHeight);
                  cursor.graphics.lineTo(0, stage.stageHeight);
                  reset();
                  addChild(path);addChild(cursor);
                  addEventListener(Event.ENTER_FRAME, update);
                  stage.addEventListener(MouseEvent.MOUSE_DOWN, reset);
              }
              private function reset(event:Event = null):void{
                  path.graphics.clear();
                  for (var i : int = 0; i < numPts; i++) {
                      pts[i] = new Point(i*55,Math.random() * 200);//generate points
                      path.graphics.lineStyle(3,0);
                      if(i == 0) path.graphics.moveTo(pts[0].x,pts[0].y);//draw path
                      path.graphics.lineTo(pts[i].x,pts[i].y);
                      if(i > 0){//right angled triangles
                          path.graphics.lineStyle(1,0x990000);
                          path.graphics.lineTo(pts[i-1].x,pts[i].y);
                          path.graphics.lineTo(pts[i-1].x,pts[i-1].y);
                          path.graphics.moveTo(pts[i].x,pts[i].y);
                      }
                  }
              }
              private function update(event:Event):void{
                  cursor.x = mouseX;
                  cursor.y = getYforX(mouseX, pts);
              }
              private function getYforX(x:Number,pts:Vector.<Point>):Number{
                  var numPts:int = pts.length;
                  for (var i : int = 1; i < numPts; i++) {
                      if(x > pts[i-1].x && x < pts[i].x) {//find the line on which the cursor lies
                          t = (x-pts[i-1].x)/(pts[i].x-pts[i-1].x);//ratio between the x distance from the start of the line to mouseX and the whole line (end.x-start.x)
                          return pts[i-1].y + ((pts[i].y-pts[i-1].y) * t);//Thales similar triangles version, cheaper version of Point.interpolate(pts[i], pts[i-1], t).y; 
                      }
                  }
                  return -1;
              }
          }
      }
      

      请注意,如果点数组中的x值按升序排序(例如,您的路径仅从左到右),则此方法有效。

      一个肮脏的黑客攻击是循环遍历点对并将Y值存储在查找表中。循环次数应为'行细节'

      然后,这让我感到困惑:

        

      只需要评估一系列点(不仅仅是2),理想情况下   样条曲线而不是简单地连接点   所以你有多个点,但是样条曲线在哪里,因为你提到了0个锚点?

      HTH